From 29872ced41513317509d0503bc87e16f99db1aed Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Sun, 27 Sep 2015 17:35:23 +0200 Subject: [PATCH 001/178] Fix crash in upscaleMipMapForDepth --- Engine/Image.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Engine/Image.cpp b/Engine/Image.cpp index b28f02338c..6953e08757 100644 --- a/Engine/Image.cpp +++ b/Engine/Image.cpp @@ -1740,13 +1740,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]; } From d270d8cde567bfb1fca4c59cf230b1d07fc5f42c Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Sun, 27 Sep 2015 17:59:39 +0200 Subject: [PATCH 002/178] RenderRoi: bug fix with render scale support = 0 --- Engine/EffectInstanceRenderRoI.cpp | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/Engine/EffectInstanceRenderRoI.cpp b/Engine/EffectInstanceRenderRoI.cpp index 31dab7baba..91e05c56c2 100644 --- a/Engine/EffectInstanceRenderRoI.cpp +++ b/Engine/EffectInstanceRenderRoI.cpp @@ -818,7 +818,7 @@ EffectInstance::renderRoI(const RenderRoIArgs & args, #if NATRON_ENABLE_TRIMAP if (!frameRenderArgs.canAbort && frameRenderArgs.isRenderResponseToUserInteraction) { #ifndef DEBUG - isPlaneCached->getRestToRender_trimap(roi, rectsLeftToRender, &planesToRender.isBeingRenderedElsewhere); + isPlaneCached->getRestToRender_trimap(upscaledRoi, rectsLeftToRender, &planesToRender.isBeingRenderedElsewhere); #else // in debug mode, check that the result of getRestToRender_trimap and getRestToRender is the same if the image // is not currently rendered concurrently @@ -832,9 +832,9 @@ EffectInstance::renderRoI(const RenderRoIArgs & args, } if (!ibr) { Image::ReadAccess racc( isPlaneCached.get() ); - isPlaneCached->getRestToRender_trimap(roi, rectsLeftToRender, &planesToRender.isBeingRenderedElsewhere); + isPlaneCached->getRestToRender_trimap(upscaledRoi, rectsLeftToRender, &planesToRender.isBeingRenderedElsewhere); std::list tmpRects; - isPlaneCached->getRestToRender(roi, tmpRects); + isPlaneCached->getRestToRender(upscaledRoi, tmpRects); //If it crashes here that means the image is no longer being rendered but its bitmap still contains PIXEL_UNAVAILABLE pixels. //The other thread should have removed that image from the cache or marked the image as rendered. @@ -846,14 +846,14 @@ EffectInstance::renderRoI(const RenderRoIArgs & args, assert(*it == *oIt); } } else { - isPlaneCached->getRestToRender_trimap(roi, rectsLeftToRender, &planesToRender.isBeingRenderedElsewhere); + isPlaneCached->getRestToRender_trimap(upscaledRoi, rectsLeftToRender, &planesToRender.isBeingRenderedElsewhere); } #endif } else { - isPlaneCached->getRestToRender(roi, rectsLeftToRender); + isPlaneCached->getRestToRender(upscaledRoi, rectsLeftToRender); } #else - isPlaneCached->getRestToRender(roi, rectsLeftToRender); + isPlaneCached->getRestToRender(upscaledRoi, rectsLeftToRender); #endif if ( isDuringPaintStroke && !rectsLeftToRender.empty() && !lastStrokePixelRoD.isNull() ) { rectsLeftToRender.clear(); @@ -1404,9 +1404,11 @@ EffectInstance::renderRoI(const RenderRoIArgs & args, for (std::map::iterator it = planesToRender.planes.begin(); it != planesToRender.planes.end(); ++it) { //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(), @@ -1417,7 +1419,7 @@ EffectInstance::renderRoI(const RenderRoIArgs & args, false) ); } - it->second.fullscaleImage->downscaleMipMap( it->second.fullscaleImage->getRoD(), roi, 0, args.mipMapLevel, false, it->second.downscaleImage.get() ); + it->second.fullscaleImage->downscaleMipMap( it->second.fullscaleImage->getRoD(), upscaledRoi, 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(); From 3ecd1b28c8fe4ca3194cd1f71075a94be8555c4a Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Sun, 27 Sep 2015 20:46:43 +0200 Subject: [PATCH 003/178] Draft mode: only add to key if it is used by the plug-in. Also cache image if render scale is not supported but inputs are computed with mipmaplevel > 0 (just added a flag to the key for that) --- Engine/EffectInstance.cpp | 221 ++++++++++------------------- Engine/EffectInstance.h | 7 +- Engine/EffectInstanceRenderRoI.cpp | 122 ++++++++-------- Engine/Image.cpp | 7 +- Engine/Image.h | 3 +- Engine/ImageKey.cpp | 12 +- Engine/ImageKey.h | 7 +- Engine/OfxEffectInstance.cpp | 6 + Engine/OfxEffectInstance.h | 1 + Engine/RotoContext.cpp | 2 +- libs/OpenFX | 2 +- 11 files changed, 166 insertions(+), 224 deletions(-) diff --git a/Engine/EffectInstance.cpp b/Engine/EffectInstance.cpp index 501ff7ad6e..f654a41f90 100644 --- a/Engine/EffectInstance.cpp +++ b/Engine/EffectInstance.cpp @@ -1476,7 +1476,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 +1486,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 +1525,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 +1641,6 @@ EffectInstance::tiledRenderingFunctor( TiledRenderingFunctorArgs & args, specificData, args.frameTLS, args.renderFullScaleThenDownscale, - args.renderUseScaleOneInputs, args.isSequentialRender, args.isRenderResponseToUserInteraction, args.firstFrame, @@ -1676,7 +1666,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 +1686,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 +1712,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); } @@ -1745,7 +1733,7 @@ EffectInstance::tiledRenderingFunctor(const QThread* callingThread, // check the bitmap! 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); @@ -1786,28 +1774,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 +1846,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,7 +1909,6 @@ EffectInstance::tiledRenderingFunctor(const QThread* callingThread, rectToRender.identityTime, rectToRender.identityInput, renderFullScaleThenDownscale, - renderUseScaleOneInputs, isSequentialRender, isRenderResponseToUserInteraction, renderMappedRectToRender, @@ -1967,7 +1940,6 @@ EffectInstance::renderHandler(RenderArgs & args, double identityTime, Natron::EffectInstance* identityInput, bool renderFullScaleThenDownscale, - bool renderUseScaleOneInputs, bool isSequentialRender, bool isRenderResponseToUserInteraction, const RectI & renderMappedRectToRender, @@ -1999,7 +1971,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) { @@ -2037,7 +2008,7 @@ EffectInstance::renderHandler(RenderArgs & args, 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 +2029,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 +2047,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); @@ -2165,7 +2136,7 @@ EffectInstance::renderHandler(RenderArgs & args, #if NATRON_ENABLE_TRIMAP if (!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 +2196,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 +2254,38 @@ 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); + + + 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.fullscaleImage->markForRendered(renderMappedRectToRender); + } else { // if (renderFullScaleThenDownscale) { ///Copy the rectangle rendered in the downscaled image if (it->second.tmpImage != it->second.downscaleImage) { @@ -2428,7 +2351,7 @@ EffectInstance::allocateImagePlaneAndSetInThreadLocalStorage(const Natron::Image 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(), img->getRoD(), img->getBounds(), img->getBounds(), false, params->getFramesNeeded(), plane, img->getBitDepth(), img->getPixelAspectRatio(), img->getMipMapLevel(), false, false, useCache, &p.fullscaleImage, &p.downscaleImage); if (!ok) { return ImagePtr(); } else { diff --git a/Engine/EffectInstance.h b/Engine/EffectInstance.h index d471d61239..65f7e8b56e 100644 --- a/Engine/EffectInstance.h +++ b/Engine/EffectInstance.h @@ -1127,6 +1127,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 @@ -1636,7 +1638,6 @@ class EffectInstance bool isRenderMadeInResponseToUserInteraction, U64 nodeHash, bool renderFullScaleThenDownscale, - bool useScaleOneInputImages, bool byPassCache, Natron::ImageBitDepthEnum outputClipPrefDepth, const std::list & outputClipPrefsComps, @@ -1694,7 +1695,6 @@ class EffectInstance double par, unsigned int mipmapLevel, bool renderFullScaleThenDownscale, - bool renderScaleOneUpstreamIfRenderScaleSupportDisabled, bool useDiskCache, bool createInCache, boost::shared_ptr* fullScaleImage, @@ -1725,7 +1725,6 @@ class EffectInstance ParallelRenderArgs frameArgs; std::map, ParallelRenderArgs > frameTLS; bool renderFullScaleThenDownscale; - bool renderUseScaleOneInputs; bool isSequentialRender; bool isRenderResponseToUserInteraction; int firstFrame; @@ -1753,7 +1752,6 @@ class EffectInstance const RectToRender & rectToRender, const std::map, ParallelRenderArgs > & frameTls, bool renderFullScaleThenDownscale, - bool renderUseScaleOneInputs, bool isSequentialRender, bool isRenderResponseToUserInteraction, int firstFrame, int lastFrame, @@ -1800,7 +1798,6 @@ class EffectInstance double identityTime, Natron::EffectInstance* identityInput, bool renderFullScaleThenDownscale, - bool renderUseScaleOneInputs, bool isSequentialRender, bool isRenderResponseToUserInteraction, const RectI & renderMappedRectToRender, diff --git a/Engine/EffectInstanceRenderRoI.cpp b/Engine/EffectInstanceRenderRoI.cpp index 91e05c56c2..4d3f41341d 100644 --- a/Engine/EffectInstanceRenderRoI.cpp +++ b/Engine/EffectInstanceRenderRoI.cpp @@ -291,37 +291,18 @@ EffectInstance::renderRoI(const RenderRoIArgs & args, } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////// End get RoD /////////////////////////////////////////////////////////////// - - - /*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; + upscaledRoi = roi; } @@ -464,7 +445,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; @@ -594,7 +575,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; } @@ -613,24 +594,40 @@ EffectInstance::renderRoI(const RenderRoIArgs & args, 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 = supportsRenderQuality(); bool isFrameVaryingOrAnimated = isFrameVaryingOrAnimated_Recursive(); bool createInCache = shouldCacheOutput(isFrameVaryingOrAnimated); - Natron::ImageKey key = Natron::Image::makeKey(getNode().get(),nodeHash, isFrameVaryingOrAnimated, args.time, args.view, frameRenderArgs.draftMode); + 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 +672,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 +896,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 +1003,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 +1079,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 +1106,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 +1130,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 +1190,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,7 +1209,7 @@ 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, + bool hasResized = it->second.fullscaleImage->ensureBounds(renderFullScaleThenDownscale ? upscaledImageBounds : downscaledImageBounds, fillGrownBoundsWithZeroes, fillGrownBoundsWithZeroes); @@ -1254,7 +1259,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 +1319,6 @@ EffectInstance::renderRoI(const RenderRoIArgs & args, frameRenderArgs.isRenderResponseToUserInteraction, nodeHash, renderFullScaleThenDownscale, - renderScaleOneUpstreamIfRenderScaleSupportDisabled, byPassCache, outputDepth, outputClipPrefComps, @@ -1332,17 +1336,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 +1380,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,8 +1391,8 @@ EffectInstance::renderRoI(const RenderRoIArgs & args, } std::list restToRender; - if (useImageAsOutput) { - it->second.fullscaleImage->getRestToRender(roi, restToRender); + if (renderFullScaleThenDownscale) { + it->second.fullscaleImage->getRestToRender(upscaledRoi, restToRender); } else { it->second.downscaleImage->getRestToRender(roi, restToRender); } @@ -1478,7 +1482,6 @@ EffectInstance::renderRoIInternal(double time, bool isRenderMadeInResponseToUserInteraction, U64 nodeHash, bool renderFullScaleThenDownscale, - bool useScaleOneInputImages, bool byPassCache, Natron::ImageBitDepthEnum outputClipPrefDepth, const std::list & outputClipPrefsComps, @@ -1600,7 +1603,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; @@ -1659,7 +1661,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/Image.cpp b/Engine/Image.cpp index 6953e08757..79fe6d747c 100644 --- a/Engine/Image.cpp +++ b/Engine/Image.cpp @@ -634,7 +634,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, @@ -695,9 +695,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 diff --git a/Engine/Image.h b/Engine/Image.h index 73bd83ba7f..e56222b8bc 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, 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/OfxEffectInstance.cpp b/Engine/OfxEffectInstance.cpp index 5e0d42318a..d9847bf455 100644 --- a/Engine/OfxEffectInstance.cpp +++ b/Engine/OfxEffectInstance.cpp @@ -2714,6 +2714,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..ecb21477bc 100644 --- a/Engine/OfxEffectInstance.h +++ b/Engine/OfxEffectInstance.h @@ -231,6 +231,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; diff --git a/Engine/RotoContext.cpp b/Engine/RotoContext.cpp index 1824558d0c..55fff61594 100644 --- a/Engine/RotoContext.cpp +++ b/Engine/RotoContext.cpp @@ -2353,7 +2353,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/libs/OpenFX b/libs/OpenFX index de8216d05b..6945782584 160000 --- a/libs/OpenFX +++ b/libs/OpenFX @@ -1 +1 @@ -Subproject commit de8216d05b0d4f230734d1b7a2b7b7144a4f9f15 +Subproject commit 6945782584e9806a655f32b9459280d53c06e143 From 3969ae6c1383e6efeac2da27e41c8d1fb2cd297d Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Sun, 27 Sep 2015 20:51:33 +0200 Subject: [PATCH 004/178] Remove upscaledRoi --- Engine/EffectInstanceRenderRoI.cpp | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/Engine/EffectInstanceRenderRoI.cpp b/Engine/EffectInstanceRenderRoI.cpp index 4d3f41341d..b3c061e9b0 100644 --- a/Engine/EffectInstanceRenderRoI.cpp +++ b/Engine/EffectInstanceRenderRoI.cpp @@ -291,7 +291,7 @@ EffectInstance::renderRoI(const RenderRoIArgs & args, } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////// End get RoD /////////////////////////////////////////////////////////////// - RectI roi,upscaledRoi; + RectI roi; if (renderFullScaleThenDownscale) { //We cache 'image', hence the RoI should be expressed in its coordinates @@ -299,10 +299,8 @@ EffectInstance::renderRoI(const RenderRoIArgs & args, RectD canonicalRoI; args.roi.toCanonical(args.mipMapLevel, par, rod, &canonicalRoI); canonicalRoI.toPixelEnclosing(0, par, &roi); - upscaledRoi = roi; } else { roi = args.roi; - upscaledRoi = roi; } @@ -590,7 +588,7 @@ 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 { @@ -823,7 +821,7 @@ EffectInstance::renderRoI(const RenderRoIArgs & args, #if NATRON_ENABLE_TRIMAP if (!frameRenderArgs.canAbort && frameRenderArgs.isRenderResponseToUserInteraction) { #ifndef DEBUG - isPlaneCached->getRestToRender_trimap(upscaledRoi, rectsLeftToRender, &planesToRender.isBeingRenderedElsewhere); + isPlaneCached->getRestToRender_trimap(roi, rectsLeftToRender, &planesToRender.isBeingRenderedElsewhere); #else // in debug mode, check that the result of getRestToRender_trimap and getRestToRender is the same if the image // is not currently rendered concurrently @@ -837,9 +835,9 @@ EffectInstance::renderRoI(const RenderRoIArgs & args, } if (!ibr) { Image::ReadAccess racc( isPlaneCached.get() ); - isPlaneCached->getRestToRender_trimap(upscaledRoi, rectsLeftToRender, &planesToRender.isBeingRenderedElsewhere); + isPlaneCached->getRestToRender_trimap(roi, rectsLeftToRender, &planesToRender.isBeingRenderedElsewhere); std::list tmpRects; - isPlaneCached->getRestToRender(upscaledRoi, tmpRects); + isPlaneCached->getRestToRender(roi, tmpRects); //If it crashes here that means the image is no longer being rendered but its bitmap still contains PIXEL_UNAVAILABLE pixels. //The other thread should have removed that image from the cache or marked the image as rendered. @@ -851,14 +849,14 @@ EffectInstance::renderRoI(const RenderRoIArgs & args, assert(*it == *oIt); } } else { - isPlaneCached->getRestToRender_trimap(upscaledRoi, rectsLeftToRender, &planesToRender.isBeingRenderedElsewhere); + isPlaneCached->getRestToRender_trimap(roi, rectsLeftToRender, &planesToRender.isBeingRenderedElsewhere); } #endif } else { - isPlaneCached->getRestToRender(upscaledRoi, rectsLeftToRender); + isPlaneCached->getRestToRender(roi, rectsLeftToRender); } #else - isPlaneCached->getRestToRender(upscaledRoi, rectsLeftToRender); + isPlaneCached->getRestToRender(roi, rectsLeftToRender); #endif if ( isDuringPaintStroke && !rectsLeftToRender.empty() && !lastStrokePixelRoD.isNull() ) { rectsLeftToRender.clear(); @@ -1392,7 +1390,7 @@ EffectInstance::renderRoI(const RenderRoIArgs & args, std::list restToRender; if (renderFullScaleThenDownscale) { - it->second.fullscaleImage->getRestToRender(upscaledRoi, restToRender); + it->second.fullscaleImage->getRestToRender(roi, restToRender); } else { it->second.downscaleImage->getRestToRender(roi, restToRender); } @@ -1423,7 +1421,7 @@ EffectInstance::renderRoI(const RenderRoIArgs & args, false) ); } - it->second.fullscaleImage->downscaleMipMap( it->second.fullscaleImage->getRoD(), upscaledRoi, 0, args.mipMapLevel, false, it->second.downscaleImage.get() ); + 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(); From 7454656de1bae26def8ca447568dbcfe8ed1b4b5 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Sun, 27 Sep 2015 21:06:34 +0200 Subject: [PATCH 005/178] Fix renderFullScale --- Engine/EffectInstance.cpp | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/Engine/EffectInstance.cpp b/Engine/EffectInstance.cpp index f654a41f90..73dfb69eeb 100644 --- a/Engine/EffectInstance.cpp +++ b/Engine/EffectInstance.cpp @@ -2258,32 +2258,34 @@ EffectInstance::renderHandler(RenderArgs & args, 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() ) ) { + 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.downscaleImage->getComponents(), it->second.tmpImage->getRoD(), it->second.tmpImage->getBounds(), mipMapLevel, it->second.tmpImage->getPixelAspectRatio(), it->second.downscaleImage->getBitDepth(), false) ); + 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.downscaleImage->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.downscaleImage->copyUnProcessedChannels(downscaledRectToRender, planes.outputPremult, originalImagePremultiplication, processChannels, originalInputImage); - if (useMaskMix) { - it->second.downscaleImage->applyMaskMix(downscaledRectToRender, maskImage.get(), originalInputImage.get(), doMask, false, mix); - } + it->second.fullscaleImage->markForRendered(renderMappedRectToRender); } else { // if (renderFullScaleThenDownscale) { From f62a727e6aeb385ef35920c61b96913afea5f468 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Sun, 27 Sep 2015 22:38:35 +0200 Subject: [PATCH 006/178] Fix unit test --- Tests/Image_Test.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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); } From 903213f5d8c41e1694a38f26fa612e6d9a771564 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Sun, 27 Sep 2015 23:35:42 +0200 Subject: [PATCH 007/178] update openfx --- libs/OpenFX | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/OpenFX b/libs/OpenFX index 6945782584..2b409121b4 160000 --- a/libs/OpenFX +++ b/libs/OpenFX @@ -1 +1 @@ -Subproject commit 6945782584e9806a655f32b9459280d53c06e143 +Subproject commit 2b409121b4ef8e89c92e19dd8a77400c6d4cfd8f From d486d851efeeaf933521d004e31dbdaffa186932 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole-Andr=C3=A9=20Rodlie?= Date: Mon, 28 Sep 2015 03:34:35 +0200 Subject: [PATCH 008/178] Linux SDK: build librsvg+pango (and depends, total 14 new) --- tools/linux/common.sh | 16 +- tools/linux/include/scripts/build-sdk.sh | 287 ++++++++++++++++--- tools/linux/include/scripts/setup-centos6.sh | 2 +- 3 files changed, 258 insertions(+), 47 deletions(-) diff --git a/tools/linux/common.sh b/tools/linux/common.sh index d65c35e572..dee0ccdfb3 100644 --- a/tools/linux/common.sh +++ b/tools/linux/common.sh @@ -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,18 @@ 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 TC_GCC=4.8.5 TC_MPC=1.0.1 diff --git a/tools/linux/include/scripts/build-sdk.sh b/tools/linux/include/scripts/build-sdk.sh index f5d7e7ace2..9cec28b81d 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" --enable-static --enable-shared || 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 @@ -217,6 +261,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 +397,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 +500,99 @@ 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 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 +629,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 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 From a3a9932f3664ed83bb4c873794e4e9dcd60a51f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole-Andr=C3=A9=20Rodlie?= Date: Mon, 28 Sep 2015 07:12:33 +0200 Subject: [PATCH 009/178] Linux: sdk buildfix (missing depend) --- tools/linux/common.sh | 1 + tools/linux/include/scripts/build-sdk.sh | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/tools/linux/common.sh b/tools/linux/common.sh index dee0ccdfb3..82c19af930 100644 --- a/tools/linux/common.sh +++ b/tools/linux/common.sh @@ -170,6 +170,7 @@ 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 diff --git a/tools/linux/include/scripts/build-sdk.sh b/tools/linux/include/scripts/build-sdk.sh index 9cec28b81d..5f50d9969e 100644 --- a/tools/linux/include/scripts/build-sdk.sh +++ b/tools/linux/include/scripts/build-sdk.sh @@ -156,7 +156,7 @@ if [ ! -f "$INSTALL_PATH/lib/libz.so.1" ]; then fi tar xvf "$SRC_PATH/$ZLIB_TAR" || exit 1 cd zlib* || exit 1 - env CFLAGS="$BF" CXXFLAGS="$BF" ./configure --prefix="$INSTALL_PATH" --enable-static --enable-shared || exit 1 + env CFLAGS="$BF" CXXFLAGS="$BF" ./configure --prefix="$INSTALL_PATH" || exit 1 make -j${MKJOBS} || exit 1 make install || exit 1 fi @@ -580,6 +580,19 @@ if [ ! -f "$INSTALL_PATH/lib/libcroco-0.6.so.3" ]; then 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 From 7892b81cb539bff08eb61e1ad2a8434db45d7330 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole-Andr=C3=A9=20Rodlie?= Date: Mon, 28 Sep 2015 08:12:28 +0200 Subject: [PATCH 010/178] Linux: deploy libs changes --- .../linux/include/scripts/build-installer.sh | 57 ++++++++++++++++++- 1 file changed, 54 insertions(+), 3 deletions(-) diff --git a/tools/linux/include/scripts/build-installer.sh b/tools/linux/include/scripts/build-installer.sh index c875a8293a..08dd5891b0 100644 --- a/tools/linux/include/scripts/build-installer.sh +++ b/tools/linux/include/scripts/build-installer.sh @@ -108,7 +108,20 @@ for y in $OFX_LIB_DEP; do cp -v $y $IO_LIBS/ || exit 1 done -rm -f $IO_LIBS/{libgcc*,libstdc*} +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/* IO_LIC=$OFX_IO_PATH/meta/ofx-io-license.txt echo "" >>$IO_LIC || exit 1 @@ -261,8 +274,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 @@ -323,6 +347,19 @@ 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 . +) + + # TODO: At this point send unstripped binaries (and debug binaries?) to Socorro server for breakpad strip -s $CLIBS_PATH/data/lib/* @@ -388,8 +425,22 @@ 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/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 From de5e70152654157263732454105b80e82c39c206 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole-Andr=C3=A9=20Rodlie?= Date: Mon, 28 Sep 2015 09:14:01 +0200 Subject: [PATCH 011/178] Linux: fontconfig --- tools/linux/include/scripts/Natron.sh | 4 ++++ tools/linux/include/scripts/build-installer.sh | 3 +++ 2 files changed, 7 insertions(+) diff --git a/tools/linux/include/scripts/Natron.sh b/tools/linux/include/scripts/Natron.sh index 58c7e0eec7..efc2d8b165 100644 --- a/tools/linux/include/scripts/Natron.sh +++ b/tools/linux/include/scripts/Natron.sh @@ -12,6 +12,10 @@ DIR=`cd -P "$SOURCEDIR" && pwd` export LC_NUMERIC=C +if [ -d "$DIR/Resources/etc/fonts" ]; then + export FONTCONFIG_PATH=$DIR/Resources/etc/fonts +fi + if [ "$1" = "-update" -a -x "$DIR/NatronSetup" ]; then "$DIR/NatronSetup" --updater exit 0 diff --git a/tools/linux/include/scripts/build-installer.sh b/tools/linux/include/scripts/build-installer.sh index 08dd5891b0..b8fe817ff2 100644 --- a/tools/linux/include/scripts/build-installer.sh +++ b/tools/linux/include/scripts/build-installer.sh @@ -359,6 +359,9 @@ mv $IO_LIBS/{libOpenColor*,libgomp*} $CLIBS_PATH/data/lib/ || exit 1 ln -sf ../../../lib/libgomp.so.1 . ) +mkdir -p $CLIBS_PATH/data/share/etc || exit 1 +cp -a $INSTALL_PATH/etc/fonts $CLIBS_PATH/data/share/etc/ || 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 From 1ce4b8298559f63928d5c4a64a83d2589aa45bf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole-Andr=C3=A9=20Rodlie?= Date: Mon, 28 Sep 2015 09:15:11 +0200 Subject: [PATCH 012/178] AppManager: set fontconfig path on all platforms --- Engine/AppManager.cpp | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/Engine/AppManager.cpp b/Engine/AppManager.cpp index 2eea4cf740..112b6ba1d8 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,8 +161,6 @@ AppManager::load(int &argc, qputenv("FONTCONFIG_PATH", path.toUtf8()); } } -#endif - initPython(argc, argv); From eb79712772ee35739dbb5c4d3cbbbbf33840b184 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Mon, 28 Sep 2015 09:27:10 +0200 Subject: [PATCH 013/178] Dopesheet: handle recursion in frame range computation. Also try catch exception --- Engine/AppManager.cpp | 8 +++-- Engine/Node.cpp | 5 ++- Engine/NodeWrapper.cpp | 2 +- Gui/DopeSheetView.cpp | 75 ++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 84 insertions(+), 6 deletions(-) diff --git a/Engine/AppManager.cpp b/Engine/AppManager.cpp index 2eea4cf740..7cb61d9160 100644 --- a/Engine/AppManager.cpp +++ b/Engine/AppManager.cpp @@ -178,8 +178,12 @@ AppManager::load(int &argc, } #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 diff --git a/Engine/Node.cpp b/Engine/Node.cpp index 0a22ea783d..cb3d0df690 100644 --- a/Engine/Node.cpp +++ b/Engine/Node.cpp @@ -2362,11 +2362,14 @@ Node::makeCacheInfo() const std::string Node::makeInfoForInput(int inputNumber) const { + if (inputNumber < 0 || 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"; diff --git a/Engine/NodeWrapper.cpp b/Engine/NodeWrapper.cpp index 7406071c2c..d77ee1846e 100644 --- a/Engine/NodeWrapper.cpp +++ b/Engine/NodeWrapper.cpp @@ -144,7 +144,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(); } diff --git a/Gui/DopeSheetView.cpp b/Gui/DopeSheetView.cpp index 65738a1bc5..33fc015c9e 100644 --- a/Gui/DopeSheetView.cpp +++ b/Gui/DopeSheetView.cpp @@ -261,6 +261,8 @@ class DopeSheetViewPrivate // std::map nodeRanges; + std::list nodeRangesBeingComputed; // to avoid recursion in groups + int rangeComputationRecursion; // for rendering QFont *font; @@ -308,6 +310,8 @@ DopeSheetViewPrivate::DopeSheetViewPrivate(DopeSheetView *qq) : gui(0), timeline(), nodeRanges(), + nodeRangesBeingComputed(), + rangeComputationRecursion(0), font(new QFont(appFont,appFontSize)), textRenderer(), kfTexturesIDs(), @@ -1808,6 +1812,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 +1844,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 +1894,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 +1928,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 +1955,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 +2049,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) From be4258463d048b42072164808546977b97a4cd56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole-Andr=C3=A9=20Rodlie?= Date: Mon, 28 Sep 2015 09:27:30 +0200 Subject: [PATCH 014/178] Linux: add missing symlink in installer --- tools/linux/include/scripts/build-installer.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/linux/include/scripts/build-installer.sh b/tools/linux/include/scripts/build-installer.sh index b8fe817ff2..95ee3cd73d 100644 --- a/tools/linux/include/scripts/build-installer.sh +++ b/tools/linux/include/scripts/build-installer.sh @@ -431,6 +431,7 @@ 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 . From a9dc15b878d837237d7f8a576667641b4bac040e Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Mon, 28 Sep 2015 09:43:59 +0200 Subject: [PATCH 015/178] Fix #921 --- Gui/Edge.cpp | 8 +++++++- Gui/NodeGraph20.cpp | 7 ++++--- 2 files changed, 11 insertions(+), 4 deletions(-) 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/NodeGraph20.cpp b/Gui/NodeGraph20.cpp index 892457b7df..219f24b7b1 100644 --- a/Gui/NodeGraph20.cpp +++ b/Gui/NodeGraph20.cpp @@ -228,7 +228,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 +312,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 +325,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; } From 1cd0c193de3d11283c106b99facc7ceb1f067642 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Mon, 28 Sep 2015 16:02:14 +0200 Subject: [PATCH 016/178] Clean up code of all data depending on the graph state (clip preferences, draft state etc...) --- Engine/AppInstance.cpp | 20 +- Engine/AppManager.cpp | 5 +- Engine/AppManager.h | 3 +- Engine/EffectInstance.cpp | 38 +-- Engine/EffectInstance.h | 36 +-- Engine/EffectInstanceRenderRoI.cpp | 2 +- Engine/Engine.pro | 2 +- Engine/Node.cpp | 433 ++++++++++++++++++++++------- Engine/Node.h | 27 +- Engine/NodeGroup.cpp | 4 +- Engine/NodeGroup.h | 2 +- Engine/OfxEffectInstance.cpp | 164 +++-------- Engine/OfxEffectInstance.h | 9 +- Engine/OfxHost.cpp | 5 +- Engine/OfxHost.h | 3 +- Engine/OfxImageEffectInstance.cpp | 29 +- Engine/OfxImageEffectInstance.h | 2 +- Engine/ParallelRenderArgs.cpp | 7 + Engine/Project.cpp | 4 +- Engine/ProjectPrivate.cpp | 2 +- Engine/ViewerInstance.cpp | 19 +- Engine/ViewerInstance.h | 6 +- Gui/Gui.cpp | 8 +- Gui/Gui.h | 5 +- Gui/GuiAppInstance.cpp | 40 ++- Gui/NodeGraph.cpp | 20 +- Gui/NodeGraph.h | 7 +- Gui/NodeGraph05.cpp | 13 +- 28 files changed, 538 insertions(+), 377 deletions(-) diff --git a/Engine/AppInstance.cpp b/Engine/AppInstance.cpp index 418eac6ec3..b60cda406c 100644 --- a/Engine/AppInstance.cpp +++ b/Engine/AppInstance.cpp @@ -1387,17 +1387,17 @@ AppInstance::getAppIDString() const } void -AppInstance::onGroupCreationFinished(const boost::shared_ptr& /*node*/) +AppInstance::onGroupCreationFinished(const boost::shared_ptr& node) { -// 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()) { + NodeGroup* isGrp = dynamic_cast(node->getLiveInstance()); + assert(isGrp); + if (!isGrp) { + return; + } + isGrp->forceComputeInputDependentDataOnAllTrees(); + } } bool diff --git a/Engine/AppManager.cpp b/Engine/AppManager.cpp index a8f2c2c816..27669ff96d 100644 --- a/Engine/AppManager.cpp +++ b/Engine/AppManager.cpp @@ -1464,9 +1464,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..9538c81c4c 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); diff --git a/Engine/EffectInstance.cpp b/Engine/EffectInstance.cpp index 73dfb69eeb..21054840e6 100644 --- a/Engine/EffectInstance.cpp +++ b/Engine/EffectInstance.cpp @@ -3002,11 +3002,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 @@ -4117,11 +4113,6 @@ EffectInstance::getNearestNonIdentity(double time) } } -void -EffectInstance::restoreClipPreferences() -{ - setSupportsRenderScaleMaybe(eSupportsYes); -} void EffectInstance::onNodeHashChanged(U64 hash) @@ -4276,33 +4267,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 65f7e8b56e..32c6ff4135 100644 --- a/Engine/EffectInstance.h +++ b/Engine/EffectInstance.h @@ -673,11 +673,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 +692,6 @@ class EffectInstance bool forceGetClipPrefAction, std::list & markedNodes); - virtual void checkOFXClipPreferences(double /*time*/, - const RenderScale & /*scale*/, - const std::string & /*reason*/, - bool /*forceGetClipPrefAction*/) - { - } public: @@ -907,9 +907,6 @@ class EffectInstance virtual void aboutToRestoreDefaultValues() OVERRIDE FINAL; virtual bool shouldCacheOutput(bool isFrameVaryingOrAnimated) const; -protected: - - /** * @brief Can be derived to get the region that the plugin is capable of filling. * This is meaningful for plugins that generate images or transform images. @@ -917,8 +914,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); /** @@ -1189,11 +1191,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. @@ -1816,10 +1813,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 b3c061e9b0..f194fab81f 100644 --- a/Engine/EffectInstanceRenderRoI.cpp +++ b/Engine/EffectInstanceRenderRoI.cpp @@ -605,7 +605,7 @@ EffectInstance::renderRoI(const RenderRoIArgs & args, } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////// End Compute RoI ///////////////////////////////////////////////////////////////////////// - bool draftModeSupported = supportsRenderQuality(); + bool draftModeSupported = getNode()->isDraftModeUsed(); bool isFrameVaryingOrAnimated = isFrameVaryingOrAnimated_Recursive(); bool createInCache = shouldCacheOutput(isFrameVaryingOrAnimated); diff --git a/Engine/Engine.pro b/Engine/Engine.pro index 04fdd8d013..197b5db5c9 100644 --- a/Engine/Engine.pro +++ b/Engine/Engine.pro @@ -72,7 +72,7 @@ win32-msvc* { # XCode clang 3.5 without optimization generates code that crashes #(Natron on OSX, XCode 6, Spaceship_Natron.ntp) *-xcode { - #QMAKE_CXXFLAGS += -O1 + QMAKE_CXXFLAGS += -O1 } SOURCES += \ diff --git a/Engine/Node.cpp b/Engine/Node.cpp index cb3d0df690..4109cd6bae 100644 --- a/Engine/Node.cpp +++ b/Engine/Node.cpp @@ -269,6 +269,8 @@ struct Node::Implementation , currentSupportTiles(false) , currentSupportOpenGLRender(Natron::ePluginOpenGLRenderSupportNone) , currentSupportSequentialRender(Natron::eSequentialPreferenceNotSequential) + , draftModeUsed(false) + , mustComputeInputRelatedData(true) , duringPaintStrokeCreation(false) , lastStrokeMovementMutex() , lastStrokeMovementBbox() @@ -325,6 +327,7 @@ struct Node::Implementation bool getSelectedLayer(int inputNb,const ChannelSelector& selector, ImageComponents* comp) const; + Node* _publicInterface; boost::weak_ptr group; @@ -483,6 +486,8 @@ struct Node::Implementation bool currentSupportTiles; Natron::PluginOpenGLRenderSupport currentSupportOpenGLRender; Natron::SequentialPreferenceEnum currentSupportSequentialRender; + bool draftModeUsed,mustComputeInputRelatedData; + bool duringPaintStrokeCreation; // protected by lastStrokeMovementMutex mutable QMutex lastStrokeMovementMutex; @@ -499,6 +504,7 @@ struct Node::Implementation bool useAlpha0ToConvertFromRGBToRGBA; bool isBeingDestroyed; + }; /** @@ -606,7 +612,7 @@ Node::load(const std::string & parentMultiInstanceName, } } - + bool hasUsedFileDialog = false; if (func.first) { _imp->liveInstance.reset(func.second(thisShared)); assert(_imp->liveInstance); @@ -631,6 +637,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 +645,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(); } @@ -717,10 +724,31 @@ Node::load(const std::string & parentMultiInstanceName, _imp->nodeCreated = true; - refreshChannelSelectors(serialization.isNull()); + refreshAllInputRelatedData(serialization.isNull()); _imp->runOnNodeCreatedCB(serialization.isNull()); + + ///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 (hasUsedFileDialog) { + boost::shared_ptr fileNameKnob = getKnobByName(kOfxImageEffectFileParamName); + if (fileNameKnob) { + fileNameKnob->evaluateValueChange(0,Natron::eValueChangedReasonUserEdited); + } + } + } // load bool @@ -5167,7 +5195,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 > diffSoFar && diffSoFar != 0) { closestComp = it; } } @@ -5318,32 +5346,51 @@ Node::onInputChanged(int inputNb) } 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(); + + refreshMaskEnabledNess(inputNb); + refreshLayersChoiceSecretness(inputNb); + refreshDynamicProperties(); + + ViewerInstance* isViewer = dynamic_cast(_imp->liveInstance.get()); + if (isViewer) { + isViewer->refreshActiveInputs(inputNb); } - 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); - } + + + if (!getApp()->getProject()->isLoadingProject() && !getApp()->isCreatingPythonGroup()) { + + ///When loading a group (or project) just wait until everything is setup to actually compute input + ///related data such as clip preferences + + /** + * 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); + + forceRefreshAllInputRelatedData(); } - _imp->liveInstance->onInputChanged(inputNb); - _imp->liveInstance->refreshChannelSelectors_recursive(); + _imp->duringInputChangedAction = false; } @@ -6484,6 +6531,232 @@ Node::shouldCacheOutput(bool isFrameVaryingOrAnimated) const } +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); + + 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); + + { + QMutexLocker k(&_imp->pluginsPropMutex); + _imp->mustComputeInputRelatedData = false; + } + + return hasChanged; +} + +bool +Node::isInputRelatedDataDirty() const +{ + QMutexLocker k(&_imp->pluginsPropMutex); + return _imp->mustComputeInputRelatedData; +} + +void +Node::forceRefreshAllInputRelatedData() +{ + markInputRelatedDataDirtyRecursive(); + refreshInputRelatedDataRecursive(); +} + +void +Node::markInputRelatedDataDirtyRecursiveInternal(std::list& markedNodes) { + std::list::iterator found = std::find(markedNodes.begin(), markedNodes.end(), this); + if (found != markedNodes.end()) { + return; + } + { + QMutexLocker k(&_imp->pluginsPropMutex); + _imp->mustComputeInputRelatedData = true; + } + markedNodes.push_back(this); + + std::list outputs; + getOutputsWithGroupRedirection(outputs); + for (std::list::const_iterator it = outputs.begin(); it != outputs.end(); ++it) { + (*it)->markInputRelatedDataDirtyRecursiveInternal( markedNodes ); + } + + + +} + +void +Node::markInputRelatedDataDirtyRecursive() +{ + std::list marked; + markInputRelatedDataDirtyRecursiveInternal(marked); +} + + +void +Node::refreshInputRelatedDataRecursive() +{ + std::list markedNodes; + if (refreshInputRelatedDataInternal(markedNodes)) { + + ///Now notify outputs we have changed + std::list outputs; + getOutputsWithGroupRedirection(outputs); + for (std::list::const_iterator it = outputs.begin(); it != outputs.end(); ++it) { + (*it)->refreshInputRelatedDataInternal( markedNodes ); + } + } +} + +bool +Node::isDraftModeUsed() const +{ + QMutexLocker k(&_imp->pluginsPropMutex); + return _imp->draftModeUsed; +} + void Node::setPosition(double x,double y) { @@ -6599,59 +6872,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 @@ -7162,16 +7382,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; @@ -7182,9 +7403,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; @@ -7242,13 +7462,14 @@ Node::refreshChannelSelectors(bool setValues) } } } - } + } // if (node) { if (!gotColor) { assert(choices.size() > 0); std::vector::iterator pos = choices.begin(); ++pos; colorIndex = 1; + colorComp = ImageComponents::getRGBAComponents(); choices.insert(pos,kNatronRGBAComponentsName); } @@ -7264,9 +7485,20 @@ Node::refreshChannelSelectors(bool setValues) if (!gotMotionBw) { choices.push_back(kNatronBackwardMotionVectorsPlaneName); } - + + 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 (setValues) { assert(colorIndex != -1 && colorIndex >= 0 && colorIndex < (int)choices.size()); @@ -7290,7 +7522,7 @@ Node::refreshChannelSelectors(bool setValues) layerKnob->setValue(i, 0); _imp->liveInstance->endChanges(true); layerKnob->unblockValueChanges(); - if (isColor && _imp->enabledChan[0].lock()) { + if (isColor && it->first == -1 && _imp->enabledChan[0].lock()) { assert(colorIndex != -1); //Since color plane may have changed (RGB, or RGBA or Alpha), adjust the secretness of the checkboxes const std::vector& channels = colorComp.getComponentsNames(); @@ -7306,8 +7538,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; @@ -7317,9 +7549,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; @@ -7378,8 +7609,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); @@ -7403,6 +7646,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..16c627013e 100644 --- a/Engine/Node.h +++ b/Engine/Node.h @@ -916,7 +916,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. @@ -1009,7 +1008,8 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON unsigned int getPluginPythonModuleVersion() const; - void refreshChannelSelectors(bool setValues); + //Returns true if changed + bool refreshChannelSelectors(bool setValues); bool getProcessChannel(int channelIndex) const; @@ -1030,8 +1030,31 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON void removeAllImagesFromCacheWithMatchingIDAndDifferentKey(U64 nodeHashKey); void removeAllImagesFromCache(); + bool isDraftModeUsed() const; + bool isInputRelatedDataDirty() const; + + void forceRefreshAllInputRelatedData(); + private: + void refreshInputRelatedDataRecursive(); + + void refreshAllInputRelatedData(bool canChangeValues); + + bool refreshMaskEnabledNess(int inpubNb); + + bool refreshLayersChoiceSecretness(int inpubNb); + + void markInputRelatedDataDirtyRecursive(); + + void markInputRelatedDataDirtyRecursiveInternal(std::list& markedNodes); + + 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); public Q_SLOTS: diff --git a/Engine/NodeGroup.cpp b/Engine/NodeGroup.cpp index 5d137cdb7f..3e6226c1e8 100644 --- a/Engine/NodeGroup.cpp +++ b/Engine/NodeGroup.cpp @@ -913,7 +913,7 @@ NodeCollection::recomputeFrameRangeForAllReaders(int* firstFrame,int* lastFrame) } void -NodeCollection::forceGetClipPreferencesOnAllTrees() +NodeCollection::forceComputeInputDependentDataOnAllTrees() { NodeList nodes; getNodes_recursive(nodes); @@ -922,7 +922,7 @@ NodeCollection::forceGetClipPreferencesOnAllTrees() std::list markedNodes; for (std::list::iterator it = trees.begin(); it != trees.end(); ++it) { - it->output.node->restoreClipPreferencesRecursive(markedNodes); + it->output.node->forceRefreshAllInputRelatedData(); } } 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/OfxEffectInstance.cpp b/Engine/OfxEffectInstance.cpp index d9847bf455..389d6bf387 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 diff --git a/Engine/OfxEffectInstance.h b/Engine/OfxEffectInstance.h index ecb21477bc..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 { @@ -246,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, @@ -271,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..749c3e987b 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; } 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..3fd6fb85d2 100644 --- a/Engine/OfxImageEffectInstance.cpp +++ b/Engine/OfxImageEffectInstance.cpp @@ -1061,15 +1061,32 @@ OfxImageEffectInstance::getClipPreferences_safe(std::mapgetLiveInstance(); 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/Project.cpp b/Engine/Project.cpp index b1700653d9..9f07eb2261 100644 --- a/Engine/Project.cpp +++ b/Engine/Project.cpp @@ -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); 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/ViewerInstance.cpp b/Engine/ViewerInstance.cpp index 7e0b5718cd..5c6ef46247 100644 --- a/Engine/ViewerInstance.cpp +++ b/Engine/ViewerInstance.cpp @@ -2748,17 +2748,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 +2768,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..867f8c3545 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; diff --git a/Gui/Gui.cpp b/Gui/Gui.cpp index c4186922b9..ace60d9162 100644 --- a/Gui/Gui.cpp +++ b/Gui/Gui.cpp @@ -235,10 +235,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 +249,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..4957891ca6 100644 --- a/Gui/Gui.h +++ b/Gui/Gui.h @@ -140,10 +140,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); diff --git a/Gui/GuiAppInstance.cpp b/Gui/GuiAppInstance.cpp index 41f59917da..66961868ba 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" @@ -421,7 +422,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 +465,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 @@ -1061,11 +1088,18 @@ GuiAppInstance::onGroupCreationFinished(const boost::shared_ptr& n 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, true); + graph->moveNodesForIdealPosition(nodeGui, selectedNode, true); std::list viewers; node->hasViewersConnected(&viewers); diff --git a/Gui/NodeGraph.cpp b/Gui/NodeGraph.cpp index ad14971c49..979bf51161 100644 --- a/Gui/NodeGraph.cpp +++ b/Gui/NodeGraph.cpp @@ -329,10 +329,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 +379,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(); diff --git a/Gui/NodeGraph.h b/Gui/NodeGraph.h index 20e9e35834..615ab02889 100644 --- a/Gui/NodeGraph.h +++ b/Gui/NodeGraph.h @@ -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); @@ -167,7 +166,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); diff --git a/Gui/NodeGraph05.cpp b/Gui/NodeGraph05.cpp index 13338fc6ea..f083d37bf7 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; From fb5f361bbc2c6ee796b3da0223db294022bb471e Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Mon, 28 Sep 2015 16:40:04 +0200 Subject: [PATCH 017/178] Fix recursion --- Engine/Engine.pro | 2 +- Engine/Node.cpp | 46 +++++++++++++++++++++++++++++++--------------- Engine/Node.h | 2 ++ 3 files changed, 34 insertions(+), 16 deletions(-) diff --git a/Engine/Engine.pro b/Engine/Engine.pro index 197b5db5c9..04fdd8d013 100644 --- a/Engine/Engine.pro +++ b/Engine/Engine.pro @@ -72,7 +72,7 @@ win32-msvc* { # XCode clang 3.5 without optimization generates code that crashes #(Natron on OSX, XCode 6, Spaceship_Natron.ntp) *-xcode { - QMAKE_CXXFLAGS += -O1 + #QMAKE_CXXFLAGS += -O1 } SOURCES += \ diff --git a/Engine/Node.cpp b/Engine/Node.cpp index 4109cd6bae..0e2dc42a21 100644 --- a/Engine/Node.cpp +++ b/Engine/Node.cpp @@ -6648,6 +6648,11 @@ Node::refreshAllInputRelatedData(bool canChangeValues,const std::vectorpluginsPropMutex); + _imp->mustComputeInputRelatedData = false; + } + return hasChanged; } @@ -6683,11 +6688,6 @@ Node::refreshInputRelatedDataInternal(std::list& markedNodes) bool hasChanged = refreshAllInputRelatedData(false, inputsCopy); - { - QMutexLocker k(&_imp->pluginsPropMutex); - _imp->mustComputeInputRelatedData = false; - } - return hasChanged; } @@ -6702,7 +6702,19 @@ void Node::forceRefreshAllInputRelatedData() { markInputRelatedDataDirtyRecursive(); - refreshInputRelatedDataRecursive(); + + 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 @@ -6734,20 +6746,24 @@ Node::markInputRelatedDataDirtyRecursive() markInputRelatedDataDirtyRecursiveInternal(marked); } +void +Node::refreshInputRelatedDataRecursiveInternal(std::list& markedNodes) +{ + refreshInputRelatedDataInternal(markedNodes); + + ///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; - if (refreshInputRelatedDataInternal(markedNodes)) { - - ///Now notify outputs we have changed - std::list outputs; - getOutputsWithGroupRedirection(outputs); - for (std::list::const_iterator it = outputs.begin(); it != outputs.end(); ++it) { - (*it)->refreshInputRelatedDataInternal( markedNodes ); - } - } + refreshInputRelatedDataRecursiveInternal(markedNodes); } bool diff --git a/Engine/Node.h b/Engine/Node.h index 16c627013e..f4a85c5a5b 100644 --- a/Engine/Node.h +++ b/Engine/Node.h @@ -1037,6 +1037,8 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON private: + void refreshInputRelatedDataRecursiveInternal(std::list& markedNodes); + void refreshInputRelatedDataRecursive(); void refreshAllInputRelatedData(bool canChangeValues); From 59a174a01e07cdd9805dd0bc13d76ce5e192639e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Devernay?= Date: Mon, 28 Sep 2015 17:04:59 +0200 Subject: [PATCH 018/178] travis: fix addons section --- .travis.yml | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) 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 From 0762d467fa529cf1ad13cd9333517a2aaf7dd5ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Devernay?= Date: Mon, 28 Sep 2015 17:06:09 +0200 Subject: [PATCH 019/178] update openfx --- libs/OpenFX | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/OpenFX b/libs/OpenFX index 2b409121b4..5b79aab822 160000 --- a/libs/OpenFX +++ b/libs/OpenFX @@ -1 +1 @@ -Subproject commit 2b409121b4ef8e89c92e19dd8a77400c6d4cfd8f +Subproject commit 5b79aab822b378814e1967236e3576b677ce3b96 From ddcc89eb99d4f4a30b7245f734ab68189ba9790a Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Mon, 28 Sep 2015 17:08:20 +0200 Subject: [PATCH 020/178] Don't call refreshAllInputRelatedData if loading a project or group --- Engine/Node.cpp | 4 +++- Gui/GuiAppInstance.cpp | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Engine/Node.cpp b/Engine/Node.cpp index 0e2dc42a21..095d3d1e65 100644 --- a/Engine/Node.cpp +++ b/Engine/Node.cpp @@ -724,7 +724,9 @@ Node::load(const std::string & parentMultiInstanceName, _imp->nodeCreated = true; - refreshAllInputRelatedData(serialization.isNull()); + if (!getApp()->getProject()->isLoadingProject() && !getApp()->isCreatingPythonGroup()) { + refreshAllInputRelatedData(serialization.isNull()); + } _imp->runOnNodeCreatedCB(serialization.isNull()); diff --git a/Gui/GuiAppInstance.cpp b/Gui/GuiAppInstance.cpp index 66961868ba..1cdb6e5dde 100644 --- a/Gui/GuiAppInstance.cpp +++ b/Gui/GuiAppInstance.cpp @@ -45,7 +45,7 @@ #include "Gui/GuiApplicationManager.h" #include "Gui/Gui.h" -#include "Gui/BackdropGui.h" +#include "Gui/BackDropGui.h" #include "Gui/NodeGraph.h" #include "Gui/NodeGui.h" #include "Gui/MultiInstancePanel.h" From 677f631a630366cc6285a55ffa04ca498594111e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Devernay?= Date: Mon, 28 Sep 2015 17:36:57 +0200 Subject: [PATCH 021/178] fix the build (beware of capitalization) --- Gui/BackDropGui.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 From 5d2502220b0f388a56971097c15d2545bfd4f46c Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Mon, 28 Sep 2015 18:25:10 +0200 Subject: [PATCH 022/178] Don' tassert --- Engine/EffectInstance.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Engine/EffectInstance.cpp b/Engine/EffectInstance.cpp index 21054840e6..5eff569e19 100644 --- a/Engine/EffectInstance.cpp +++ b/Engine/EffectInstance.cpp @@ -815,7 +815,9 @@ EffectInstance::getImage(int inputNb, */ ///Check that the rendered image contains what we requested. - assert( (!isMask && inputImg->getComponents() == comp) || (isMask && inputImg->getComponents() == maskComps) ); + if ((!isMask && inputImg->getComponents() != comp) || (isMask && inputImg->getComponents() != maskComps)) { + return ImagePtr(); + } if (roiPixel) { *roiPixel = pixelRoI; From 0e2e56da11f85b92c4617d188f55805cf0ac65bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole-Andr=C3=A9=20Rodlie?= Date: Tue, 29 Sep 2015 04:24:22 +0200 Subject: [PATCH 023/178] Linux: removed (old) licenses, bundle ffmpeg, minor scripts cleanup --- tools/linux/common.sh | 11 +- tools/linux/include/scripts/Natron.sh | 18 +- .../linux/include/scripts/build-installer.sh | 192 ++---------------- tools/linux/include/scripts/build-plugins.sh | 5 - tools/linux/include/scripts/build-sdk.sh | 2 - tools/linux/include/scripts/ffmpeg.sh | 14 ++ tools/linux/include/xml/corelibs.xml | 6 +- tools/linux/include/xml/openfx-arena.xml | 4 - tools/linux/include/xml/openfx-io.xml | 3 - tools/linux/include/xml/openfx-misc.xml | 3 - 10 files changed, 50 insertions(+), 208 deletions(-) create mode 100644 tools/linux/include/scripts/ffmpeg.sh diff --git a/tools/linux/common.sh b/tools/linux/common.sh index 82c19af930..dc0dc516b2 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=20150928 # SDK # @@ -229,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/scripts/Natron.sh b/tools/linux/include/scripts/Natron.sh index efc2d8b165..9cffaf739c 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,25 +14,31 @@ 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 + export FONTCONFIG_PATH="$DIR/Resources/etc/fonts" 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 95ee3cd73d..3f53989e01 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,17 +99,7 @@ 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 @@ -123,148 +120,12 @@ rm -f $IO_LIBS/{libgcc*,libstdc*,libbz2*,libfont*,libfree*,libpng*,libjpeg*,libt ) strip -s $IO_LIBS/* -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 - # OFX MISC OFX_MISC_VERSION=$TAG 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 @@ -302,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 @@ -368,23 +225,6 @@ cp -a $INSTALL_PATH/etc/fonts $CLIBS_PATH/data/share/etc/ || exit 1 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 @@ -398,7 +238,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 @@ -412,15 +251,8 @@ 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 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 5f50d9969e..437651cd60 100644 --- a/tools/linux/include/scripts/build-sdk.sh +++ b/tools/linux/include/scripts/build-sdk.sh @@ -250,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" 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/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 From 7727598828d8ca6e34f0fa42f549c1eed06207f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole-Andr=C3=A9=20Rodlie?= Date: Tue, 29 Sep 2015 04:36:10 +0200 Subject: [PATCH 024/178] Linux: fix cache path in fonts.conf --- tools/linux/include/scripts/build-installer.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/linux/include/scripts/build-installer.sh b/tools/linux/include/scripts/build-installer.sh index 3f53989e01..85a2e92d4e 100644 --- a/tools/linux/include/scripts/build-installer.sh +++ b/tools/linux/include/scripts/build-installer.sh @@ -218,6 +218,7 @@ mv $IO_LIBS/{libOpenColor*,libgomp*} $CLIBS_PATH/data/lib/ || exit 1 mkdir -p $CLIBS_PATH/data/share/etc || exit 1 cp -a $INSTALL_PATH/etc/fonts $CLIBS_PATH/data/share/etc/ || exit 1 +sed -i "s#/opt/Natron-${SDK_VERSION}/#/#" $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 From 914620ad9532bbb58b5fce393441077706439871 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole-Andr=C3=A9=20Rodlie?= Date: Tue, 29 Sep 2015 05:01:36 +0200 Subject: [PATCH 025/178] Linux: update centos build instructions, fixed forced path in build-installer --- INSTALL_LINUX.md | 18 +++++------------- tools/linux/include/scripts/build-installer.sh | 2 +- 2 files changed, 6 insertions(+), 14 deletions(-) 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/tools/linux/include/scripts/build-installer.sh b/tools/linux/include/scripts/build-installer.sh index 85a2e92d4e..c4c127939e 100644 --- a/tools/linux/include/scripts/build-installer.sh +++ b/tools/linux/include/scripts/build-installer.sh @@ -218,7 +218,7 @@ mv $IO_LIBS/{libOpenColor*,libgomp*} $CLIBS_PATH/data/lib/ || exit 1 mkdir -p $CLIBS_PATH/data/share/etc || exit 1 cp -a $INSTALL_PATH/etc/fonts $CLIBS_PATH/data/share/etc/ || exit 1 -sed -i "s#/opt/Natron-${SDK_VERSION}/#/#" $CLIBS_PATH/data/share/etc/fonts/fonts.conf || exit 1 +sed -i "s#${SDK_PATH}/Natron-${SDK_VERSION}/#/#" $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 From 856db83cc62ab40636cbbc6d5999e8f1cb5cedff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Devernay?= Date: Tue, 29 Sep 2015 08:16:04 +0200 Subject: [PATCH 026/178] Viewer: correct a few tooltips (don't use the future when things happen in the present) --- Gui/ViewerTab.cpp | 45 +++++++++++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/Gui/ViewerTab.cpp b/Gui/ViewerTab.cpp index 7eb1d44b71..b8e7a25cc0 100644 --- a/Gui/ViewerTab.cpp +++ b/Gui/ViewerTab.cpp @@ -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(); @@ -422,11 +426,11 @@ 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); @@ -463,7 +467,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())); @@ -676,16 +680,17 @@ 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(); _imp->frameRangeEdit->setMaximumWidth(70); @@ -703,7 +708,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 +723,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)); @@ -743,7 +748,7 @@ ViewerTab::ViewerTab(const std::list & existingRotoNodes, _imp->fpsBox->setValue(24.0); _imp->fpsBox->setIncrement(0.1); _imp->fpsBox->setToolTip( "

" + tr("fps:") + "

" + tr( - "Enter here the desired playback rate.") ); + "Viewer playback framerate.") ); _imp->playerLayout->addWidget(_imp->fpsBox); @@ -760,8 +765,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 +867,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); From 87a16d3d7a9dc0d01eae3fbf604994c4b9444c92 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Tue, 29 Sep 2015 09:32:07 +0200 Subject: [PATCH 027/178] Don't fail, print a warning instead --- Engine/EffectInstance.cpp | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/Engine/EffectInstance.cpp b/Engine/EffectInstance.cpp index 5eff569e19..5f7ef39376 100644 --- a/Engine/EffectInstance.cpp +++ b/Engine/EffectInstance.cpp @@ -599,11 +599,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; @@ -743,9 +743,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, comp, depth); } else { - inputImg = roto->renderMaskFromStroke(attachedStroke, pixelRoI, prefComps, + inputImg = roto->renderMaskFromStroke(attachedStroke, pixelRoI, comp, time, view, depth, mipMapLevel); if ( roto->isDoingNeatRender() ) { getNode()->updateStrokeImage(inputImg); @@ -814,11 +814,26 @@ EffectInstance::getImage(int inputNb, * instantaneous thanks to the image cache. */ +#ifdef DEBUG ///Check that the rendered image contains what we requested. if ((!isMask && inputImg->getComponents() != comp) || (isMask && inputImg->getComponents() != maskComps)) { - return ImagePtr(); + ImageComponents cc; + if (isMask) { + cc = maskComps; + } else { + cc = comp; + } + 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 preferences is" << prefComps.front().getComponentsGlobalName().c_str(); } +#endif + if (roiPixel) { *roiPixel = pixelRoI; } @@ -845,16 +860,16 @@ EffectInstance::getImage(int inputNb, } - if ( prefComps.getNumComponents() != inputImg->getComponents().getNumComponents() ) { + if ( comp.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) ); + remappedImg.reset( new Image(comp, 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->getComponents().getNumComponents() == 4 && comp.getNumComponents() == 3; inputImg->convertToFormat( inputImg->getBounds(), colorspace, colorspace, channelForMask, false, unPremultIfNeeded, remappedImg.get() ); From a0e3aa171f3022ac5cbd882f64eedb4070cfe393 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Tue, 29 Sep 2015 10:04:53 +0200 Subject: [PATCH 028/178] Abort: bug fix --- Engine/EffectInstance.cpp | 2 +- Engine/Node.cpp | 36 +++++++++++++++++++++++++++++------- Engine/Node.h | 2 +- Engine/RotoContext.cpp | 15 +++++++++++++++ Engine/RotoContext.h | 2 ++ Engine/RotoPaint.cpp | 21 ++------------------- 6 files changed, 50 insertions(+), 28 deletions(-) diff --git a/Engine/EffectInstance.cpp b/Engine/EffectInstance.cpp index 5f7ef39376..2bcf840b5d 100644 --- a/Engine/EffectInstance.cpp +++ b/Engine/EffectInstance.cpp @@ -829,7 +829,7 @@ EffectInstance::getImage(int inputNb, ImageBitDepthEnum depth; n->getPreferredDepthAndComponents(-1, &prefComps, &depth); assert(!prefComps.empty()); - qDebug() << n->getScriptName_mt_safe().c_str() << "output clip preferences is" << prefComps.front().getComponentsGlobalName().c_str(); + qDebug() << n->getScriptName_mt_safe().c_str() << "output clip preference is" << prefComps.front().getComponentsGlobalName().c_str(); } #endif diff --git a/Engine/Node.cpp b/Engine/Node.cpp index 095d3d1e65..bec476cd6a 100644 --- a/Engine/Node.cpp +++ b/Engine/Node.cpp @@ -3085,7 +3085,7 @@ Node::hasOutputNodesConnected(std::list* writers { Natron::OutputEffectInstance* thisWriter = dynamic_cast(_imp->liveInstance.get()); - if (thisWriter) { + if (thisWriter && thisWriter->isOutput()) { std::list::const_iterator alreadyExists = std::find(writers->begin(), writers->end(), thisWriter); if ( alreadyExists == writers->end() ) { writers->push_back(thisWriter); @@ -6720,7 +6720,7 @@ Node::forceRefreshAllInputRelatedData() } void -Node::markInputRelatedDataDirtyRecursiveInternal(std::list& markedNodes) { +Node::markInputRelatedDataDirtyRecursiveInternal(std::list& markedNodes,bool recurse) { std::list::iterator found = std::find(markedNodes.begin(), markedNodes.end(), this); if (found != markedNodes.end()) { return; @@ -6731,10 +6731,22 @@ Node::markInputRelatedDataDirtyRecursiveInternal(std::list& marke } markedNodes.push_back(this); - std::list outputs; - getOutputsWithGroupRedirection(outputs); - for (std::list::const_iterator it = outputs.begin(); it != outputs.end(); ++it) { - (*it)->markInputRelatedDataDirtyRecursiveInternal( markedNodes ); + 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)->markInputRelatedDataDirtyRecursiveInternal(markedNodes,false); + } + } + + if (recurse) { + std::list outputs; + getOutputsWithGroupRedirection(outputs); + for (std::list::const_iterator it = outputs.begin(); it != outputs.end(); ++it) { + (*it)->markInputRelatedDataDirtyRecursiveInternal( markedNodes, true ); + } } @@ -6745,7 +6757,7 @@ void Node::markInputRelatedDataDirtyRecursive() { std::list marked; - markInputRelatedDataDirtyRecursiveInternal(marked); + markInputRelatedDataDirtyRecursiveInternal(marked, true); } void @@ -6753,12 +6765,22 @@ Node::refreshInputRelatedDataRecursiveInternal(std::list& markedN { refreshInputRelatedDataInternal(markedNodes); + if (isRotoPaintingNode()) { + boost::shared_ptr roto = getRotoContext(); + assert(roto); + boost::shared_ptr bottomMerge = roto->getRotoPaintBottomMergeNode(); + if (bottomMerge) { + bottomMerge->refreshInputRelatedDataRecursiveInternal(markedNodes); + } + } + ///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 diff --git a/Engine/Node.h b/Engine/Node.h index f4a85c5a5b..cee881172d 100644 --- a/Engine/Node.h +++ b/Engine/Node.h @@ -1049,7 +1049,7 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON void markInputRelatedDataDirtyRecursive(); - void markInputRelatedDataDirtyRecursiveInternal(std::list& markedNodes); + void markInputRelatedDataDirtyRecursiveInternal(std::list& markedNodes,bool recurse); bool refreshAllInputRelatedData(bool hasSerializationData,const std::vector >& inputs); diff --git a/Engine/RotoContext.cpp b/Engine/RotoContext.cpp index 55fff61594..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 { 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/RotoPaint.cpp b/Engine/RotoPaint.cpp index 644961a090..f04814c66f 100644 --- a/Engine/RotoPaint.cpp +++ b/Engine/RotoPaint.cpp @@ -223,15 +223,7 @@ 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); + boost::shared_ptr bottomMerge = roto->getRotoPaintBottomMergeNode(); FramesNeededMap ret; std::map > views; @@ -251,16 +243,7 @@ RotoPaint::getRegionsOfInterest(double /*time*/, RoIMap* ret) { boost::shared_ptr roto = getNode()->getRotoContext(); - std::list > items = roto->getCurvesByRenderOrder(); - if (items.empty()) { - return; - } - - const boost::shared_ptr& firstStrokeItem = items.back(); - assert(firstStrokeItem); - boost::shared_ptr bottomMerge = firstStrokeItem->getMergeNode(); - assert(bottomMerge); - + boost::shared_ptr bottomMerge = roto->getRotoPaintBottomMergeNode(); ret->insert(std::make_pair(bottomMerge->getLiveInstance(), renderWindow)); } From 982a02f58c25881ca489a38c2e1f706510506f49 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Tue, 29 Sep 2015 10:07:58 +0200 Subject: [PATCH 029/178] Fix #925 --- Engine/Node.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Engine/Node.cpp b/Engine/Node.cpp index bec476cd6a..ac33173c47 100644 --- a/Engine/Node.cpp +++ b/Engine/Node.cpp @@ -2392,7 +2392,7 @@ Node::makeCacheInfo() const std::string Node::makeInfoForInput(int inputNumber) const { - if (inputNumber < 0 || inputNumber >= getMaxInputCount()) { + if (inputNumber < -1 || inputNumber >= getMaxInputCount()) { return ""; } const Natron::Node* inputNode = 0; From 9aa1c2b443acc4e3f403b53bcfb2edaedc2ee1b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Devernay?= Date: Tue, 29 Sep 2015 10:54:56 +0200 Subject: [PATCH 030/178] Viewer: fix labels/tooltips --- Gui/ViewerTab.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Gui/ViewerTab.cpp b/Gui/ViewerTab.cpp index b8e7a25cc0..e68bb20ac9 100644 --- a/Gui/ViewerTab.cpp +++ b/Gui/ViewerTab.cpp @@ -680,7 +680,7 @@ ViewerTab::ViewerTab(const std::list & existingRotoNodes, //QFont font(appFont,appFontSize); - _imp->canEditFrameRangeLabel = new ClickableLabel(tr("Playback range"),_imp->playerButtonsContainer); + _imp->canEditFrameRangeLabel = new ClickableLabel(tr("Playback range:"),_imp->playerButtonsContainer); //_imp->canEditFrameRangeLabel->setFont(font); _imp->playerLayout->addWidget(_imp->canEditFrameRangeLabel); @@ -734,7 +734,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); @@ -748,7 +748,7 @@ ViewerTab::ViewerTab(const std::list & existingRotoNodes, _imp->fpsBox->setValue(24.0); _imp->fpsBox->setIncrement(0.1); _imp->fpsBox->setToolTip( "

" + tr("fps:") + "

" + tr( - "Viewer playback framerate.") ); + "Viewer playback framerate, in frames per second.") ); _imp->playerLayout->addWidget(_imp->fpsBox); From 161a5fa4ae4423d47612478d738c656806bfe2f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Devernay?= Date: Tue, 29 Sep 2015 11:52:55 +0200 Subject: [PATCH 031/178] TimeLineGui: mark TODOs for #917 --- Global/Enums.h | 2 ++ Gui/TimeLineGui.cpp | 51 ++++++++++++++++++++++++++++++++------------- 2 files changed, 38 insertions(+), 15 deletions(-) diff --git a/Global/Enums.h b/Global/Enums.h index 6cd384c32a..bb8cee4092 100644 --- a/Global/Enums.h +++ b/Global/Enums.h @@ -37,6 +37,8 @@ enum TimelineStateEnum eTimelineStateIdle, eTimelineStateDraggingCursor, eTimelineStateDraggingBoundary, + eTimelineStatePanning, + eTimelineStateSelectingRange, }; enum TimelineChangeReasonEnum diff --git a/Gui/TimeLineGui.cpp b/Gui/TimeLineGui.cpp index 5b1c42a615..0a2723f091 100644 --- a/Gui/TimeLineGui.cpp +++ b/Gui/TimeLineGui.cpp @@ -670,24 +670,21 @@ TimeLineGui::seek(SequenceTime time) void TimeLineGui::mousePressEvent(QMouseEvent* e) { - int leftBound,rightBound; - { - QMutexLocker k(&_imp->boundariesMutex); - leftBound = _imp->leftBoundary; - rightBound = _imp->rightBoundary; - } if (buttonDownIsMiddle(e)) { - centerOn(leftBound, rightBound); - - if (_imp->gui->isTripleSyncEnabled()) { - _imp->updateEditorFrameRanges(); - _imp->updateOpenedViewersFrameRanges(); - } + _imp->state = eTimelineStatePanning; + } else if (buttonDownIsRight(e)) { + _imp->state = eTimelineStateSelectingRange; } else { _imp->lastMouseEventWidgetCoord = e->pos(); double t = toTimeLineCoordinates(e->x(),0).x(); 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(); @@ -720,7 +717,12 @@ TimeLineGui::mouseMoveEvent(QMouseEvent* e) SequenceTime tseq = std::floor(t + 0.5); bool distortViewPort = false; bool onEditingFinishedOnly = appPTR->getCurrentSettings()->getRenderOnEditingFinishedOnly(); - if (_imp->state == eTimelineStateDraggingCursor && !onEditingFinishedOnly) { + if (_imp->state == eTimelineStatePanning) { +#pragma message WARN("TODO: timeline panning") + } else if (_imp->state == eTimelineStateSelectingRange) { +#pragma message WARN("TODO: timeline select range") + // https://github.com/MrKepzie/Natron/issues/917 + } else if (_imp->state == eTimelineStateDraggingCursor && !onEditingFinishedOnly) { if ( tseq != _imp->timeline->currentFrame() ) { _imp->gui->setDraftRenderEnabled(true); _imp->gui->getApp()->setLastViewerUsingTimeline(_imp->viewer->getNode()); @@ -783,8 +785,27 @@ TimeLineGui::leaveEvent(QEvent* e) void TimeLineGui::mouseReleaseEvent(QMouseEvent* e) { - if (_imp->state == eTimelineStateDraggingCursor) { - + if (_imp->state == eTimelineStateSelectingRange) { +#pragma message WARN("TODO: timeline select range") + + // TODO: https://github.com/MrKepzie/Natron/issues/917 + // - 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) + // - if they are different, zoom on that range + int leftBound,rightBound; + { + QMutexLocker k(&_imp->boundariesMutex); + leftBound = _imp->leftBoundary; + rightBound = _imp->rightBoundary; + } + + centerOn(leftBound, rightBound); + + if (_imp->gui->isTripleSyncEnabled()) { + _imp->updateEditorFrameRanges(); + _imp->updateOpenedViewersFrameRanges(); + } + } else if (_imp->state == eTimelineStateDraggingCursor) { + bool wasScrubbing = false; if (_imp->gui->isDraftRenderEnabled()) { _imp->gui->setDraftRenderEnabled(false); From ea67ff8f0fb77d67f9ec3a92facf37660a5eb34f Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Tue, 29 Sep 2015 12:02:37 +0200 Subject: [PATCH 032/178] Factorize image format conversions in renderRoI in convertPlanesFormatsIfNeeded. Also fix several UI bugs --- Engine/EffectInstance.cpp | 38 ++++++----- Engine/EffectInstance.h | 8 +++ Engine/EffectInstanceRenderRoI.cpp | 100 ++++++++++++++++++++--------- Engine/Node.cpp | 5 +- Engine/ViewerInstance.cpp | 2 +- Gui/NodeGraph30.cpp | 24 ++++++- Gui/ViewerGL.cpp | 8 ++- 7 files changed, 130 insertions(+), 55 deletions(-) diff --git a/Engine/EffectInstance.cpp b/Engine/EffectInstance.cpp index 2bcf840b5d..23fe844e8e 100644 --- a/Engine/EffectInstance.cpp +++ b/Engine/EffectInstance.cpp @@ -858,27 +858,21 @@ EffectInstance::getImage(int inputNb, inputImg = rescaledImg; } - - - if ( comp.getNumComponents() != inputImg->getComponents().getNumComponents() ) { - ImagePtr remappedImg; - { - Image::ReadAccess acc = inputImg->getReadRights(); - - remappedImg.reset( new Image(comp, 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 && comp.getNumComponents() == 3; - inputImg->convertToFormat( inputImg->getBounds(), - colorspace, colorspace, - channelForMask, false, unPremultIfNeeded, remappedImg.get() ); - } - inputImg = remappedImg; + + + //Remap if needed + ImagePremultiplicationEnum outputPremult; + if (comp.isColorPlane()) { + outputPremult = n->getOutputPremultiplication(); + } else { + outputPremult = eImagePremultiplicationOpaque; } + + inputImg = convertPlanesFormatsIfNeeded(getApp(), inputImg, pixelRoI, comp, depth, getNode()->usesAlpha0ToConvertFromRGBToRGBA(), outputPremult); - - if ( inputImagesThreadLocal.empty() ) { + + + 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); } @@ -3168,7 +3162,11 @@ EffectInstance::getFramesNeeded_public(U64 hash, return framesNeeded; } - framesNeeded = getFramesNeeded(time, view); + try { + framesNeeded = getFramesNeeded(time, view); + } catch (std::exception &e) { + setPersistentMessage(Natron::eMessageTypeError, e.what()); + } _imp->actionsCache.setFramesNeededResult(hash, time, view, mipMapLevel, framesNeeded); return framesNeeded; diff --git a/Engine/EffectInstance.h b/Engine/EffectInstance.h index 32c6ff4135..aa71e57efc 100644 --- a/Engine/EffectInstance.h +++ b/Engine/EffectInstance.h @@ -1661,6 +1661,14 @@ 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); + /** * @brief Called by getImage when the thread-storage was not set by the caller thread (mostly because this is a thread that is not diff --git a/Engine/EffectInstanceRenderRoI.cpp b/Engine/EffectInstanceRenderRoI.cpp index f194fab81f..ce173e440b 100644 --- a/Engine/EffectInstanceRenderRoI.cpp +++ b/Engine/EffectInstanceRenderRoI.cpp @@ -185,6 +185,46 @@ 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) +{ + bool imageConversionNeeded = targetComponents != inputImage->getComponents() || 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)); + + bool unPremultIfNeeded = outputPremult == eImagePremultiplicationPremultiplied && inputImage->getComponentsCount() == 4 && tmp->getComponentsCount() == 3; + + if (useAlpha0ForRGBToRGBAConversion) { + inputImage->convertToFormatAlpha0( roi, + app->getDefaultColorSpaceForBitDepth(inputImage->getBitDepth()), + app->getDefaultColorSpaceForBitDepth(targetDepth), + -1, false, unPremultIfNeeded, tmp.get() ); + } else { + inputImage->convertToFormat( roi, + app->getDefaultColorSpaceForBitDepth(inputImage->getBitDepth()), + app->getDefaultColorSpaceForBitDepth(targetDepth), + -1, false, unPremultIfNeeded, tmp.get() ); + } + + return tmp; + } + +} + EffectInstance::RenderRoIRetCode EffectInstance::renderRoI(const RenderRoIArgs & args, ImageList* outputPlanes) @@ -513,7 +553,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()); + 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); + assert(tmp); + convertedPlanes.push_back(tmp); + } + *outputPlanes = convertedPlanes; + } else { + return ret; + } } else { assert( outputPlanes->empty() ); } @@ -1423,36 +1489,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); + assert(it->second.downscaleImage->getComponents() == it->first && it->second.downscaleImage->getBitDepth() == args.bitdepth); outputPlanes->push_back(it->second.downscaleImage); } diff --git a/Engine/Node.cpp b/Engine/Node.cpp index ac33173c47..664a6304aa 100644 --- a/Engine/Node.cpp +++ b/Engine/Node.cpp @@ -5359,10 +5359,13 @@ Node::onInputChanged(int inputNb) } - if (!getApp()->getProject()->isLoadingProject() && !getApp()->isCreatingPythonGroup()) { + + if ((!getApp()->getProject()->isLoadingProject() && !getApp()->isCreatingPythonGroup()) || + _imp->liveInstance->isRotoPaintNode()) { ///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. diff --git a/Engine/ViewerInstance.cpp b/Engine/ViewerInstance.cpp index 5c6ef46247..abb6a2ba81 100644 --- a/Engine/ViewerInstance.cpp +++ b/Engine/ViewerInstance.cpp @@ -604,7 +604,7 @@ ViewerInstance::renderViewer(int view, } - if ( (ret[0] == eStatusFailed) && (ret[1] == eStatusFailed) ) { + if ( (ret[0] == eStatusFailed) || (ret[1] == eStatusFailed) ) { return eStatusFailed; } diff --git a/Gui/NodeGraph30.cpp b/Gui/NodeGraph30.cpp index bf033943f7..4431101220 100644 --- a/Gui/NodeGraph30.cpp +++ b/Gui/NodeGraph30.cpp @@ -61,7 +61,27 @@ using namespace Natron; void NodeGraph::connectCurrentViewerToSelection(int inputNB) { - if ( !getLastSelectedViewer() ) { + 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; + } + } + } + } + + + if ( !lastUsedViewer ) { _imp->_gui->getApp()->createNode( CreateNodeArgs(PLUGINID_NATRON_VIEWER, "", -1,-1, @@ -76,7 +96,7 @@ NodeGraph::connectCurrentViewerToSelection(int inputNB) } ///get a pointer to the last user selected viewer - boost::shared_ptr v = boost::dynamic_pointer_cast( getLastSelectedViewer()-> + boost::shared_ptr v = boost::dynamic_pointer_cast( lastUsedViewer-> getInternalNode()->getNode() ); ///if the node is no longer active (i.e: it was deleted by the user), don't do anything. diff --git a/Gui/ViewerGL.cpp b/Gui/ViewerGL.cpp index ae254f2d4f..f26d64bfea 100644 --- a/Gui/ViewerGL.cpp +++ b/Gui/ViewerGL.cpp @@ -40,6 +40,7 @@ GCC_DIAG_UNUSED_PRIVATE_FIELD_OFF #include GCC_DIAG_UNUSED_PRIVATE_FIELD_ON #include +#include #include "Engine/Lut.h" #include "Engine/Node.h" @@ -2861,6 +2862,10 @@ ViewerGL::enterEvent(QEvent* e) // always running in the main thread assert( qApp && qApp->thread() == QThread::currentThread() ); + /* + We steal focus from all those widgets so that the user automatically + gets keyboard focus in viewer when mouse enters. + */ QWidget* currentFocus = qApp->focusWidget(); bool canSetFocus = !currentFocus || @@ -2872,7 +2877,8 @@ ViewerGL::enterEvent(QEvent* e) currentFocus->objectName() == "Properties" || currentFocus->objectName() == "SettingsPanel" || currentFocus->objectName() == "qt_tabwidget_tabbar" || - currentFocus->objectName() == "PanelTabBar"; + currentFocus->objectName() == "PanelTabBar" || + dynamic_cast(currentFocus); if (canSetFocus) { setFocus(); From 7442410c7a2e006414d1ff72a3e3813dc1e12791 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Tue, 29 Sep 2015 12:22:51 +0200 Subject: [PATCH 033/178] Missing refresh --- Gui/GuiAppInstance.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Gui/GuiAppInstance.cpp b/Gui/GuiAppInstance.cpp index 1cdb6e5dde..34595d9a9f 100644 --- a/Gui/GuiAppInstance.cpp +++ b/Gui/GuiAppInstance.cpp @@ -487,6 +487,13 @@ GuiAppInstance::createNodeGui(const boost::shared_ptr &node, } } nodegui->getDagGui()->moveNodesForIdealPosition(nodegui,selectedNode,autoConnect); + if (autoConnect) { + std::list viewers; + node->hasViewersConnected(&viewers); + for (std::list::iterator it2 = viewers.begin(); it2 != viewers.end(); ++it2) { + (*it2)->renderCurrentFrame(true); + } + } } } } From fff0b5fdc471ba35f63935315b4c27b686289e66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Devernay?= Date: Tue, 29 Sep 2015 12:50:30 +0200 Subject: [PATCH 034/178] TimeLineGui: implement panning, see #917 --- Global/Enums.h | 2 +- Gui/TimeLineGui.cpp | 137 +++++++++++++++++++++++++++++--------------- Gui/TimeLineGui.h | 4 +- 3 files changed, 94 insertions(+), 49 deletions(-) diff --git a/Global/Enums.h b/Global/Enums.h index bb8cee4092..311f344335 100644 --- a/Global/Enums.h +++ b/Global/Enums.h @@ -38,7 +38,7 @@ enum TimelineStateEnum eTimelineStateDraggingCursor, eTimelineStateDraggingBoundary, eTimelineStatePanning, - eTimelineStateSelectingRange, + eTimelineStateSelectingZoomRange, }; enum TimelineChangeReasonEnum diff --git a/Gui/TimeLineGui.cpp b/Gui/TimeLineGui.cpp index 0a2723f091..7c0b4215f2 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(); @@ -349,6 +353,12 @@ TimeLineGui::paintGL() glCheckErrorIgnoreOSXBug(); glDisable(GL_SCISSOR_TEST); + if (_imp->state == eTimelineStateSelectingZoomRange) { +#pragma message WARN("TODO: draw timeline select range") + // https://github.com/MrKepzie/Natron/issues/917 + // draw the select range, from _imp->mousePressX to _imp->mouseMoveX + } + glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); @@ -538,7 +548,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 +565,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 +580,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); } @@ -670,13 +680,15 @@ TimeLineGui::seek(SequenceTime time) void TimeLineGui::mousePressEvent(QMouseEvent* e) { + _imp->mousePressX = e->x(); + _imp->mouseMoveX = _imp->mousePressX; if (buttonDownIsMiddle(e)) { _imp->state = eTimelineStatePanning; } else if (buttonDownIsRight(e)) { - _imp->state = eTimelineStateSelectingRange; + _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; @@ -686,8 +698,8 @@ TimeLineGui::mousePressEvent(QMouseEvent* e) 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) { @@ -705,23 +717,23 @@ 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 == eTimelineStatePanning) { -#pragma message WARN("TODO: timeline panning") - } else if (_imp->state == eTimelineStateSelectingRange) { -#pragma message WARN("TODO: timeline select range") + _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); @@ -731,8 +743,14 @@ TimeLineGui::mouseMoveEvent(QMouseEvent* e) 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 @@ -752,8 +770,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) { @@ -764,6 +782,7 @@ TimeLineGui::mouseMoveEvent(QMouseEvent* e) } else { update(); } + } void @@ -785,20 +804,29 @@ TimeLineGui::leaveEvent(QEvent* e) void TimeLineGui::mouseReleaseEvent(QMouseEvent* e) { - if (_imp->state == eTimelineStateSelectingRange) { -#pragma message WARN("TODO: timeline select range") - - // TODO: https://github.com/MrKepzie/Natron/issues/917 - // - 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) + 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 - int leftBound,rightBound; - { - QMutexLocker k(&_imp->boundariesMutex); - leftBound = _imp->leftBoundary; - rightBound = _imp->rightBoundary; + 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); + centerOn(leftBound, rightBound, 0); if (_imp->gui->isTripleSyncEnabled()) { _imp->updateEditorFrameRanges(); @@ -819,7 +847,7 @@ 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()) ) { @@ -923,12 +951,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(); @@ -963,32 +992,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..75031dd0db 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); From 1bc573cf3967b92342e017cca092d090cf0a23d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Devernay?= Date: Tue, 29 Sep 2015 14:13:57 +0200 Subject: [PATCH 035/178] CurveWidgetPrivate: ignore OSX bug --- Gui/CurveWidgetPrivate.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 From 21f6183617b22732cb5c060f510311fcdb32c144 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Devernay?= Date: Tue, 29 Sep 2015 14:15:17 +0200 Subject: [PATCH 036/178] TimeLineGui: implement selection-based zooming (right-click) - right click on timeline: zoom back to project range - right-click+drag: zoom on selected range closes #917 see also #870 --- Gui/TimeLineGui.cpp | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/Gui/TimeLineGui.cpp b/Gui/TimeLineGui.cpp index 7c0b4215f2..69865f444c 100644 --- a/Gui/TimeLineGui.cpp +++ b/Gui/TimeLineGui.cpp @@ -341,8 +341,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); @@ -353,14 +353,22 @@ TimeLineGui::paintGL() glCheckErrorIgnoreOSXBug(); glDisable(GL_SCISSOR_TEST); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + if (_imp->state == eTimelineStateSelectingZoomRange) { -#pragma message WARN("TODO: draw timeline select range") + // 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(); } - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); QFontMetrics fontM(_imp->font); From d2190e1d98ae37a42b3cba7495e874d3dd318034 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Tue, 29 Sep 2015 15:02:48 +0200 Subject: [PATCH 037/178] Edition of connections in the graph can now be enclosed in begin/end InputEdition. Also add InsertNodeCommand --- Engine/Node.cpp | 89 ++++++++++----- Engine/Node.h | 4 + Engine/OfxEffectInstance.cpp | 10 +- Gui/GuiAppInstance.cpp | 7 -- Gui/NodeGraph15.cpp | 29 +---- Gui/NodeGraphUndoRedo.cpp | 209 ++++++++++++++++++++++++++++------- Gui/NodeGraphUndoRedo.h | 29 ++++- 7 files changed, 269 insertions(+), 108 deletions(-) diff --git a/Engine/Node.cpp b/Engine/Node.cpp index 664a6304aa..7a28df6e51 100644 --- a/Engine/Node.cpp +++ b/Engine/Node.cpp @@ -239,7 +239,6 @@ struct Node::Implementation , childrenMutex() , children() , multiInstanceParentName() - , duringInputChangedAction(false) , keyframesDisplayedOnTimeline(false) , timersMutex() , lastRenderStartedSlotCallTime() @@ -282,6 +281,8 @@ struct Node::Implementation , distToNextOut(0.) , useAlpha0ToConvertFromRGBToRGBA(false) , isBeingDestroyed(false) + , inputModifiedRecursion(0) + , inputsModified() { ///Initialize timers gettimeofday(&lastRenderStartedSlotCallTime, 0); @@ -442,7 +443,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 @@ -505,6 +505,13 @@ struct Node::Implementation bool isBeingDestroyed; + /* + Used to block render emitions while modifying nodes links + MT-safe: only accessed/used on main thread + */ + int inputModifiedRecursion; + std::set inputsModified; + }; /** @@ -3775,8 +3782,10 @@ Node::switchInput0And1() Q_EMIT inputChanged(inputAIndex); Q_EMIT inputChanged(inputBIndex); if (!useGuiInputs) { + beginInputEdition(); onInputChanged(inputAIndex); onInputChanged(inputBIndex); + endInputEdition(true); } computeHash(); @@ -5340,6 +5349,39 @@ 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) { + + forceRefreshAllInputRelatedData(); + refreshDynamicProperties(); + + triggerRender = triggerRender && !_imp->inputsModified.empty(); + _imp->inputsModified.clear(); + + 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) { @@ -5347,21 +5389,24 @@ Node::onInputChanged(int inputNb) return; } assert( QThread::currentThread() == qApp->thread() ); - _imp->duringInputChangedAction = true; + + bool mustCallEndInputEdition = _imp->inputModifiedRecursion == 0; + if (mustCallEndInputEdition) { + beginInputEdition(); + } refreshMaskEnabledNess(inputNb); refreshLayersChoiceSecretness(inputNb); - refreshDynamicProperties(); ViewerInstance* isViewer = dynamic_cast(_imp->liveInstance.get()); if (isViewer) { isViewer->refreshActiveInputs(inputNb); } + bool shouldDoInputChanged = (!getApp()->getProject()->isLoadingProject() && !getApp()->isCreatingPythonGroup()) || + _imp->liveInstance->isRotoPaintNode(); - - if ((!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 @@ -5390,21 +5435,23 @@ Node::onInputChanged(int inputNb) ///Don't do clip preferences while loading a project, they will be refreshed globally once the project is loaded. - _imp->liveInstance->onInputChanged(inputNb); - - forceRefreshAllInputRelatedData(); + _imp->inputsModified.insert(inputNb); + } + + + if (mustCallEndInputEdition) { + endInputEdition(true); } - _imp->duringInputChangedAction = false; } void Node::onParentMultiInstanceInputChanged(int input) { - _imp->duringInputChangedAction = true; + ++_imp->inputModifiedRecursion; _imp->liveInstance->onInputChanged(input); - _imp->duringInputChangedAction = false; + --_imp->inputModifiedRecursion; } @@ -5413,7 +5460,7 @@ Node::duringInputChangedAction() const { assert( QThread::currentThread() == qApp->thread() ); - return _imp->duringInputChangedAction; + return _imp->inputModifiedRecursion > 0; } void @@ -6433,20 +6480,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; diff --git a/Engine/Node.h b/Engine/Node.h index cee881172d..88029fe8d6 100644 --- a/Engine/Node.h +++ b/Engine/Node.h @@ -790,6 +790,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); diff --git a/Engine/OfxEffectInstance.cpp b/Engine/OfxEffectInstance.cpp index 389d6bf387..31a470bb09 100644 --- a/Engine/OfxEffectInstance.cpp +++ b/Engine/OfxEffectInstance.cpp @@ -1669,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); @@ -1684,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; } diff --git a/Gui/GuiAppInstance.cpp b/Gui/GuiAppInstance.cpp index 34595d9a9f..1cdb6e5dde 100644 --- a/Gui/GuiAppInstance.cpp +++ b/Gui/GuiAppInstance.cpp @@ -487,13 +487,6 @@ GuiAppInstance::createNodeGui(const boost::shared_ptr &node, } } nodegui->getDagGui()->moveNodesForIdealPosition(nodegui,selectedNode,autoConnect); - if (autoConnect) { - std::list viewers; - node->hasViewersConnected(&viewers); - for (std::list::iterator it2 = viewers.begin(); it2 != viewers.end(); ++it2) { - (*it2)->renderCurrentFrame(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/NodeGraphUndoRedo.cpp b/Gui/NodeGraphUndoRedo.cpp index 882f03f3b6..4c399a283e 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(edge->getSource() && 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 From 57f367c285f79b424d2a2ebaadf0ff92a71b25c4 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Tue, 29 Sep 2015 15:14:11 +0200 Subject: [PATCH 038/178] Fix linux snapshots --- tools/linux/README.md | 6 ++++++ tools/linux/snapshot.sh | 4 +++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/tools/linux/README.md b/tools/linux/README.md index 4614c54652..f1e28e359e 100644 --- a/tools/linux/README.md +++ b/tools/linux/README.md @@ -78,7 +78,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/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 From f40274dbb93edf8d6fd732ca3b2223d48aadeadd Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Tue, 29 Sep 2015 15:23:46 +0200 Subject: [PATCH 039/178] Viewerinstance: trigger render only if params changed --- Engine/ViewerInstance.cpp | 96 +++++++++++++++++++++++++++------------ 1 file changed, 67 insertions(+), 29 deletions(-) diff --git a/Engine/ViewerInstance.cpp b/Engine/ViewerInstance.cpp index abb6a2ba81..bd6d8a4928 100644 --- a/Engine/ViewerInstance.cpp +++ b/Engine/ViewerInstance.cpp @@ -2491,17 +2491,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 +2523,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 +2566,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 +2596,9 @@ ViewerInstance::onColorSpaceChanged(Natron::ViewerColorSpaceEnum colorspace) { QMutexLocker l(&_imp->viewerParamsMutex); - + if (_imp->viewerParamsLut == colorspace) { + return; + } _imp->viewerParamsLut = colorspace; } assert(_imp->uiContext); @@ -2598,17 +2615,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 +2645,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 +2663,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); } } From e5bb8d4d257b3ff978ad8187ac0968f1967dc83d Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Tue, 29 Sep 2015 15:33:15 +0200 Subject: [PATCH 040/178] bug fix --- Engine/Node.cpp | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/Engine/Node.cpp b/Engine/Node.cpp index 7a28df6e51..00c8f07a47 100644 --- a/Engine/Node.cpp +++ b/Engine/Node.cpp @@ -4173,13 +4173,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)); + } } } } From 74c17015433cb903973326df2481c6f30b6073ab Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Tue, 29 Sep 2015 15:53:15 +0200 Subject: [PATCH 041/178] Python API: all getValueAtTime take double parameters --- .../NatronEngine/BooleanParam.rst | 4 +- .../NatronEngine/ChoiceParam.rst | 4 +- .../NatronEngine/ColorParam.rst | 4 +- .../NatronEngine/Double2DParam.rst | 1 + .../NatronEngine/Double3DParam.rst | 1 + .../NatronEngine/DoubleParam.rst | 4 +- .../NatronEngine/Int2DParam.rst | 2 +- .../NatronEngine/Int3DParam.rst | 2 +- .../PythonReference/NatronEngine/IntParam.rst | 4 +- .../NatronEngine/StringParamBase.rst | 4 +- Engine/NatronEngine/booleanparam_wrapper.cpp | 26 +- Engine/NatronEngine/choiceparam_wrapper.cpp | 26 +- Engine/NatronEngine/colorparam_wrapper.cpp | 28 +- Engine/NatronEngine/double2dparam_wrapper.cpp | 14 +- Engine/NatronEngine/double3dparam_wrapper.cpp | 14 +- Engine/NatronEngine/doubleparam_wrapper.cpp | 28 +- Engine/NatronEngine/int2dparam_wrapper.cpp | 14 +- Engine/NatronEngine/int3dparam_wrapper.cpp | 14 +- Engine/NatronEngine/intparam_wrapper.cpp | 28 +- .../natronengine_module_wrapper.cpp | 298 +++++++++--------- Engine/NatronEngine/natronengine_python.h | 92 +++--- .../NatronEngine/stringparambase_wrapper.cpp | 26 +- Engine/ParameterWrapper.cpp | 32 +- Engine/ParameterWrapper.h | 32 +- Gui/EditExpressionDialog.cpp | 4 +- Gui/NatronGui/natrongui_module_wrapper.cpp | 90 +++--- Gui/NatronGui/natrongui_python.h | 12 +- 27 files changed, 405 insertions(+), 403 deletions(-) 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/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/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/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/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/natronengine_module_wrapper.cpp b/Engine/NatronEngine/natronengine_module_wrapper.cpp index 874e4deb9c..a974769fa4 100644 --- a/Engine/NatronEngine/natronengine_module_wrapper.cpp +++ b/Engine/NatronEngine/natronengine_module_wrapper.cpp @@ -32,19 +32,16 @@ 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_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_IntParam(PyObject* module); +void init_Int2DParam(PyObject* module); +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_BooleanParam(PyObject* module); @@ -53,22 +50,25 @@ void init_StringParam(PyObject* module); void init_FileParam(PyObject* module); void init_OutputFileParam(PyObject* module); void init_PathParam(PyObject* module); -void init_IntParam(PyObject* module); -void init_Int2DParam(PyObject* module); -void init_Int3DParam(PyObject* module); -void init_DoubleParam(PyObject* module); -void init_Double2DParam(PyObject* module); -void init_Double3DParam(PyObject* module); -void init_ButtonParam(PyObject* module); -void init_GroupParam(PyObject* module); void init_PageParam(PyObject* module); +void init_ButtonParam(PyObject* module); void init_ParametricParam(PyObject* module); +void init_GroupParam(PyObject* module); void init_Int2DTuple(PyObject* module); void init_Int3DTuple(PyObject* module); void init_Double2DTuple(PyObject* module); void init_Double3DTuple(PyObject* module); void init_ColorTuple(PyObject* module); +void init_PyCoreApplication(PyObject* module); +void init_Group(PyObject* module); +void init_App(PyObject* module); +void init_Effect(PyObject* module); +void init_AppSettings(PyObject* module); +void init_ItemBase(PyObject* module); +void init_Layer(PyObject* module); +void init_BezierCurve(PyObject* module); void init_RectI(PyObject* module); +void init_RectD(PyObject* module); void init_Natron(PyObject* module); // Required modules' type and converter arrays. @@ -137,99 +137,6 @@ static PythonToCppFunc is_std_vector_RectI__PythonToCpp_std_vector_RectI__Conver return 0; } -// C++ to Python conversion for type 'std::vector'. -static PyObject* std_vector_std_string__CppToPython_std_vector_std_string_(const void* cppIn) { - ::std::vector& cppInRef = *((::std::vector*)cppIn); - - // TEMPLATE - stdVectorToPyList - START - ::std::vector::size_type vectorSize = cppInRef.size(); - PyObject* pyOut = PyList_New((int) vectorSize); - for (::std::vector::size_type idx = 0; idx < vectorSize; ++idx) { - ::std::string cppItem(cppInRef[idx]); - PyList_SET_ITEM(pyOut, idx, Shiboken::Conversions::copyToPython(Shiboken::Conversions::PrimitiveTypeConverter(), &cppItem)); - } - return pyOut; - // TEMPLATE - stdVectorToPyList - END - -} -static void std_vector_std_string__PythonToCpp_std_vector_std_string_(PyObject* pyIn, void* cppOut) { - ::std::vector& cppOutRef = *((::std::vector*)cppOut); - - // TEMPLATE - pySeqToStdVector - START - int vectorSize = PySequence_Size(pyIn); - cppOutRef.reserve(vectorSize); - for (int idx = 0; idx < vectorSize; ++idx) { - Shiboken::AutoDecRef pyItem(PySequence_GetItem(pyIn, idx)); - ::std::string cppItem; - Shiboken::Conversions::pythonToCppCopy(Shiboken::Conversions::PrimitiveTypeConverter(), pyItem, &(cppItem)); - cppOutRef.push_back(cppItem); - } - // TEMPLATE - pySeqToStdVector - END - -} -static PythonToCppFunc is_std_vector_std_string__PythonToCpp_std_vector_std_string__Convertible(PyObject* pyIn) { - if (Shiboken::Conversions::convertibleSequenceTypes(Shiboken::Conversions::PrimitiveTypeConverter(), pyIn)) - return std_vector_std_string__PythonToCpp_std_vector_std_string_; - return 0; -} - -// C++ to Python conversion for type 'std::pair'. -static PyObject* std_pair_std_string_std_string__CppToPython_std_pair_std_string_std_string_(const void* cppIn) { - ::std::pair& cppInRef = *((::std::pair*)cppIn); - - PyObject* pyOut = PyTuple_New(2); - PyTuple_SET_ITEM(pyOut, 0, Shiboken::Conversions::copyToPython(Shiboken::Conversions::PrimitiveTypeConverter(), &cppInRef.first)); - PyTuple_SET_ITEM(pyOut, 1, Shiboken::Conversions::copyToPython(Shiboken::Conversions::PrimitiveTypeConverter(), &cppInRef.second)); - return pyOut; - -} -static void std_pair_std_string_std_string__PythonToCpp_std_pair_std_string_std_string_(PyObject* pyIn, void* cppOut) { - ::std::pair& cppOutRef = *((::std::pair*)cppOut); - - Shiboken::Conversions::pythonToCppCopy(Shiboken::Conversions::PrimitiveTypeConverter(), PySequence_Fast_GET_ITEM(pyIn, 0), &(cppOutRef.first)); - Shiboken::Conversions::pythonToCppCopy(Shiboken::Conversions::PrimitiveTypeConverter(), PySequence_Fast_GET_ITEM(pyIn, 1), &(cppOutRef.second)); - -} -static PythonToCppFunc is_std_pair_std_string_std_string__PythonToCpp_std_pair_std_string_std_string__Convertible(PyObject* pyIn) { - if (Shiboken::Conversions::convertiblePairTypes(Shiboken::Conversions::PrimitiveTypeConverter(), false, Shiboken::Conversions::PrimitiveTypeConverter(), false, pyIn)) - return std_pair_std_string_std_string__PythonToCpp_std_pair_std_string_std_string_; - return 0; -} - -// C++ to Python conversion for type 'const std::list > &'. -static PyObject* conststd_list_std_pair_std_string_std_string__REF_CppToPython_conststd_list_std_pair_std_string_std_string__REF(const void* cppIn) { - ::std::list >& cppInRef = *((::std::list >*)cppIn); - - // TEMPLATE - stdListToPyList - START - PyObject* pyOut = PyList_New((int) cppInRef.size()); - ::std::list >::const_iterator it = cppInRef.begin(); - for (int idx = 0; it != cppInRef.end(); ++it, ++idx) { - ::std::pair cppItem(*it); - PyList_SET_ITEM(pyOut, idx, Shiboken::Conversions::copyToPython(SbkNatronEngineTypeConverters[SBK_NATRONENGINE_STD_PAIR_STD_STRING_STD_STRING_IDX], &cppItem)); - } - return pyOut; - // TEMPLATE - stdListToPyList - END - -} -static void conststd_list_std_pair_std_string_std_string__REF_PythonToCpp_conststd_list_std_pair_std_string_std_string__REF(PyObject* pyIn, void* cppOut) { - ::std::list >& cppOutRef = *((::std::list >*)cppOut); - - // TEMPLATE - pyListToStdList - START - for (int i = 0; i < PySequence_Size(pyIn); i++) { - Shiboken::AutoDecRef pyItem(PySequence_GetItem(pyIn, i)); - ::std::pair cppItem = ::std::pair(); - Shiboken::Conversions::pythonToCppCopy(SbkNatronEngineTypeConverters[SBK_NATRONENGINE_STD_PAIR_STD_STRING_STD_STRING_IDX], pyItem, &(cppItem)); - cppOutRef.push_back(cppItem); - } - // TEMPLATE - pyListToStdList - END - -} -static PythonToCppFunc is_conststd_list_std_pair_std_string_std_string__REF_PythonToCpp_conststd_list_std_pair_std_string_std_string__REF_Convertible(PyObject* pyIn) { - if (Shiboken::Conversions::convertibleSequenceTypes(SbkNatronEngineTypeConverters[SBK_NATRONENGINE_STD_PAIR_STD_STRING_STD_STRING_IDX], pyIn)) - return conststd_list_std_pair_std_string_std_string__REF_PythonToCpp_conststd_list_std_pair_std_string_std_string__REF; - return 0; -} - // C++ to Python conversion for type 'std::list'. static PyObject* std_list_ItemBasePTR__CppToPython_std_list_ItemBasePTR_(const void* cppIn) { ::std::list& cppInRef = *((::std::list*)cppIn); @@ -400,6 +307,99 @@ static PythonToCppFunc is_std_list_std_string__PythonToCpp_std_list_std_string__ return 0; } +// C++ to Python conversion for type 'std::vector'. +static PyObject* std_vector_std_string__CppToPython_std_vector_std_string_(const void* cppIn) { + ::std::vector& cppInRef = *((::std::vector*)cppIn); + + // TEMPLATE - stdVectorToPyList - START + ::std::vector::size_type vectorSize = cppInRef.size(); + PyObject* pyOut = PyList_New((int) vectorSize); + for (::std::vector::size_type idx = 0; idx < vectorSize; ++idx) { + ::std::string cppItem(cppInRef[idx]); + PyList_SET_ITEM(pyOut, idx, Shiboken::Conversions::copyToPython(Shiboken::Conversions::PrimitiveTypeConverter(), &cppItem)); + } + return pyOut; + // TEMPLATE - stdVectorToPyList - END + +} +static void std_vector_std_string__PythonToCpp_std_vector_std_string_(PyObject* pyIn, void* cppOut) { + ::std::vector& cppOutRef = *((::std::vector*)cppOut); + + // TEMPLATE - pySeqToStdVector - START + int vectorSize = PySequence_Size(pyIn); + cppOutRef.reserve(vectorSize); + for (int idx = 0; idx < vectorSize; ++idx) { + Shiboken::AutoDecRef pyItem(PySequence_GetItem(pyIn, idx)); + ::std::string cppItem; + Shiboken::Conversions::pythonToCppCopy(Shiboken::Conversions::PrimitiveTypeConverter(), pyItem, &(cppItem)); + cppOutRef.push_back(cppItem); + } + // TEMPLATE - pySeqToStdVector - END + +} +static PythonToCppFunc is_std_vector_std_string__PythonToCpp_std_vector_std_string__Convertible(PyObject* pyIn) { + if (Shiboken::Conversions::convertibleSequenceTypes(Shiboken::Conversions::PrimitiveTypeConverter(), pyIn)) + return std_vector_std_string__PythonToCpp_std_vector_std_string_; + return 0; +} + +// C++ to Python conversion for type 'std::pair'. +static PyObject* std_pair_std_string_std_string__CppToPython_std_pair_std_string_std_string_(const void* cppIn) { + ::std::pair& cppInRef = *((::std::pair*)cppIn); + + PyObject* pyOut = PyTuple_New(2); + PyTuple_SET_ITEM(pyOut, 0, Shiboken::Conversions::copyToPython(Shiboken::Conversions::PrimitiveTypeConverter(), &cppInRef.first)); + PyTuple_SET_ITEM(pyOut, 1, Shiboken::Conversions::copyToPython(Shiboken::Conversions::PrimitiveTypeConverter(), &cppInRef.second)); + return pyOut; + +} +static void std_pair_std_string_std_string__PythonToCpp_std_pair_std_string_std_string_(PyObject* pyIn, void* cppOut) { + ::std::pair& cppOutRef = *((::std::pair*)cppOut); + + Shiboken::Conversions::pythonToCppCopy(Shiboken::Conversions::PrimitiveTypeConverter(), PySequence_Fast_GET_ITEM(pyIn, 0), &(cppOutRef.first)); + Shiboken::Conversions::pythonToCppCopy(Shiboken::Conversions::PrimitiveTypeConverter(), PySequence_Fast_GET_ITEM(pyIn, 1), &(cppOutRef.second)); + +} +static PythonToCppFunc is_std_pair_std_string_std_string__PythonToCpp_std_pair_std_string_std_string__Convertible(PyObject* pyIn) { + if (Shiboken::Conversions::convertiblePairTypes(Shiboken::Conversions::PrimitiveTypeConverter(), false, Shiboken::Conversions::PrimitiveTypeConverter(), false, pyIn)) + return std_pair_std_string_std_string__PythonToCpp_std_pair_std_string_std_string_; + return 0; +} + +// C++ to Python conversion for type 'const std::list > &'. +static PyObject* conststd_list_std_pair_std_string_std_string__REF_CppToPython_conststd_list_std_pair_std_string_std_string__REF(const void* cppIn) { + ::std::list >& cppInRef = *((::std::list >*)cppIn); + + // TEMPLATE - stdListToPyList - START + PyObject* pyOut = PyList_New((int) cppInRef.size()); + ::std::list >::const_iterator it = cppInRef.begin(); + for (int idx = 0; it != cppInRef.end(); ++it, ++idx) { + ::std::pair cppItem(*it); + PyList_SET_ITEM(pyOut, idx, Shiboken::Conversions::copyToPython(SbkNatronEngineTypeConverters[SBK_NATRONENGINE_STD_PAIR_STD_STRING_STD_STRING_IDX], &cppItem)); + } + return pyOut; + // TEMPLATE - stdListToPyList - END + +} +static void conststd_list_std_pair_std_string_std_string__REF_PythonToCpp_conststd_list_std_pair_std_string_std_string__REF(PyObject* pyIn, void* cppOut) { + ::std::list >& cppOutRef = *((::std::list >*)cppOut); + + // TEMPLATE - pyListToStdList - START + for (int i = 0; i < PySequence_Size(pyIn); i++) { + Shiboken::AutoDecRef pyItem(PySequence_GetItem(pyIn, i)); + ::std::pair cppItem = ::std::pair(); + Shiboken::Conversions::pythonToCppCopy(SbkNatronEngineTypeConverters[SBK_NATRONENGINE_STD_PAIR_STD_STRING_STD_STRING_IDX], pyItem, &(cppItem)); + cppOutRef.push_back(cppItem); + } + // TEMPLATE - pyListToStdList - END + +} +static PythonToCppFunc is_conststd_list_std_pair_std_string_std_string__REF_PythonToCpp_conststd_list_std_pair_std_string_std_string__REF_Convertible(PyObject* pyIn) { + if (Shiboken::Conversions::convertibleSequenceTypes(SbkNatronEngineTypeConverters[SBK_NATRONENGINE_STD_PAIR_STD_STRING_STD_STRING_IDX], pyIn)) + return conststd_list_std_pair_std_string_std_string__REF_PythonToCpp_conststd_list_std_pair_std_string_std_string__REF; + return 0; +} + // C++ to Python conversion for type 'QList'. static PyObject* _QList_QVariant__CppToPython__QList_QVariant_(const void* cppIn) { ::QList& cppInRef = *((::QList*)cppIn); @@ -558,19 +558,16 @@ 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_Roto(module); init_UserParamHolder(module); - init_Effect(module); init_Param(module); init_AnimatedParam(module); + init_IntParam(module); + init_Int2DParam(module); + init_Int3DParam(module); + init_DoubleParam(module); + init_Double2DParam(module); + init_Double3DParam(module); init_ColorParam(module); init_ChoiceParam(module); init_BooleanParam(module); @@ -579,22 +576,25 @@ SBK_MODULE_INIT_FUNCTION_BEGIN(NatronEngine) init_FileParam(module); init_OutputFileParam(module); init_PathParam(module); - init_IntParam(module); - init_Int2DParam(module); - init_Int3DParam(module); - init_DoubleParam(module); - init_Double2DParam(module); - init_Double3DParam(module); - init_ButtonParam(module); - init_GroupParam(module); init_PageParam(module); + init_ButtonParam(module); init_ParametricParam(module); + init_GroupParam(module); init_Int2DTuple(module); init_Int3DTuple(module); init_Double2DTuple(module); init_Double3DTuple(module); init_ColorTuple(module); + init_PyCoreApplication(module); + init_Group(module); + init_App(module); + init_Effect(module); + init_AppSettings(module); + init_ItemBase(module); + init_Layer(module); + init_BezierCurve(module); init_RectI(module); + init_RectD(module); init_Natron(module); // Register converter for type 'NatronEngine.std::size_t'. @@ -613,28 +613,6 @@ SBK_MODULE_INIT_FUNCTION_BEGIN(NatronEngine) std_vector_RectI__PythonToCpp_std_vector_RectI_, is_std_vector_RectI__PythonToCpp_std_vector_RectI__Convertible); - // Register converter for type 'std::vector'. - SbkNatronEngineTypeConverters[SBK_NATRONENGINE_STD_VECTOR_STD_STRING_IDX] = Shiboken::Conversions::createConverter(&PyList_Type, std_vector_std_string__CppToPython_std_vector_std_string_); - Shiboken::Conversions::registerConverterName(SbkNatronEngineTypeConverters[SBK_NATRONENGINE_STD_VECTOR_STD_STRING_IDX], "std::vector"); - Shiboken::Conversions::addPythonToCppValueConversion(SbkNatronEngineTypeConverters[SBK_NATRONENGINE_STD_VECTOR_STD_STRING_IDX], - std_vector_std_string__PythonToCpp_std_vector_std_string_, - is_std_vector_std_string__PythonToCpp_std_vector_std_string__Convertible); - - // Register converter for type 'std::pair'. - SbkNatronEngineTypeConverters[SBK_NATRONENGINE_STD_PAIR_STD_STRING_STD_STRING_IDX] = Shiboken::Conversions::createConverter(&PyList_Type, std_pair_std_string_std_string__CppToPython_std_pair_std_string_std_string_); - Shiboken::Conversions::registerConverterName(SbkNatronEngineTypeConverters[SBK_NATRONENGINE_STD_PAIR_STD_STRING_STD_STRING_IDX], "std::pair"); - Shiboken::Conversions::addPythonToCppValueConversion(SbkNatronEngineTypeConverters[SBK_NATRONENGINE_STD_PAIR_STD_STRING_STD_STRING_IDX], - std_pair_std_string_std_string__PythonToCpp_std_pair_std_string_std_string_, - is_std_pair_std_string_std_string__PythonToCpp_std_pair_std_string_std_string__Convertible); - - // Register converter for type 'const std::list>&'. - SbkNatronEngineTypeConverters[SBK_NATRONENGINE_STD_LIST_STD_PAIR_STD_STRING_STD_STRING_IDX] = Shiboken::Conversions::createConverter(&PyList_Type, conststd_list_std_pair_std_string_std_string__REF_CppToPython_conststd_list_std_pair_std_string_std_string__REF); - Shiboken::Conversions::registerConverterName(SbkNatronEngineTypeConverters[SBK_NATRONENGINE_STD_LIST_STD_PAIR_STD_STRING_STD_STRING_IDX], "const std::list>&"); - Shiboken::Conversions::registerConverterName(SbkNatronEngineTypeConverters[SBK_NATRONENGINE_STD_LIST_STD_PAIR_STD_STRING_STD_STRING_IDX], "std::list>"); - Shiboken::Conversions::addPythonToCppValueConversion(SbkNatronEngineTypeConverters[SBK_NATRONENGINE_STD_LIST_STD_PAIR_STD_STRING_STD_STRING_IDX], - conststd_list_std_pair_std_string_std_string__REF_PythonToCpp_conststd_list_std_pair_std_string_std_string__REF, - is_conststd_list_std_pair_std_string_std_string__REF_PythonToCpp_conststd_list_std_pair_std_string_std_string__REF_Convertible); - // Register converter for type 'std::list'. SbkNatronEngineTypeConverters[SBK_NATRONENGINE_STD_LIST_ITEMBASEPTR_IDX] = Shiboken::Conversions::createConverter(&PyList_Type, std_list_ItemBasePTR__CppToPython_std_list_ItemBasePTR_); Shiboken::Conversions::registerConverterName(SbkNatronEngineTypeConverters[SBK_NATRONENGINE_STD_LIST_ITEMBASEPTR_IDX], "std::list"); @@ -671,6 +649,28 @@ SBK_MODULE_INIT_FUNCTION_BEGIN(NatronEngine) std_list_std_string__PythonToCpp_std_list_std_string_, is_std_list_std_string__PythonToCpp_std_list_std_string__Convertible); + // Register converter for type 'std::vector'. + SbkNatronEngineTypeConverters[SBK_NATRONENGINE_STD_VECTOR_STD_STRING_IDX] = Shiboken::Conversions::createConverter(&PyList_Type, std_vector_std_string__CppToPython_std_vector_std_string_); + Shiboken::Conversions::registerConverterName(SbkNatronEngineTypeConverters[SBK_NATRONENGINE_STD_VECTOR_STD_STRING_IDX], "std::vector"); + Shiboken::Conversions::addPythonToCppValueConversion(SbkNatronEngineTypeConverters[SBK_NATRONENGINE_STD_VECTOR_STD_STRING_IDX], + std_vector_std_string__PythonToCpp_std_vector_std_string_, + is_std_vector_std_string__PythonToCpp_std_vector_std_string__Convertible); + + // Register converter for type 'std::pair'. + SbkNatronEngineTypeConverters[SBK_NATRONENGINE_STD_PAIR_STD_STRING_STD_STRING_IDX] = Shiboken::Conversions::createConverter(&PyList_Type, std_pair_std_string_std_string__CppToPython_std_pair_std_string_std_string_); + Shiboken::Conversions::registerConverterName(SbkNatronEngineTypeConverters[SBK_NATRONENGINE_STD_PAIR_STD_STRING_STD_STRING_IDX], "std::pair"); + Shiboken::Conversions::addPythonToCppValueConversion(SbkNatronEngineTypeConverters[SBK_NATRONENGINE_STD_PAIR_STD_STRING_STD_STRING_IDX], + std_pair_std_string_std_string__PythonToCpp_std_pair_std_string_std_string_, + is_std_pair_std_string_std_string__PythonToCpp_std_pair_std_string_std_string__Convertible); + + // Register converter for type 'const std::list>&'. + SbkNatronEngineTypeConverters[SBK_NATRONENGINE_STD_LIST_STD_PAIR_STD_STRING_STD_STRING_IDX] = Shiboken::Conversions::createConverter(&PyList_Type, conststd_list_std_pair_std_string_std_string__REF_CppToPython_conststd_list_std_pair_std_string_std_string__REF); + Shiboken::Conversions::registerConverterName(SbkNatronEngineTypeConverters[SBK_NATRONENGINE_STD_LIST_STD_PAIR_STD_STRING_STD_STRING_IDX], "const std::list>&"); + Shiboken::Conversions::registerConverterName(SbkNatronEngineTypeConverters[SBK_NATRONENGINE_STD_LIST_STD_PAIR_STD_STRING_STD_STRING_IDX], "std::list>"); + Shiboken::Conversions::addPythonToCppValueConversion(SbkNatronEngineTypeConverters[SBK_NATRONENGINE_STD_LIST_STD_PAIR_STD_STRING_STD_STRING_IDX], + conststd_list_std_pair_std_string_std_string__REF_PythonToCpp_conststd_list_std_pair_std_string_std_string__REF, + is_conststd_list_std_pair_std_string_std_string__REF_PythonToCpp_conststd_list_std_pair_std_string_std_string__REF_Convertible); + // Register converter for type 'QList'. SbkNatronEngineTypeConverters[SBK_NATRONENGINE_QLIST_QVARIANT_IDX] = Shiboken::Conversions::createConverter(&PyList_Type, _QList_QVariant__CppToPython__QList_QVariant_); Shiboken::Conversions::registerConverterName(SbkNatronEngineTypeConverters[SBK_NATRONENGINE_QLIST_QVARIANT_IDX], "QList"); diff --git a/Engine/NatronEngine/natronengine_python.h b/Engine/NatronEngine/natronengine_python.h index 37d0bdfbfb..5a746fec4a 100644 --- a/Engine/NatronEngine/natronengine_python.h +++ b/Engine/NatronEngine/natronengine_python.h @@ -72,18 +72,32 @@ 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_ITEMBASE_IDX 24 +#define SBK_BEZIERCURVE_IDX 3 +#define SBK_BEZIERCURVE_CAIROOPERATORENUM_IDX 4 +#define SBK_LAYER_IDX 25 +#define SBK_APPSETTINGS_IDX 2 +#define SBK_GROUP_IDX 17 +#define SBK_APP_IDX 1 +#define SBK_PYCOREAPPLICATION_IDX 46 #define SBK_COLORTUPLE_IDX 9 #define SBK_DOUBLE3DTUPLE_IDX 13 #define SBK_DOUBLE2DTUPLE_IDX 11 #define SBK_INT3DTUPLE_IDX 22 #define SBK_INT2DTUPLE_IDX 20 #define SBK_PARAM_IDX 43 -#define SBK_PARAMETRICPARAM_IDX 44 -#define SBK_PAGEPARAM_IDX 42 #define SBK_GROUPPARAM_IDX 18 #define SBK_BUTTONPARAM_IDX 6 #define SBK_ANIMATEDPARAM_IDX 0 +#define SBK_STRINGPARAMBASE_IDX 53 +#define SBK_PATHPARAM_IDX 45 +#define SBK_OUTPUTFILEPARAM_IDX 41 +#define SBK_FILEPARAM_IDX 16 +#define SBK_STRINGPARAM_IDX 51 +#define SBK_STRINGPARAM_TYPEENUM_IDX 52 +#define SBK_BOOLEANPARAM_IDX 5 #define SBK_CHOICEPARAM_IDX 7 #define SBK_COLORPARAM_IDX 8 #define SBK_DOUBLEPARAM_IDX 14 @@ -92,25 +106,11 @@ CLANG_DIAG_ON(uninitialized) #define SBK_INTPARAM_IDX 23 #define SBK_INT2DPARAM_IDX 19 #define SBK_INT3DPARAM_IDX 21 -#define SBK_STRINGPARAMBASE_IDX 53 -#define SBK_PATHPARAM_IDX 45 -#define SBK_OUTPUTFILEPARAM_IDX 41 -#define SBK_FILEPARAM_IDX 16 -#define SBK_STRINGPARAM_IDX 51 -#define SBK_STRINGPARAM_TYPEENUM_IDX 52 -#define SBK_BOOLEANPARAM_IDX 5 +#define SBK_PARAMETRICPARAM_IDX 44 +#define SBK_PAGEPARAM_IDX 42 #define SBK_USERPARAMHOLDER_IDX 54 -#define SBK_ROTO_IDX 50 -#define SBK_ITEMBASE_IDX 24 -#define SBK_BEZIERCURVE_IDX 3 -#define SBK_BEZIERCURVE_CAIROOPERATORENUM_IDX 4 -#define SBK_LAYER_IDX 25 -#define SBK_APPSETTINGS_IDX 2 -#define SBK_GROUP_IDX 17 -#define SBK_APP_IDX 1 #define SBK_EFFECT_IDX 15 -#define SBK_PYCOREAPPLICATION_IDX 46 -#define SBK_RECTD_IDX 48 +#define SBK_ROTO_IDX 50 #define SBK_NatronEngine_IDX_COUNT 55 // This variable stores all Python types exported by this module. @@ -122,14 +122,14 @@ extern SbkConverter** SbkNatronEngineTypeConverters; // Converter indices #define SBK_STD_SIZE_T_IDX 0 #define SBK_NATRONENGINE_STD_VECTOR_RECTI_IDX 1 // std::vector -#define SBK_NATRONENGINE_STD_VECTOR_STD_STRING_IDX 2 // std::vector -#define SBK_NATRONENGINE_STD_PAIR_STD_STRING_STD_STRING_IDX 3 // std::pair -#define SBK_NATRONENGINE_STD_LIST_STD_PAIR_STD_STRING_STD_STRING_IDX 4 // const std::list > & -#define SBK_NATRONENGINE_STD_LIST_ITEMBASEPTR_IDX 5 // std::list -#define SBK_NATRONENGINE_STD_LIST_PARAMPTR_IDX 6 // std::list -#define SBK_NATRONENGINE_STD_LIST_EFFECTPTR_IDX 7 // std::list -#define SBK_NATRONENGINE_STD_LIST_INT_IDX 8 // const std::list & -#define SBK_NATRONENGINE_STD_LIST_STD_STRING_IDX 9 // std::list +#define SBK_NATRONENGINE_STD_LIST_ITEMBASEPTR_IDX 2 // std::list +#define SBK_NATRONENGINE_STD_LIST_PARAMPTR_IDX 3 // std::list +#define SBK_NATRONENGINE_STD_LIST_EFFECTPTR_IDX 4 // std::list +#define SBK_NATRONENGINE_STD_LIST_INT_IDX 5 // const std::list & +#define SBK_NATRONENGINE_STD_LIST_STD_STRING_IDX 6 // std::list +#define SBK_NATRONENGINE_STD_VECTOR_STD_STRING_IDX 7 // std::vector +#define SBK_NATRONENGINE_STD_PAIR_STD_STRING_STD_STRING_IDX 8 // std::pair +#define SBK_NATRONENGINE_STD_LIST_STD_PAIR_STD_STRING_STD_STRING_IDX 9 // const std::list > & #define SBK_NATRONENGINE_QLIST_QVARIANT_IDX 10 // QList #define SBK_NATRONENGINE_QLIST_QSTRING_IDX 11 // QList #define SBK_NATRONENGINE_QMAP_QSTRING_QVARIANT_IDX 12 // QMap @@ -156,18 +156,32 @@ 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< ::ItemBase >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_ITEMBASE_IDX]); } +template<> inline PyTypeObject* SbkType< ::BezierCurve::CairoOperatorEnum >() { return SbkNatronEngineTypes[SBK_BEZIERCURVE_CAIROOPERATORENUM_IDX]; } +template<> inline PyTypeObject* SbkType< ::BezierCurve >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_BEZIERCURVE_IDX]); } +template<> inline PyTypeObject* SbkType< ::Layer >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_LAYER_IDX]); } +template<> inline PyTypeObject* SbkType< ::AppSettings >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_APPSETTINGS_IDX]); } +template<> inline PyTypeObject* SbkType< ::Group >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_GROUP_IDX]); } +template<> inline PyTypeObject* SbkType< ::App >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_APP_IDX]); } +template<> inline PyTypeObject* SbkType< ::PyCoreApplication >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_PYCOREAPPLICATION_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]); } template<> inline PyTypeObject* SbkType< ::Double2DTuple >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_DOUBLE2DTUPLE_IDX]); } template<> inline PyTypeObject* SbkType< ::Int3DTuple >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_INT3DTUPLE_IDX]); } template<> inline PyTypeObject* SbkType< ::Int2DTuple >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_INT2DTUPLE_IDX]); } template<> inline PyTypeObject* SbkType< ::Param >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_PARAM_IDX]); } -template<> inline PyTypeObject* SbkType< ::ParametricParam >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_PARAMETRICPARAM_IDX]); } -template<> inline PyTypeObject* SbkType< ::PageParam >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_PAGEPARAM_IDX]); } 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< ::StringParamBase >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_STRINGPARAMBASE_IDX]); } +template<> inline PyTypeObject* SbkType< ::PathParam >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_PATHPARAM_IDX]); } +template<> inline PyTypeObject* SbkType< ::OutputFileParam >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_OUTPUTFILEPARAM_IDX]); } +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< ::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]); } @@ -176,25 +190,11 @@ template<> inline PyTypeObject* SbkType< ::Double3DParam >() { return reinterpre template<> inline PyTypeObject* SbkType< ::IntParam >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_INTPARAM_IDX]); } template<> inline PyTypeObject* SbkType< ::Int2DParam >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_INT2DPARAM_IDX]); } template<> inline PyTypeObject* SbkType< ::Int3DParam >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_INT3DPARAM_IDX]); } -template<> inline PyTypeObject* SbkType< ::StringParamBase >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_STRINGPARAMBASE_IDX]); } -template<> inline PyTypeObject* SbkType< ::PathParam >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_PATHPARAM_IDX]); } -template<> inline PyTypeObject* SbkType< ::OutputFileParam >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_OUTPUTFILEPARAM_IDX]); } -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< ::ParametricParam >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_PARAMETRICPARAM_IDX]); } +template<> inline PyTypeObject* SbkType< ::PageParam >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_PAGEPARAM_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]); } -template<> inline PyTypeObject* SbkType< ::BezierCurve::CairoOperatorEnum >() { return SbkNatronEngineTypes[SBK_BEZIERCURVE_CAIROOPERATORENUM_IDX]; } -template<> inline PyTypeObject* SbkType< ::BezierCurve >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_BEZIERCURVE_IDX]); } -template<> inline PyTypeObject* SbkType< ::Layer >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_LAYER_IDX]); } -template<> inline PyTypeObject* SbkType< ::AppSettings >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_APPSETTINGS_IDX]); } -template<> inline PyTypeObject* SbkType< ::Group >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_GROUP_IDX]); } -template<> 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]); } +template<> inline PyTypeObject* SbkType< ::Roto >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_ROTO_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/ParameterWrapper.cpp b/Engine/ParameterWrapper.cpp index 412cfe45ce..303a663bcf 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); } diff --git a/Engine/ParameterWrapper.h b/Engine/ParameterWrapper.h index 6067403615..c6379b49a6 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. 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/NatronGui/natrongui_module_wrapper.cpp b/Gui/NatronGui/natrongui_module_wrapper.cpp index 844ab26bb6..61a193684d 100644 --- a/Gui/NatronGui/natrongui_module_wrapper.cpp +++ b/Gui/NatronGui/natrongui_module_wrapper.cpp @@ -32,10 +32,10 @@ static PyMethodDef NatronGui_methods[] = { }; // Classes initialization functions ------------------------------------------------------------ -void init_GuiApp(PyObject* module); -void init_PyGuiApplication(PyObject* module); void init_PyViewer(PyObject* module); void init_PyTabWidget(PyObject* module); +void init_GuiApp(PyObject* module); +void init_PyGuiApplication(PyObject* module); void init_PyModalDialog(PyObject* module); void init_PyPanel(PyObject* module); @@ -46,40 +46,6 @@ SbkConverter** SbkPySide_QtGuiTypeConverters; // Module initialization ------------------------------------------------------------ // Container Type converters. -// C++ to Python conversion for type 'std::list'. -static PyObject* _std_list_std_string__CppToPython__std_list_std_string_(const void* cppIn) { - ::std::list& cppInRef = *((::std::list*)cppIn); - - // TEMPLATE - stdListToPyList - START - PyObject* pyOut = PyList_New((int) cppInRef.size()); - ::std::list::const_iterator it = cppInRef.begin(); - for (int idx = 0; it != cppInRef.end(); ++it, ++idx) { - ::std::string cppItem(*it); - PyList_SET_ITEM(pyOut, idx, Shiboken::Conversions::copyToPython(Shiboken::Conversions::PrimitiveTypeConverter(), &cppItem)); - } - return pyOut; - // TEMPLATE - stdListToPyList - END - -} -static void _std_list_std_string__PythonToCpp__std_list_std_string_(PyObject* pyIn, void* cppOut) { - ::std::list& cppOutRef = *((::std::list*)cppOut); - - // TEMPLATE - pyListToStdList - START - for (int i = 0; i < PySequence_Size(pyIn); i++) { - Shiboken::AutoDecRef pyItem(PySequence_GetItem(pyIn, i)); - ::std::string cppItem; - Shiboken::Conversions::pythonToCppCopy(Shiboken::Conversions::PrimitiveTypeConverter(), pyItem, &(cppItem)); - cppOutRef.push_back(cppItem); - } - // TEMPLATE - pyListToStdList - END - -} -static PythonToCppFunc is__std_list_std_string__PythonToCpp__std_list_std_string__Convertible(PyObject* pyIn) { - if (Shiboken::Conversions::convertibleSequenceTypes(Shiboken::Conversions::PrimitiveTypeConverter(), pyIn)) - return _std_list_std_string__PythonToCpp__std_list_std_string_; - return 0; -} - // C++ to Python conversion for type 'std::list'. static PyObject* _std_list_EffectPTR__CppToPython__std_list_EffectPTR_(const void* cppIn) { ::std::list& cppInRef = *((::std::list*)cppIn); @@ -184,6 +150,40 @@ static PythonToCppFunc is__conststd_list_int_REF_PythonToCpp__conststd_list_int_ return 0; } +// C++ to Python conversion for type 'std::list'. +static PyObject* _std_list_std_string__CppToPython__std_list_std_string_(const void* cppIn) { + ::std::list& cppInRef = *((::std::list*)cppIn); + + // TEMPLATE - stdListToPyList - START + PyObject* pyOut = PyList_New((int) cppInRef.size()); + ::std::list::const_iterator it = cppInRef.begin(); + for (int idx = 0; it != cppInRef.end(); ++it, ++idx) { + ::std::string cppItem(*it); + PyList_SET_ITEM(pyOut, idx, Shiboken::Conversions::copyToPython(Shiboken::Conversions::PrimitiveTypeConverter(), &cppItem)); + } + return pyOut; + // TEMPLATE - stdListToPyList - END + +} +static void _std_list_std_string__PythonToCpp__std_list_std_string_(PyObject* pyIn, void* cppOut) { + ::std::list& cppOutRef = *((::std::list*)cppOut); + + // TEMPLATE - pyListToStdList - START + for (int i = 0; i < PySequence_Size(pyIn); i++) { + Shiboken::AutoDecRef pyItem(PySequence_GetItem(pyIn, i)); + ::std::string cppItem; + Shiboken::Conversions::pythonToCppCopy(Shiboken::Conversions::PrimitiveTypeConverter(), pyItem, &(cppItem)); + cppOutRef.push_back(cppItem); + } + // TEMPLATE - pyListToStdList - END + +} +static PythonToCppFunc is__std_list_std_string__PythonToCpp__std_list_std_string__Convertible(PyObject* pyIn) { + if (Shiboken::Conversions::convertibleSequenceTypes(Shiboken::Conversions::PrimitiveTypeConverter(), pyIn)) + return _std_list_std_string__PythonToCpp__std_list_std_string_; + return 0; +} + // C++ to Python conversion for type 'QList'. static PyObject* _QList_QActionPTR__CppToPython__QList_QActionPTR_(const void* cppIn) { ::QList& cppInRef = *((::QList*)cppIn); @@ -494,20 +494,13 @@ SBK_MODULE_INIT_FUNCTION_BEGIN(NatronGui) #endif // Initialize classes in the type system - init_GuiApp(module); - init_PyGuiApplication(module); init_PyViewer(module); init_PyTabWidget(module); + init_GuiApp(module); + init_PyGuiApplication(module); init_PyModalDialog(module); init_PyPanel(module); - // Register converter for type 'std::list'. - SbkNatronGuiTypeConverters[SBK_NATRONGUI_STD_LIST_STD_STRING_IDX] = Shiboken::Conversions::createConverter(&PyList_Type, _std_list_std_string__CppToPython__std_list_std_string_); - Shiboken::Conversions::registerConverterName(SbkNatronGuiTypeConverters[SBK_NATRONGUI_STD_LIST_STD_STRING_IDX], "std::list"); - Shiboken::Conversions::addPythonToCppValueConversion(SbkNatronGuiTypeConverters[SBK_NATRONGUI_STD_LIST_STD_STRING_IDX], - _std_list_std_string__PythonToCpp__std_list_std_string_, - is__std_list_std_string__PythonToCpp__std_list_std_string__Convertible); - // Register converter for type 'std::list'. SbkNatronGuiTypeConverters[SBK_NATRONGUI_STD_LIST_EFFECTPTR_IDX] = Shiboken::Conversions::createConverter(&PyList_Type, _std_list_EffectPTR__CppToPython__std_list_EffectPTR_); Shiboken::Conversions::registerConverterName(SbkNatronGuiTypeConverters[SBK_NATRONGUI_STD_LIST_EFFECTPTR_IDX], "std::list"); @@ -531,6 +524,13 @@ SBK_MODULE_INIT_FUNCTION_BEGIN(NatronGui) _conststd_list_int_REF_PythonToCpp__conststd_list_int_REF, is__conststd_list_int_REF_PythonToCpp__conststd_list_int_REF_Convertible); + // Register converter for type 'std::list'. + SbkNatronGuiTypeConverters[SBK_NATRONGUI_STD_LIST_STD_STRING_IDX] = Shiboken::Conversions::createConverter(&PyList_Type, _std_list_std_string__CppToPython__std_list_std_string_); + Shiboken::Conversions::registerConverterName(SbkNatronGuiTypeConverters[SBK_NATRONGUI_STD_LIST_STD_STRING_IDX], "std::list"); + Shiboken::Conversions::addPythonToCppValueConversion(SbkNatronGuiTypeConverters[SBK_NATRONGUI_STD_LIST_STD_STRING_IDX], + _std_list_std_string__PythonToCpp__std_list_std_string_, + is__std_list_std_string__PythonToCpp__std_list_std_string__Convertible); + // Register converter for type 'QList'. SbkNatronGuiTypeConverters[SBK_NATRONGUI_QLIST_QACTIONPTR_IDX] = Shiboken::Conversions::createConverter(&PyList_Type, _QList_QActionPTR__CppToPython__QList_QActionPTR_); Shiboken::Conversions::registerConverterName(SbkNatronGuiTypeConverters[SBK_NATRONGUI_QLIST_QACTIONPTR_IDX], "QList"); diff --git a/Gui/NatronGui/natrongui_python.h b/Gui/NatronGui/natrongui_python.h index 5af481fe4c..573ab7974b 100644 --- a/Gui/NatronGui/natrongui_python.h +++ b/Gui/NatronGui/natrongui_python.h @@ -56,9 +56,9 @@ CLANG_DIAG_ON(uninitialized) // Type indices #define SBK_PYTABWIDGET_IDX 4 +#define SBK_GUIAPP_IDX 0 #define SBK_PYVIEWER_IDX 5 #define SBK_PYGUIAPPLICATION_IDX 1 -#define SBK_GUIAPP_IDX 0 #define SBK_PYPANEL_IDX 3 #define SBK_PYMODALDIALOG_IDX 2 #define SBK_NatronGui_IDX_COUNT 6 @@ -70,10 +70,10 @@ extern PyTypeObject** SbkNatronGuiTypes; extern SbkConverter** SbkNatronGuiTypeConverters; // Converter indices -#define SBK_NATRONGUI_STD_LIST_STD_STRING_IDX 0 // std::list -#define SBK_NATRONGUI_STD_LIST_EFFECTPTR_IDX 1 // std::list -#define SBK_NATRONGUI_STD_VECTOR_STD_STRING_IDX 2 // const std::vector & -#define SBK_NATRONGUI_STD_LIST_INT_IDX 3 // const std::list & +#define SBK_NATRONGUI_STD_LIST_EFFECTPTR_IDX 0 // std::list +#define SBK_NATRONGUI_STD_VECTOR_STD_STRING_IDX 1 // const std::vector & +#define SBK_NATRONGUI_STD_LIST_INT_IDX 2 // const std::list & +#define SBK_NATRONGUI_STD_LIST_STD_STRING_IDX 3 // std::list #define SBK_NATRONGUI_QLIST_QACTIONPTR_IDX 4 // QList #define SBK_NATRONGUI_QLIST_QOBJECTPTR_IDX 5 // const QList & #define SBK_NATRONGUI_QLIST_QBYTEARRAY_IDX 6 // QList @@ -90,9 +90,9 @@ namespace Shiboken // PyType functions, to get the PyObjectType for a type T template<> inline PyTypeObject* SbkType< ::PyTabWidget >() { return reinterpret_cast(SbkNatronGuiTypes[SBK_PYTABWIDGET_IDX]); } +template<> inline PyTypeObject* SbkType< ::GuiApp >() { return reinterpret_cast(SbkNatronGuiTypes[SBK_GUIAPP_IDX]); } template<> inline PyTypeObject* SbkType< ::PyViewer >() { return reinterpret_cast(SbkNatronGuiTypes[SBK_PYVIEWER_IDX]); } template<> inline PyTypeObject* SbkType< ::PyGuiApplication >() { return reinterpret_cast(SbkNatronGuiTypes[SBK_PYGUIAPPLICATION_IDX]); } -template<> inline PyTypeObject* SbkType< ::GuiApp >() { return reinterpret_cast(SbkNatronGuiTypes[SBK_GUIAPP_IDX]); } template<> inline PyTypeObject* SbkType< ::PyPanel >() { return reinterpret_cast(SbkNatronGuiTypes[SBK_PYPANEL_IDX]); } template<> inline PyTypeObject* SbkType< ::PyModalDialog >() { return reinterpret_cast(SbkNatronGuiTypes[SBK_PYMODALDIALOG_IDX]); } From 41b36144c91d6b0077d052165c9052665b62ed8f Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Tue, 29 Sep 2015 16:00:03 +0200 Subject: [PATCH 042/178] Python API: beginChanges() and endChanges() also call beginInputEdition() and endInputEdition --- .../source/PythonReference/NatronEngine/Effect.rst | 12 ++++++++++-- Engine/Node.cpp | 12 ++++++++---- Engine/NodeWrapper.cpp | 2 ++ 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/Documentation/source/PythonReference/NatronEngine/Effect.rst b/Documentation/source/PythonReference/NatronEngine/Effect.rst index f799015da7..efb3fe6c64 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() diff --git a/Engine/Node.cpp b/Engine/Node.cpp index 00c8f07a47..5c320da74c 100644 --- a/Engine/Node.cpp +++ b/Engine/Node.cpp @@ -5368,11 +5368,15 @@ Node::endInputEdition(bool triggerRender) if (!_imp->inputModifiedRecursion) { - forceRefreshAllInputRelatedData(); - refreshDynamicProperties(); - - triggerRender = triggerRender && !_imp->inputsModified.empty(); + bool hasChanged = !_imp->inputsModified.empty(); _imp->inputsModified.clear(); + + if (hasChanged) { + forceRefreshAllInputRelatedData(); + refreshDynamicProperties(); + } + + triggerRender = triggerRender && hasChanged; if (triggerRender) { std::list viewers; diff --git a/Engine/NodeWrapper.cpp b/Engine/NodeWrapper.cpp index d77ee1846e..26967fa1e7 100644 --- a/Engine/NodeWrapper.cpp +++ b/Engine/NodeWrapper.cpp @@ -298,12 +298,14 @@ void Effect::beginChanges() { _node->getLiveInstance()->beginChanges(); + _node->beginInputEdition(); } void Effect::endChanges() { _node->getLiveInstance()->endChanges(); + _node->endInputEdition(true); } IntParam* From a9c5fb7c86e6e8a8b4f40ec780d5483cf3877b7e Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Tue, 29 Sep 2015 16:34:33 +0200 Subject: [PATCH 043/178] Factorize focus stealing code --- Gui/CurveWidget.cpp | 20 ++----------- Gui/DopeSheetView.cpp | 4 ++- Gui/Gui.h | 6 ++++ Gui/Gui.pro | 1 + Gui/Gui50.cpp | 55 ++++++++++++++++++++++++++++++++---- Gui/Histogram.cpp | 14 +-------- Gui/NodeGraph30.cpp | 17 ++--------- Gui/PropertiesBinWrapper.cpp | 35 +++++++++++++++++++++++ Gui/PropertiesBinWrapper.h | 7 ++++- Gui/ScaleSliderQWidget.cpp | 2 +- Gui/ViewerGL.cpp | 24 ++-------------- 11 files changed, 109 insertions(+), 76 deletions(-) create mode 100644 Gui/PropertiesBinWrapper.cpp diff --git a/Gui/CurveWidget.cpp b/Gui/CurveWidget.cpp index 40ff0e18d6..cab4fb88b0 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" @@ -1351,24 +1350,9 @@ 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) { + if (_imp->_gui->isFocusStealingPossible()) { setFocus(); } - QGLWidget::enterEvent(e); } diff --git a/Gui/DopeSheetView.cpp b/Gui/DopeSheetView.cpp index 33fc015c9e..2ba63eb729 100644 --- a/Gui/DopeSheetView.cpp +++ b/Gui/DopeSheetView.cpp @@ -3439,7 +3439,9 @@ void DopeSheetView::enterEvent(QEvent *e) { running_in_main_thread(); - setFocus(); + if (_imp->gui && _imp->gui->isFocusStealingPossible()) { + setFocus(); + } QGLWidget::enterEvent(e); } diff --git a/Gui/Gui.h b/Gui/Gui.h index 4957891ca6..c2a5e227b3 100644 --- a/Gui/Gui.h +++ b/Gui/Gui.h @@ -587,6 +587,12 @@ 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(); + Q_SIGNALS: diff --git a/Gui/Gui.pro b/Gui/Gui.pro index ee16d5ef7d..1bbabf7a2e 100644 --- a/Gui/Gui.pro +++ b/Gui/Gui.pro @@ -164,6 +164,7 @@ SOURCES += \ PreferencesPanel.cpp \ ProjectGui.cpp \ ProjectGuiSerialization.cpp \ + PropertiesBinWrapper.cpp \ PythonPanels.cpp \ QtDecoder.cpp \ QtEncoder.cpp \ diff --git a/Gui/Gui50.cpp b/Gui/Gui50.cpp index 83c06f74c7..f09f65a725 100644 --- a/Gui/Gui50.cpp +++ b/Gui/Gui50.cpp @@ -43,8 +43,11 @@ GCC_DIAG_UNUSED_PRIVATE_FIELD_OFF #include GCC_DIAG_UNUSED_PRIVATE_FIELD_ON #include +#include #include #include +#include +#include #include "Engine/GroupOutput.h" #include "Engine/Node.h" @@ -56,6 +59,7 @@ GCC_DIAG_UNUSED_PRIVATE_FIELD_ON #include "Gui/ActionShortcuts.h" #include "Gui/CurveEditor.h" +#include "Gui/CurveWidget.h" #include "Gui/DockablePanel.h" #include "Gui/DopeSheetEditor.h" #include "Gui/GuiAppInstance.h" @@ -73,7 +77,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; @@ -432,15 +436,30 @@ Gui::resizeEvent(QResizeEvent* e) setMtSafeWindowSize( width(), height() ); } +static RightClickableWidget* isParentSettingsPanelRecursive(QWidget* w) +{ + if (!w) { + return false; + } + RightClickableWidget* panel = qobject_cast(w); + if (panel) { + return panel; + } else { + return isParentSettingsPanelRecursive(w->parentWidget()); + } +} + void Gui::keyPressEvent(QKeyEvent* e) { QWidget* w = qApp->widgetAt( QCursor::pos() ); - if ( w && ( w->objectName() == QString("SettingsPanel") ) && (e->key() == Qt::Key_Escape) ) { - RightClickableWidget* panel = dynamic_cast(w); - assert(panel); - panel->getPanel()->closePanel(); + if (e->key() == Qt::Key_Escape) { + + RightClickableWidget* panel = isParentSettingsPanelRecursive(w); + if (panel) { + panel->getPanel()->closePanel(); + } } Qt::Key key = (Qt::Key)e->key(); @@ -842,4 +861,28 @@ 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 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" || + dynamic_cast(currentFocus) || + dynamic_cast(currentFocus); + + return canSetFocus; + +} \ No newline at end of file diff --git a/Gui/Histogram.cpp b/Gui/Histogram.cpp index 88b448d95f..8a0d541ad1 100644 --- a/Gui/Histogram.cpp +++ b/Gui/Histogram.cpp @@ -1439,19 +1439,7 @@ Histogram::keyPressEvent(QKeyEvent* e) 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) { + if (_imp->gui && _imp->gui->isFocusStealingPossible()) { setFocus(); } QGLWidget::enterEvent(e); diff --git a/Gui/NodeGraph30.cpp b/Gui/NodeGraph30.cpp index 4431101220..f3ada244af 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) @@ -141,20 +143,7 @@ void NodeGraph::enterEvent(QEvent* e) { 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) { + if (getGui() && getGui()->isFocusStealingPossible()) { setFocus(); } diff --git a/Gui/PropertiesBinWrapper.cpp b/Gui/PropertiesBinWrapper.cpp new file mode 100644 index 0000000000..715746bfbf --- /dev/null +++ b/Gui/PropertiesBinWrapper.cpp @@ -0,0 +1,35 @@ +/* ***** 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 "Gui/Gui.h" + +void +PropertiesBinWrapper::enterEvent(QEvent* event) +{ + if (_gui->isFocusStealingPossible()) { + setFocus(); + } + QWidget::enterEvent(event); +} \ No newline at end of file diff --git a/Gui/PropertiesBinWrapper.h b/Gui/PropertiesBinWrapper.h index dd22957ac2..d623175931 100644 --- a/Gui/PropertiesBinWrapper.h +++ b/Gui/PropertiesBinWrapper.h @@ -31,14 +31,19 @@ #include "Engine/ScriptObject.h" +class Gui; class PropertiesBinWrapper : public QWidget, public ScriptObject { + Gui* _gui; public: - PropertiesBinWrapper(QWidget* parent) + PropertiesBinWrapper(Gui* parent) : QWidget(parent) , ScriptObject() + , _gui(parent) { } + + virtual void enterEvent(QEvent* event) OVERRIDE FINAL; }; #endif // Gui_PropertiesBinWrapper_h diff --git a/Gui/ScaleSliderQWidget.cpp b/Gui/ScaleSliderQWidget.cpp index fc4e4e1a9f..efb738346e 100644 --- a/Gui/ScaleSliderQWidget.cpp +++ b/Gui/ScaleSliderQWidget.cpp @@ -118,7 +118,7 @@ ScaleSliderQWidget::ScaleSliderQWidget(double min, setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); QSize sizeh = sizeHint(); _imp->zoomCtx.setScreenSize(sizeh.width(), sizeh.height()); - setFocusPolicy(Qt::ClickFocus); + setFocusPolicy(Qt::NoFocus); } QSize diff --git a/Gui/ViewerGL.cpp b/Gui/ViewerGL.cpp index f26d64bfea..fb424f2df9 100644 --- a/Gui/ViewerGL.cpp +++ b/Gui/ViewerGL.cpp @@ -41,6 +41,7 @@ GCC_DIAG_UNUSED_PRIVATE_FIELD_OFF GCC_DIAG_UNUSED_PRIVATE_FIELD_ON #include #include +#include #include "Engine/Lut.h" #include "Engine/Node.h" @@ -2859,28 +2860,7 @@ ViewerGL::focusOutEvent(QFocusEvent* e) void ViewerGL::enterEvent(QEvent* e) { - // always running in the main thread - assert( qApp && qApp->thread() == QThread::currentThread() ); - - /* - We steal focus from all those widgets so that the user automatically - gets keyboard focus in viewer when mouse enters. - */ - 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" || - dynamic_cast(currentFocus); - - if (canSetFocus) { + if (_imp->viewerTab && _imp->viewerTab->getGui() && _imp->viewerTab->getGui()->isFocusStealingPossible()) { setFocus(); } QWidget::enterEvent(e); From d14f203db26703331bf73365184f6e56673a901e Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Tue, 29 Sep 2015 16:35:43 +0200 Subject: [PATCH 044/178] Fix assert --- Gui/NodeGraphUndoRedo.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gui/NodeGraphUndoRedo.cpp b/Gui/NodeGraphUndoRedo.cpp index 4c399a283e..f75ba96fbf 100644 --- a/Gui/NodeGraphUndoRedo.cpp +++ b/Gui/NodeGraphUndoRedo.cpp @@ -538,7 +538,7 @@ InsertNodeCommand::InsertNodeCommand(NodeGraph* graph, : ConnectCommand(graph,edge,edge->getSource(),newSrc,parent) , _inputEdge(0) { - assert(edge->getSource() && newSrc); + assert(newSrc); setText(QObject::tr("Insert node")); } From 52d064589adacfe3a4126f2f5f0437682879d3d5 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Tue, 29 Sep 2015 18:50:26 +0200 Subject: [PATCH 045/178] Factorize UI code: all panels now derive from PanelWidget --- Gui/CurveEditor.cpp | 26 ++++--- Gui/CurveEditor.h | 7 +- Gui/CurveWidget.cpp | 32 +------- Gui/CurveWidget.h | 2 - Gui/DopeSheetEditor.cpp | 17 +++-- Gui/DopeSheetEditor.h | 5 +- Gui/DopeSheetView.cpp | 9 --- Gui/DopeSheetView.h | 1 - Gui/Gui.h | 15 ++-- Gui/Gui.pro | 2 + Gui/Gui20.cpp | 14 ++-- Gui/Gui40.cpp | 10 +-- Gui/GuiAppInstance.cpp | 2 +- Gui/GuiAppWrapper.cpp | 6 +- Gui/GuiPrivate.cpp | 22 +++--- Gui/GuiPrivate.h | 2 +- Gui/Histogram.cpp | 138 +++++++++++++++++------------------ Gui/Histogram.h | 5 +- Gui/NodeGraph.cpp | 20 ++--- Gui/NodeGraph.h | 7 +- Gui/NodeGraph05.cpp | 4 +- Gui/NodeGraph10.cpp | 6 +- Gui/NodeGraph25.cpp | 10 +-- Gui/NodeGraph30.cpp | 9 +-- Gui/NodeGraph35.cpp | 10 +-- Gui/NodeGraph45.cpp | 10 +-- Gui/NodeGraphPrivate.cpp | 4 +- Gui/NodeGraphPrivate.h | 4 +- Gui/NodeGraphPrivate10.cpp | 8 +- Gui/PanelWidget.cpp | 62 ++++++++++++++++ Gui/PanelWidget.h | 69 ++++++++++++++++++ Gui/PropertiesBinWrapper.cpp | 20 +++-- Gui/PropertiesBinWrapper.h | 24 +++--- Gui/PythonPanels.cpp | 35 +++++---- Gui/PythonPanels.h | 3 +- Gui/RegisteredTabs.h | 4 +- Gui/ScriptEditor.cpp | 35 +++++---- Gui/ScriptEditor.h | 5 +- Gui/TabWidget.cpp | 111 ++++++++++++++-------------- Gui/TabWidget.h | 32 ++++---- Gui/ViewerGL.cpp | 8 -- Gui/ViewerGL.h | 1 - Gui/ViewerTab.cpp | 18 ++--- Gui/ViewerTab.h | 17 ++--- Gui/ViewerTab10.cpp | 54 ++++++++------ Gui/ViewerTab20.cpp | 46 ++++++------ Gui/ViewerTab30.cpp | 17 +---- Gui/ViewerTab40.cpp | 18 ++--- Gui/ViewerTabPrivate.cpp | 11 ++- Gui/ViewerTabPrivate.h | 8 +- 50 files changed, 545 insertions(+), 460 deletions(-) create mode 100644 Gui/PanelWidget.cpp create mode 100644 Gui/PanelWidget.h diff --git a/Gui/CurveEditor.cpp b/Gui/CurveEditor.cpp index 386dfef872..a56331e57d 100644 --- a/Gui/CurveEditor.cpp +++ b/Gui/CurveEditor.cpp @@ -103,8 +103,6 @@ class CurveEditorTreeWidget : public QTreeWidget struct CurveEditorPrivate { - Gui* gui; - std::list nodes; std::list rotos; QVBoxLayout* mainLayout; @@ -130,9 +128,8 @@ struct CurveEditorPrivate boost::weak_ptr selectedKnobCurve; - CurveEditorPrivate(Gui* gui) - : gui(gui) - , nodes() + CurveEditorPrivate() + : nodes() , rotos() , mainLayout(0) , splitter(0) @@ -163,8 +160,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 +894,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(); } } @@ -1542,6 +1539,13 @@ CurveEditor::keyPressEvent(QKeyEvent* e) } } +void +CurveEditor::enterEvent(QEvent* e) +{ + enterEventBase(); + QWidget::enterEvent(e); +} + boost::shared_ptr CurveEditor::getSelectedCurve() const { @@ -1578,7 +1582,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 +1612,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..7f1ed8f33b 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 @@ -406,6 +405,8 @@ public Q_SLOTS: void onExprLineEditFinished(); private: + + virtual void enterEvent(QEvent* e) OVERRIDE FINAL; virtual void keyPressEvent(QKeyEvent* e) OVERRIDE FINAL; diff --git a/Gui/CurveWidget.cpp b/Gui/CurveWidget.cpp index cab4fb88b0..e8e29e9c9f 100644 --- a/Gui/CurveWidget.cpp +++ b/Gui/CurveWidget.cpp @@ -1346,15 +1346,7 @@ CurveWidget::keyPressEvent(QKeyEvent* e) } } // keyPressEvent -void -CurveWidget::enterEvent(QEvent* e) -{ - // always running in the main thread - if (_imp->_gui->isFocusStealingPossible()) { - setFocus(); - } - QGLWidget::enterEvent(e); -} + //struct RefreshTangent_functor{ // CurveWidgetPrivate* _imp; @@ -1688,28 +1680,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..78481144a8 100644 --- a/Gui/CurveWidget.h +++ b/Gui/CurveWidget.h @@ -211,7 +211,6 @@ public Q_SLOTS: virtual void mouseMoveEvent(QMouseEvent* 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 +233,6 @@ public Q_SLOTS: private: - bool isTabVisible() const; boost::scoped_ptr _imp; }; diff --git a/Gui/DopeSheetEditor.cpp b/Gui/DopeSheetEditor.cpp index 14575055d4..7ed26b2e1a 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* @@ -208,3 +206,8 @@ DopeSheetEditor::keyPressEvent(QKeyEvent* e) } } +void DopeSheetEditor::enterEvent(QEvent *e) +{ + enterEventBase(); + QWidget::enterEvent(e); +} diff --git a/Gui/DopeSheetEditor.h b/Gui/DopeSheetEditor.h index 0aecdf7ac4..097a95b204 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: @@ -98,6 +98,7 @@ class DopeSheetEditor : public QWidget, public ScriptObject private: + virtual void enterEvent(QEvent *e) OVERRIDE FINAL; virtual void keyPressEvent(QKeyEvent* e) OVERRIDE FINAL; boost::scoped_ptr _imp; diff --git a/Gui/DopeSheetView.cpp b/Gui/DopeSheetView.cpp index 2ba63eb729..f6cb0f6a8a 100644 --- a/Gui/DopeSheetView.cpp +++ b/Gui/DopeSheetView.cpp @@ -3435,16 +3435,7 @@ DopeSheetView::wheelEvent(QWheelEvent *e) } } -void DopeSheetView::enterEvent(QEvent *e) -{ - running_in_main_thread(); - if (_imp->gui && _imp->gui->isFocusStealingPossible()) { - setFocus(); - } - - QGLWidget::enterEvent(e); -} void DopeSheetView::focusInEvent(QFocusEvent *e) { diff --git a/Gui/DopeSheetView.h b/Gui/DopeSheetView.h index 28166fb117..29143f675d 100644 --- a/Gui/DopeSheetView.h +++ b/Gui/DopeSheetView.h @@ -155,7 +155,6 @@ 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; diff --git a/Gui/Gui.h b/Gui/Gui.h index c2a5e227b3..d88996683b 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; @@ -296,8 +297,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); @@ -321,10 +322,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. @@ -353,9 +354,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; diff --git a/Gui/Gui.pro b/Gui/Gui.pro index 1bbabf7a2e..edc9666a29 100644 --- a/Gui/Gui.pro +++ b/Gui/Gui.pro @@ -160,6 +160,7 @@ SOURCES += \ NodeGui.cpp \ NodeGuiSerialization.cpp \ NodeSettingsPanel.cpp \ + PanelWidget.cpp \ PickKnobDialog.cpp \ PreferencesPanel.cpp \ ProjectGui.cpp \ @@ -292,6 +293,7 @@ HEADERS += \ NodeGui.h \ NodeGuiSerialization.h \ NodeSettingsPanel.h \ + PanelWidget.h \ PickKnobDialog.h \ PreferencesPanel.h \ ProjectGui.h \ diff --git a/Gui/Gui20.cpp b/Gui/Gui20.cpp index eeeea39293..b8f5f96159 100644 --- a/Gui/Gui20.cpp +++ b/Gui/Gui20.cpp @@ -242,7 +242,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 +251,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 +369,7 @@ Gui::addViewerTab(ViewerTab* tab, } void -Gui::registerTab(QWidget* tab, +Gui::registerTab(PanelWidget* tab, ScriptObject* obj) { std::string name = obj->getScriptName(); @@ -381,7 +381,7 @@ Gui::registerTab(QWidget* tab, } void -Gui::unregisterTab(QWidget* tab) +Gui::unregisterTab(PanelWidget* tab) { for (RegisteredTabs::iterator it = _imp->_registeredTabs.begin(); it != _imp->_registeredTabs.end(); ++it) { if (it->second.first == tab) { @@ -509,7 +509,7 @@ Gui::removeViewerTab(ViewerTab* tab, if ( it != _imp->_viewerTabs.end() ) { _imp->_viewerTabs.erase(it); } - tab->notifyAppClosing(); + tab->notifyGuiClosingPublic(); tab->deleteLater(); } } @@ -685,7 +685,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 +699,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..fc3880247a 100644 --- a/Gui/Gui40.cpp +++ b/Gui/Gui40.cpp @@ -83,20 +83,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 +512,7 @@ Gui::getPropertiesLayout() const } void -Gui::appendTabToDefaultViewerPane(QWidget* tab, +Gui::appendTabToDefaultViewerPane(PanelWidget* tab, ScriptObject* obj) { TabWidget* viewerAnchor = getAnchor(); diff --git a/Gui/GuiAppInstance.cpp b/Gui/GuiAppInstance.cpp index 1cdb6e5dde..f4fb103ecd 100644 --- a/Gui/GuiAppInstance.cpp +++ b/Gui/GuiAppInstance.cpp @@ -179,7 +179,7 @@ GuiAppInstance::~GuiAppInstance() QCoreApplication::processEvents(); //#ifndef __NATRON_WIN32__ - _imp->_gui->getNodeGraph()->discardGuiPointer(); + _imp->_gui->getNodeGraph()->notifyGuiClosingPublic(); _imp->_gui->deleteLater(); _imp->_gui = 0; _imp.reset(); 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/GuiPrivate.cpp b/Gui/GuiPrivate.cpp index 4988fb4ef1..68b8d7957b 100644 --- a/Gui/GuiPrivate.cpp +++ b/Gui/GuiPrivate.cpp @@ -268,10 +268,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 +290,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 diff --git a/Gui/GuiPrivate.h b/Gui/GuiPrivate.h index 9eecd93338..96f7c812d8 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. diff --git a/Gui/Histogram.cpp b/Gui/Histogram.cpp index 8a0d541ad1..748bbcb765 100644 --- a/Gui/Histogram.cpp +++ b/Gui/Histogram.cpp @@ -79,94 +79,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 +269,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 +400,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 +455,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 +562,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 +612,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(); @@ -1439,9 +1437,7 @@ Histogram::keyPressEvent(QKeyEvent* e) void Histogram::enterEvent(QEvent* e) { - if (_imp->gui && _imp->gui->isFocusStealingPossible()) { - setFocus(); - } + enterEventBase(); QGLWidget::enterEvent(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/NodeGraph.cpp b/Gui/NodeGraph.cpp index 979bf51161..5a36cad34b 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) ); @@ -178,7 +178,7 @@ NodeGraph::NodeGraph(Gui* gui, _imp->_menu = new Natron::Menu(this); //_imp->_menu->setFont( QFont(appFont,appFontSize) ); - boost::shared_ptr timeline = _imp->_gui->getApp()->getTimeLine(); + boost::shared_ptr timeline = getGui()->getApp()->getTimeLine(); QObject::connect( timeline.get(),SIGNAL( frameChanged(SequenceTime,int) ), this,SLOT( onTimeChanged(SequenceTime,int) ) ); QObject::connect( timeline.get(),SIGNAL( frameAboutToChange() ), this,SLOT( onTimelineTimeAboutToChange() ) ); } @@ -196,7 +196,7 @@ NodeGraph::~NodeGraph() (*it)->discardGraphPointer(); } - if (_imp->_gui) { + if (getGui()) { QGraphicsScene* scene = _imp->_hintInputEdge->scene(); if (scene) { scene->removeItem(_imp->_hintInputEdge); @@ -236,16 +236,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(); @@ -389,7 +383,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 615ab02889..435a855f98 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 @@ -116,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(); diff --git a/Gui/NodeGraph05.cpp b/Gui/NodeGraph05.cpp index f083d37bf7..a5aeb49f0b 100644 --- a/Gui/NodeGraph05.cpp +++ b/Gui/NodeGraph05.cpp @@ -232,7 +232,7 @@ NodeGraph::moveNodesForIdealPosition(const boost::shared_ptr &node, 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); @@ -347,7 +347,7 @@ NodeGraph::moveNodesForIdealPosition(const boost::shared_ptr &node, 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..9602dc69f9 100644 --- a/Gui/NodeGraph10.cpp +++ b/Gui/NodeGraph10.cpp @@ -195,7 +195,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 +207,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); diff --git a/Gui/NodeGraph25.cpp b/Gui/NodeGraph25.cpp index d259f33d50..daa0160dd8 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) { @@ -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) { @@ -243,9 +243,9 @@ NodeGraph::keyPressEvent(QKeyEvent* e) QKeyEvent* ev = new QKeyEvent(QEvent::KeyPress, key, modifiers); QCoreApplication::postEvent(parentWidget(),ev); } else if ( isKeybind(kShortcutGroupNodegraph, kShortcutIDActionGraphCreateReader, modifiers, key) ) { - _imp->_gui->createReader(); + 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) ) { diff --git a/Gui/NodeGraph30.cpp b/Gui/NodeGraph30.cpp index f3ada244af..9adeb7d1da 100644 --- a/Gui/NodeGraph30.cpp +++ b/Gui/NodeGraph30.cpp @@ -84,7 +84,7 @@ NodeGraph::connectCurrentViewerToSelection(int inputNB) if ( !lastUsedViewer ) { - _imp->_gui->getApp()->createNode( CreateNodeArgs(PLUGINID_NATRON_VIEWER, + getGui()->getApp()->createNode( CreateNodeArgs(PLUGINID_NATRON_VIEWER, "", -1,-1, true, @@ -142,11 +142,8 @@ NodeGraph::connectCurrentViewerToSelection(int inputNB) void NodeGraph::enterEvent(QEvent* e) { + enterEventBase(); QGraphicsView::enterEvent(e); - if (getGui() && getGui()->isFocusStealingPossible()) { - setFocus(); - } - _imp->_nodeCreationShortcutEnabled = true; } @@ -410,7 +407,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..ddeec726f3 100644 --- a/Gui/NodeGraph45.cpp +++ b/Gui/NodeGraph45.cpp @@ -141,7 +141,7 @@ NodeGraph::onTimelineTimeAboutToChange() { assert(QThread::currentThread() == qApp->thread()); _imp->wasLaskUserSeekDuringPlayback = false; - 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) { RenderEngine* engine = (*it)->getInternalNode()->getRenderEngine(); _imp->wasLaskUserSeekDuringPlayback |= engine->abortRendering(true,true); @@ -155,10 +155,10 @@ NodeGraph::onTimeChanged(SequenceTime time, assert(QThread::currentThread() == qApp->thread()); std::vector viewers; - if (!_imp->_gui) { + if (!getGui()) { return; } - boost::shared_ptr project = _imp->_gui->getApp()->getProject(); + boost::shared_ptr project = getGui()->getApp()->getProject(); ///Refresh all knobs at the current time for (std::list >::iterator it = _imp->_nodes.begin(); it != _imp->_nodes.end(); ++it) { @@ -223,8 +223,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..2338207218 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() diff --git a/Gui/NodeGraphPrivate.h b/Gui/NodeGraphPrivate.h index 6da5b7c2c4..5bb7b76bc2 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; @@ -230,8 +229,7 @@ struct NodeGraphPrivate ViewerTab* lastSelectedViewer; bool wasLaskUserSeekDuringPlayback; - NodeGraphPrivate(Gui* gui, - NodeGraph* p, + NodeGraphPrivate(NodeGraph* p, const boost::shared_ptr& group); void resetAllClipboards(); 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/PanelWidget.cpp b/Gui/PanelWidget.cpp new file mode 100644 index 0000000000..f5a183eb43 --- /dev/null +++ b/Gui/PanelWidget.cpp @@ -0,0 +1,62 @@ +/* ***** 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 + +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(); +} + +void +PanelWidget::enterEventBase() +{ + if (_gui && _gui->isFocusStealingPossible()) { + _thisWidget->setFocus(); + } + +} \ No newline at end of file diff --git a/Gui/PanelWidget.h b/Gui/PanelWidget.h new file mode 100644 index 0000000000..a18680f3a3 --- /dev/null +++ b/Gui/PanelWidget.h @@ -0,0 +1,69 @@ +/* ***** 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 + +CLANG_DIAG_OFF(deprecated) +CLANG_DIAG_OFF(uninitialized) +#include +CLANG_DIAG_ON(deprecated) +CLANG_DIAG_ON(uninitialized) + +#include "Global/Macros.h" + +#include "Engine/ScriptObject.h" + +class Gui; +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; + } + +protected: + + virtual void notifyGuiClosing() {} + + /** + * @brief To be called in the enterEvent handler of all derived classes. + **/ + void enterEventBase(); + +}; + +#endif // PANELWIDGET_H diff --git a/Gui/PropertiesBinWrapper.cpp b/Gui/PropertiesBinWrapper.cpp index 715746bfbf..6fb37802ad 100644 --- a/Gui/PropertiesBinWrapper.cpp +++ b/Gui/PropertiesBinWrapper.cpp @@ -25,11 +25,21 @@ #include "PropertiesBinWrapper.h" #include "Gui/Gui.h" +PropertiesBinWrapper::PropertiesBinWrapper(Gui* parent) +: QWidget(parent) +, PanelWidget(this,parent) +{ + +} + +PropertiesBinWrapper::~PropertiesBinWrapper() +{ + +} + void -PropertiesBinWrapper::enterEvent(QEvent* event) +PropertiesBinWrapper::enterEvent(QEvent* e) { - if (_gui->isFocusStealingPossible()) { - setFocus(); - } - QWidget::enterEvent(event); + enterEventBase(); + QWidget::enterEvent(e); } \ No newline at end of file diff --git a/Gui/PropertiesBinWrapper.h b/Gui/PropertiesBinWrapper.h index d623175931..bf3b4af944 100644 --- a/Gui/PropertiesBinWrapper.h +++ b/Gui/PropertiesBinWrapper.h @@ -25,25 +25,21 @@ #include // ***** END PYTHON BLOCK ***** -#include -#include "Global/Macros.h" +#include "Gui/PanelWidget.h" -#include "Engine/ScriptObject.h" - -class Gui; -class PropertiesBinWrapper : public QWidget, public ScriptObject +class PropertiesBinWrapper : public QWidget, public PanelWidget { - Gui* _gui; + public: - PropertiesBinWrapper(Gui* parent) - : QWidget(parent) - , ScriptObject() - , _gui(parent) - { - } - virtual void enterEvent(QEvent* event) OVERRIDE FINAL; + PropertiesBinWrapper(Gui* parent); + + virtual ~PropertiesBinWrapper(); + +private: + + virtual void enterEvent(QEvent* e) OVERRIDE FINAL; }; #endif // Gui_PropertiesBinWrapper_h diff --git a/Gui/PythonPanels.cpp b/Gui/PythonPanels.cpp index e864f058ff..0aef0ba32f 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 @@ -467,7 +464,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 +498,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..eff5bcf87e 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,7 +101,7 @@ class PyModalDialog : public QDialog, public UserParamHolder struct PyPanelPrivate; -class PyPanel : public QWidget, public UserParamHolder, public ScriptObject +class PyPanel : public QWidget, public UserParamHolder, public PanelWidget { Q_OBJECT 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/ScriptEditor.cpp b/Gui/ScriptEditor.cpp index 24b5c7dad7..82ac602d34 100644 --- a/Gui/ScriptEditor.cpp +++ b/Gui/ScriptEditor.cpp @@ -51,8 +51,6 @@ struct ScriptEditorPrivate { - Gui* gui; - QVBoxLayout* mainLayout; QWidget* buttonsContainer; @@ -77,9 +75,8 @@ struct ScriptEditorPrivate QMutex autoSavedScriptMutex; QString autoSavedScript; - ScriptEditorPrivate(Gui* gui) - : gui(gui) - , mainLayout(0) + ScriptEditorPrivate() + : mainLayout(0) , buttonsContainer(0) , buttonsContainerLayout(0) , undoB(0) @@ -103,7 +100,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); @@ -298,13 +296,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 +323,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 +348,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); @@ -505,3 +503,10 @@ ScriptEditor::printAutoDeclaredVariable(const QString& str) cpy.replace("\n", "
"); _imp->outputEdit->append("" + cpy + ""); } + +void +ScriptEditor::enterEvent(QEvent *e) +{ + enterEventBase(); + QWidget::enterEvent(e); +} \ No newline at end of file diff --git a/Gui/ScriptEditor.h b/Gui/ScriptEditor.h index 3441eedd04..cc30f9cc5f 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 @@ -95,6 +95,7 @@ public Q_SLOTS: private: + virtual void enterEvent(QEvent *e) OVERRIDE FINAL; virtual void keyPressEvent(QKeyEvent* e) OVERRIDE FINAL; boost::scoped_ptr _imp; diff --git a/Gui/TabWidget.cpp b/Gui/TabWidget.cpp index 080fc7c52e..40f64aebd2 100644 --- a/Gui/TabWidget.cpp +++ b/Gui/TabWidget.cpp @@ -185,7 +185,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 +193,7 @@ struct TabWidgetPrivate Button* leftCornerButton; Button* floatButton; Button* closeButton; - QWidget* currentWidget; + PanelWidget* currentWidget; bool drawDropRect; bool fullScreen; bool isAnchor; @@ -229,8 +229,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 +322,7 @@ TabWidget::count() const return _imp->tabs.size(); } -QWidget* +PanelWidget* TabWidget::tabAt(int index) const { QMutexLocker l(&_imp->tabWidgetStateMutex); @@ -333,7 +333,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 +640,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 +649,7 @@ TabWidget::closePane() } } else { while (count() > 0) { - QWidget* w = tabAt(0); + PanelWidget* w = tabAt(0); if (w) { removeTab( w, true ); } @@ -763,7 +763,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 +775,7 @@ TabWidget::getTabLabel(QWidget* tab) const } void -TabWidget::setTabLabel(QWidget* tab, +TabWidget::setTabLabel(PanelWidget* tab, const QString & name) { QMutexLocker l(&_imp->tabWidgetStateMutex); @@ -799,7 +799,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 +815,7 @@ TabWidget::floatCurrentWidget() void TabWidget::closeCurrentWidget() { - QWidget* w = currentWidget(); + PanelWidget* w = currentWidget(); if (!w) { return; } @@ -827,7 +827,7 @@ void TabWidget::closeTab(int index) { - QWidget *w = tabAt(index); + PanelWidget *w = tabAt(index); if (!w) { return; } @@ -945,14 +945,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 +997,7 @@ TabWidget::appendTab(const QIcon & icon, void TabWidget::insertTab(int index, const QIcon & icon, - QWidget* widget, + PanelWidget* widget, ScriptObject* object) { @@ -1014,8 +1014,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,16 +1032,16 @@ 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) { @@ -1076,6 +1078,7 @@ TabWidget::removeTab(int index,bool userAction) _imp->removeTabToPython(tab, obj->getScriptName()); + QWidget* w = tab->getWidget(); if (userAction) { if (isViewer) { @@ -1088,22 +1091,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 +1121,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 +1151,47 @@ TabWidget::makeCurrentTab(int index) if (_imp->modifyingTabBar) { return; } - QWidget* tab = tabAt(index); + PanelWidget* tab = tabAt(index); if (!tab) { return; } + QWidget* tabW = tab->getWidget(); { 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; } - 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; } - -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 +1253,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) { @@ -1411,7 +1416,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 +1496,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 +1594,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 @@ -1726,11 +1731,11 @@ TabWidget::keyPressEvent (QKeyEvent* e) } 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.*/ @@ -1798,7 +1803,7 @@ TabWidget::getTabScriptNames() const return ret; } -QWidget* +PanelWidget* TabWidget::currentWidget() const { QMutexLocker l(&_imp->tabWidgetStateMutex); @@ -1807,7 +1812,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; @@ -1944,7 +1949,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 +1975,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 +2006,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..13d6b68336 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 @@ -179,51 +179,51 @@ 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(QWidget** w,ScriptObject** obj) const; + void currentWidget(PanelWidget** w,ScriptObject** obj) const; /** * @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: @@ -266,7 +266,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/ViewerGL.cpp b/Gui/ViewerGL.cpp index fb424f2df9..f939582ddd 100644 --- a/Gui/ViewerGL.cpp +++ b/Gui/ViewerGL.cpp @@ -2857,14 +2857,6 @@ ViewerGL::focusOutEvent(QFocusEvent* e) QGLWidget::focusOutEvent(e); } -void -ViewerGL::enterEvent(QEvent* e) -{ - if (_imp->viewerTab && _imp->viewerTab->getGui() && _imp->viewerTab->getGui()->isFocusStealingPossible()) { - setFocus(); - } - QWidget::enterEvent(e); -} void ViewerGL::leaveEvent(QEvent* e) diff --git a/Gui/ViewerGL.h b/Gui/ViewerGL.h index af3b4bba5e..f3ffc65e68 100644 --- a/Gui/ViewerGL.h +++ b/Gui/ViewerGL.h @@ -418,7 +418,6 @@ 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; diff --git a/Gui/ViewerTab.cpp b/Gui/ViewerTab.cpp index e68bb20ac9..73258495e7 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); @@ -395,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); @@ -436,7 +436,7 @@ ViewerTab::ViewerTab(const std::list & existingRotoNodes, _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))); @@ -478,7 +478,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(); @@ -692,7 +692,7 @@ ViewerTab::ViewerTab(const std::list & existingRotoNodes, "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); @@ -883,7 +883,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))); @@ -977,7 +977,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..3d24c91d4d 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 @@ -446,6 +440,7 @@ public Q_SLOTS: virtual bool eventFilter(QObject *target, QEvent* e) OVERRIDE FINAL; virtual void keyPressEvent(QKeyEvent* e) OVERRIDE FINAL; + virtual void enterEvent(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..f29c3e5dac 100644 --- a/Gui/ViewerTab10.cpp +++ b/Gui/ViewerTab10.cpp @@ -122,7 +122,7 @@ ViewerTab::updateViewsMenu(int count) } else { _imp->viewsComboBox->setCurrentIndex(0); } - _imp->gui->updateViewsActions(count); + getGui()->updateViewsActions(count); } void @@ -237,7 +237,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 +253,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 +272,7 @@ ViewerTab::abortRendering() void ViewerTab::onEngineStarted(bool forward) { - if (!_imp->gui) { + if (!getGui()) { return; } @@ -286,15 +286,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 +307,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 +317,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 +379,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); } } @@ -427,7 +427,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 +440,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,6 +478,14 @@ ViewerTab::previousLayer() _imp->layerChoice->setCurrentIndex(currentIndex); } +void +ViewerTab::enterEvent(QEvent* e) +{ + enterEventBase(); + QWidget::enterEvent(e); +} + + void ViewerTab::keyPressEvent(QKeyEvent* e) { @@ -603,10 +611,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) ) { @@ -711,11 +719,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..c85c30cae7 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 @@ -265,7 +265,7 @@ ViewerTab::notifyOverlaysPenDown(double scaleX, QMouseEvent* e) { - if ( !_imp->app || _imp->app->isClosing() ) { + if ( !getGui()->getApp() || getGui()->getApp()->isClosing() ) { return false; } @@ -309,7 +309,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 +320,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 +395,7 @@ ViewerTab::notifyOverlaysPenMotion_internal(const boost::shared_ptrapp->getTimeLine()->currentFrame(); + double time = getGui()->getApp()->getTimeLine()->currentFrame(); #ifdef NATRON_TRANSFORM_AFFECTS_OVERLAYS int view = getCurrentView(); @@ -490,7 +490,7 @@ ViewerTab::notifyOverlaysPenMotion(double scaleX, { bool didSomething = false; - if ( !_imp->app || _imp->app->isClosing() ) { + if ( !getGui()->getApp() || getGui()->getApp()->isClosing() ) { return false; } @@ -540,7 +540,7 @@ ViewerTab::notifyOverlaysPenUp(double scaleX, { bool didSomething = false; - if ( !_imp->app || _imp->app->isClosing() ) { + if ( !getGui()->getApp() || getGui()->getApp()->isClosing() ) { return false; } @@ -553,7 +553,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(); @@ -643,7 +643,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,7 +702,7 @@ ViewerTab::notifyOverlaysKeyDown(double scaleX, { bool didSomething = false; - if ( !_imp->app || _imp->app->isClosing() ) { + if ( !getGui()->getApp() || getGui()->getApp()->isClosing() ) { return false; } @@ -750,14 +750,14 @@ 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 +813,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 +865,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 +910,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 +952,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..8125936335 100644 --- a/Gui/ViewerTab30.cpp +++ b/Gui/ViewerTab30.cpp @@ -288,12 +288,6 @@ ViewerTab::discardInternalNodePointer() _imp->viewerNode = 0; } -Gui* -ViewerTab::getGui() const -{ - return _imp->gui; -} - void ViewerTab::onAutoContrastChanged(bool b) @@ -431,7 +425,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 +476,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 +679,7 @@ ViewerTab::getRotoGuiSharedData(NodeGui* node) const void ViewerTab::onRotoEvaluatedForThisViewer() { - _imp->gui->onViewerRotoEvaluated(this); + getGui()->onViewerRotoEvaluated(this); } void @@ -721,12 +715,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(); } diff --git a/Gui/ViewerTab40.cpp b/Gui/ViewerTab40.cpp index 4886a591ab..6552e9bb8b 100644 --- a/Gui/ViewerTab40.cpp +++ b/Gui/ViewerTab40.cpp @@ -557,8 +557,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()); @@ -1093,11 +1093,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 +1112,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 +1179,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..db700a6213 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) @@ -130,7 +130,6 @@ ViewerTabPrivate::ViewerTabPrivate(Gui* gui, , inputNamesMap() , compOperatorMutex() , compOperator(eViewerCompositingOperatorNone) -, gui(gui) , viewerNode(node) , visibleToolbarsMutex() , infobarVisible(true) @@ -332,7 +331,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..79213b0035 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; @@ -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 @@ -205,8 +206,7 @@ struct ViewerTabPrivate //The last node that took the penDown/motion/keyDown/keyRelease etc... boost::weak_ptr lastOverlayNode; - 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 From fabfa1c1bc2e326bcc568e2b1920b9a6d37b7b1b Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Tue, 29 Sep 2015 18:57:43 +0200 Subject: [PATCH 046/178] clip roi --- Engine/EffectInstanceRenderRoI.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Engine/EffectInstanceRenderRoI.cpp b/Engine/EffectInstanceRenderRoI.cpp index ce173e440b..8315ff0cf3 100644 --- a/Engine/EffectInstanceRenderRoI.cpp +++ b/Engine/EffectInstanceRenderRoI.cpp @@ -206,15 +206,18 @@ EffectInstance::convertPlanesFormatsIfNeeded(const AppInstance* app, 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( roi, + inputImage->convertToFormatAlpha0( clippedRoi, app->getDefaultColorSpaceForBitDepth(inputImage->getBitDepth()), app->getDefaultColorSpaceForBitDepth(targetDepth), -1, false, unPremultIfNeeded, tmp.get() ); } else { - inputImage->convertToFormat( roi, + inputImage->convertToFormat( clippedRoi, app->getDefaultColorSpaceForBitDepth(inputImage->getBitDepth()), app->getDefaultColorSpaceForBitDepth(targetDepth), -1, false, unPremultIfNeeded, tmp.get() ); From 6c609c2a67feff2879f12ba0ac7cd951c4c98539 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Tue, 29 Sep 2015 19:13:03 +0200 Subject: [PATCH 047/178] Bug fix --- Engine/Node.cpp | 36 ++++++++++++++++++++++++++++++++++-- Engine/ViewerInstance.cpp | 7 +------ 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/Engine/Node.cpp b/Engine/Node.cpp index 5c320da74c..f63cf3750d 100644 --- a/Engine/Node.cpp +++ b/Engine/Node.cpp @@ -3607,8 +3607,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); } @@ -3622,6 +3626,10 @@ Node::connectInput(const boost::shared_ptr & input, _imp->runInputChangedCallback(inputNumber, inputChangedCB); } + if (mustCallEnd) { + endInputEdition(true); + } + return true; } @@ -3694,7 +3702,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); } @@ -3709,7 +3720,9 @@ Node::replaceInput(const boost::shared_ptr& input,int inputNumber) _imp->runInputChangedCallback(inputNumber, inputChangedCB); } - + if (mustCallEnd) { + endInputEdition(true); + } return true; } @@ -3781,11 +3794,13 @@ Node::switchInput0And1() } Q_EMIT inputChanged(inputAIndex); Q_EMIT inputChanged(inputBIndex); + bool mustCallEnd = false; if (!useGuiInputs) { beginInputEdition(); + mustCallEnd = true; onInputChanged(inputAIndex); onInputChanged(inputBIndex); - endInputEdition(true); + } computeHash(); @@ -3797,6 +3812,10 @@ Node::switchInput0And1() _imp->ifGroupForceHashChangeOfInputs(); + + if (mustCallEnd) { + endInputEdition(true); + } } // switchInput0And1 @@ -3886,7 +3905,10 @@ Node::disconnectInput(int inputNumber) } Q_EMIT inputChanged(inputNumber); + bool mustCallEnd = false; if (!useGuiValues) { + beginInputEdition(); + mustCallEnd= true; onInputChanged(inputNumber); } computeHash(); @@ -3897,6 +3919,9 @@ Node::disconnectInput(int inputNumber) if (!inputChangedCB.empty()) { _imp->runInputChangedCallback(inputNumber, inputChangedCB); } + if (mustCallEnd) { + endInputEdition(true); + } return inputNumber; } @@ -3944,7 +3969,10 @@ Node::disconnectInput(Node* input) } input->disconnectOutput(useGuiValues,this); Q_EMIT inputChanged(found); + bool mustCallEnd = false; if (!useGuiValues) { + beginInputEdition(); + mustCallEnd = true; onInputChanged(found); } computeHash(); @@ -3956,6 +3984,10 @@ Node::disconnectInput(Node* input) _imp->runInputChangedCallback(found, inputChangedCB); } + if (mustCallEnd) { + endInputEdition(true); + } + return found; } diff --git a/Engine/ViewerInstance.cpp b/Engine/ViewerInstance.cpp index bd6d8a4928..df436c8faa 100644 --- a/Engine/ViewerInstance.cpp +++ b/Engine/ViewerInstance.cpp @@ -1124,12 +1124,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; } From 11a5f116ca1011456a85f26acf92bac84335c413 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Wed, 30 Sep 2015 09:38:20 +0200 Subject: [PATCH 048/178] Fix a bug where loading a PyPlug would not load some user modifications such as node label changes etc.... --- Engine/AppInstance.cpp | 5 +---- Engine/Node.cpp | 19 +++++++++++++++-- Engine/Node.h | 7 +++++++ Engine/NodeGroupSerialization.cpp | 34 +++++++++++++++++++------------ 4 files changed, 46 insertions(+), 19 deletions(-) diff --git a/Engine/AppInstance.cpp b/Engine/AppInstance.cpp index b60cda406c..1e7d05e541 100644 --- a/Engine/AppInstance.cpp +++ b/Engine/AppInstance.cpp @@ -678,10 +678,7 @@ AppInstance::createNodeFromPythonModule(Natron::Plugin* plugin, node = containerNode; } if (requestedByLoad) { - containerNode->loadKnobs(serialization); - if (!serialization.isNull() && !serialization.getUserPages().empty()) { - containerNode->getLiveInstance()->refreshKnobs(); - } + containerNode->loadSerializationForPyPlug(serialization); node = containerNode; } diff --git a/Engine/Node.cpp b/Engine/Node.cpp index f63cf3750d..6980be648e 100644 --- a/Engine/Node.cpp +++ b/Engine/Node.cpp @@ -731,11 +731,13 @@ Node::load(const std::string & parentMultiInstanceName, _imp->nodeCreated = true; - if (!getApp()->getProject()->isLoadingProject() && !getApp()->isCreatingPythonGroup()) { + 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 @@ -1335,6 +1337,19 @@ Node::setValuesFromSerialization(const std::listliveInstance->refreshKnobs(); + } + + //Also restore script name/label (overwriting what is written in the PyPlug) + setScriptName_no_error_check(serialization.getNodeScriptName()); + setLabel(serialization.getNodeLabel()); +} + void Node::loadKnobs(const NodeSerialization & serialization,bool updateKnobGui) { diff --git a/Engine/Node.h b/Engine/Node.h index 88029fe8d6..c998cca424 100644 --- a/Engine/Node.h +++ b/Engine/Node.h @@ -131,6 +131,13 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON bool isPartOfProject, const QString& fixedName, const std::list >& paramValues); + + + /* + * @brief To be called on all nodes inside a PyPlug and the group node itself to load remaining user changes made on top of + * the default configuration that is coded in the Python script. This may for example include + */ + void loadSerializationForPyPlug(const NodeSerialization & serialization); ///called by load() and OfxEffectInstance, do not call this! void loadKnobs(const NodeSerialization & serialization,bool updateKnobGui = false); diff --git a/Engine/NodeGroupSerialization.cpp b/Engine/NodeGroupSerialization.cpp index fc3f19a56f..26eca37616 100644 --- a/Engine/NodeGroupSerialization.cpp +++ b/Engine/NodeGroupSerialization.cpp @@ -125,7 +125,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,14 +210,14 @@ 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); + n->loadSerializationForPyPlug(**it); } } @@ -277,7 +277,7 @@ NodeCollectionSerialization::restoreFromSerialization(const std::list< boost::sh NodeCollectionSerialization::restoreFromSerialization(children, group, true, moduleUpdatesProcessed, hasProjectAWriter); } } - } + } // for (std::list< boost::shared_ptr >::const_iterator it = serializedNodes.begin(); it != serializedNodes.end(); ++it) { group->getApplication()->updateProjectLoadStatus(QObject::tr("Restoring graph links in group: ") + groupName); @@ -330,8 +330,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 +348,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 +369,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 +388,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]"; + } } } } From d9eb2e93faf9c29b96992942e0de2e01fa55ed89 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Wed, 30 Sep 2015 09:55:00 +0200 Subject: [PATCH 049/178] When closing a PyPlug tab, if the parent graph is in the same pane, set it active instead of index -1 --- Gui/TabWidget.cpp | 45 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/Gui/TabWidget.cpp b/Gui/TabWidget.cpp index 40f64aebd2..5bbb9f391a 100644 --- a/Gui/TabWidget.cpp +++ b/Gui/TabWidget.cpp @@ -1048,6 +1048,39 @@ TabWidget::removeTab(int index,bool userAction) 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; + } + } + } + } + } { QMutexLocker l(&_imp->tabWidgetStateMutex); @@ -1057,7 +1090,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; @@ -1072,9 +1111,7 @@ 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()); From 895d042ff3d22742eac15c749f86b38245a9a7ad Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Wed, 30 Sep 2015 10:29:04 +0200 Subject: [PATCH 050/178] TabBar: add mouseOverFocus and clickFocus Q_PROPERTY --- Gui/TabWidget.cpp | 31 ++++++++++++++++++++++++++----- Gui/TabWidget.h | 20 +++++++++++++++++++- 2 files changed, 45 insertions(+), 6 deletions(-) diff --git a/Gui/TabWidget.cpp b/Gui/TabWidget.cpp index 5bbb9f391a..349958f565 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] @@ -1313,16 +1314,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) diff --git a/Gui/TabWidget.h b/Gui/TabWidget.h index 13d6b68336..a911c766f5 100644 --- a/Gui/TabWidget.h +++ b/Gui/TabWidget.h @@ -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 From 64b1ea5f4a77bfe7a4826e314f97fd99a5d541d8 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Wed, 30 Sep 2015 15:36:41 +0200 Subject: [PATCH 051/178] Implement Mouse over and Click focus context on panels. Mouse overed panel is with a clear orange and the click focus is in dark orange. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Click focus defines the main panel where keyboard events are registered and mouse-over focus allows you to temporarily override that focus. If the mouse-over focus is centered on a panel that doesn't recognize a particular keyboard event, the event falls back to the click focus panel. Some input widgets remain focus no matter what (text input, spinbox etc…) --- CrashReporter/CrashDialog.cpp | 3 +- Gui/CurveEditor.cpp | 20 +++++ Gui/CurveEditor.h | 3 + Gui/CurveEditorUndoRedo.cpp | 4 +- Gui/CurveGui.cpp | 55 ++++++------ Gui/CurveGui.h | 6 +- Gui/CurveWidget.cpp | 56 +++++++----- Gui/CurveWidget.h | 1 + Gui/DopeSheet.cpp | 6 +- Gui/DopeSheet.h | 3 +- Gui/DopeSheetEditor.cpp | 43 +++++++++ Gui/DopeSheetEditor.h | 3 + Gui/DopeSheetView.cpp | 57 +----------- Gui/DopeSheetView.h | 4 +- Gui/Gui.h | 10 ++- Gui/Gui20.cpp | 13 ++- Gui/Gui50.cpp | 72 ++++++++++----- Gui/GuiPrivate.cpp | 2 + Gui/GuiPrivate.h | 5 ++ Gui/Histogram.cpp | 21 +++-- Gui/NodeGraph10.cpp | 3 + Gui/NodeGraph25.cpp | 16 ++-- Gui/NodeGraph30.cpp | 1 + Gui/PanelWidget.cpp | 66 ++++++++++++++ Gui/PanelWidget.h | 13 +++ Gui/PropertiesBinWrapper.cpp | 7 ++ Gui/PropertiesBinWrapper.h | 1 + Gui/PythonPanels.cpp | 27 ++++++ Gui/PythonPanels.h | 5 +- Gui/Resources/Stylesheets/mainstyle.qss | 19 ++-- Gui/ScriptEditor.cpp | 24 ++++- Gui/ScriptEditor.h | 2 + Gui/TabWidget.cpp | 113 +++++++++++++++++++----- Gui/TabWidget.h | 5 ++ Gui/ViewerGL.cpp | 48 +--------- Gui/ViewerGL.h | 1 - Gui/ViewerTab.h | 3 + Gui/ViewerTab10.cpp | 54 +++++++++-- Gui/ViewerTab40.cpp | 6 ++ 39 files changed, 563 insertions(+), 238 deletions(-) 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/Gui/CurveEditor.cpp b/Gui/CurveEditor.cpp index a56331e57d..66433aabeb 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; @@ -1532,6 +1534,11 @@ RotoCurveEditorContext::findElement(KnobGui* knob,int dimension) const void CurveEditor::keyPressEvent(QKeyEvent* e) { + //Qt::KeyboardModifiers modifiers = e->modifiers(); + //Qt::Key key = (Qt::Key)e->key(); + + onInputEventCalled(); + if (e->key() == Qt::Key_F && modCASIsControl(e)) { _imp->filterEdit->setFocus(); } else { @@ -1546,6 +1553,19 @@ CurveEditor::enterEvent(QEvent* e) QWidget::enterEvent(e); } +void +CurveEditor::leaveEvent(QEvent* e) +{ + leaveEventBase(); + QWidget::leaveEvent(e); +} + +void +CurveEditor::onInputEventCalled() +{ + takeClickFocus(); +} + boost::shared_ptr CurveEditor::getSelectedCurve() const { diff --git a/Gui/CurveEditor.h b/Gui/CurveEditor.h index 7f1ed8f33b..e83d13d352 100644 --- a/Gui/CurveEditor.h +++ b/Gui/CurveEditor.h @@ -394,6 +394,8 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON void setSelectedCurveExpression(const QString& expression); + void onInputEventCalled(); + public Q_SLOTS: void onFilterTextChanged(const QString& filter); @@ -407,6 +409,7 @@ public Q_SLOTS: 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..571f521de5 100644 --- a/Gui/CurveEditorUndoRedo.cpp +++ b/Gui/CurveEditorUndoRedo.cpp @@ -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(), Natron::eValueChangedReasonUserEdited); } else { knob->setInterpolationAtTime(Natron::eCurveChangeReasonCurveEditor, isKnobCurve->getDimension(), it->key->key.getTime(), interp, &it->key->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 e8e29e9c9f..f1fabfe85e 100644 --- a/Gui/CurveWidget.cpp +++ b/Gui/CurveWidget.cpp @@ -643,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 @@ -1282,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) @@ -1301,17 +1301,23 @@ CurveWidget::keyPressEvent(QKeyEvent* 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(); + } + 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(); @@ -1347,7 +1353,13 @@ CurveWidget::keyPressEvent(QKeyEvent* e) } // keyPressEvent - +void +CurveWidget::enterEvent(QEvent* e) +{ + setFocus(); + QGLWidget::enterEvent(e); + +} //struct RefreshTangent_functor{ // CurveWidgetPrivate* _imp; // diff --git a/Gui/CurveWidget.h b/Gui/CurveWidget.h index 78481144a8..e17da07f02 100644 --- a/Gui/CurveWidget.h +++ b/Gui/CurveWidget.h @@ -209,6 +209,7 @@ 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 focusInEvent(QFocusEvent* e) OVERRIDE FINAL; diff --git a/Gui/DopeSheet.cpp b/Gui/DopeSheet.cpp index 4977265210..d86c215509 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() { 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 7ed26b2e1a..37f44ccb60 100644 --- a/Gui/DopeSheetEditor.cpp +++ b/Gui/DopeSheetEditor.cpp @@ -201,8 +201,39 @@ 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(); } } @@ -211,3 +242,15 @@ 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 097a95b204..0468b057ff 100644 --- a/Gui/DopeSheetEditor.h +++ b/Gui/DopeSheetEditor.h @@ -95,10 +95,13 @@ class DopeSheetEditor : public QWidget, public PanelWidget 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/DopeSheetView.cpp b/Gui/DopeSheetView.cpp index f6cb0f6a8a..c11969e3de 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" @@ -3045,6 +3046,8 @@ void DopeSheetView::paintGL() void DopeSheetView::mousePressEvent(QMouseEvent *e) { running_in_main_thread(); + + _imp->model->getEditor()->onInputEventCalled(); bool didSomething = false; @@ -3444,57 +3447,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 29143f675d..187bd4f950 100644 --- a/Gui/DopeSheetView.h +++ b/Gui/DopeSheetView.h @@ -157,9 +157,7 @@ public Q_SLOTS: 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/Gui.h b/Gui/Gui.h index d88996683b..b3a100978b 100644 --- a/Gui/Gui.h +++ b/Gui/Gui.h @@ -126,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); @@ -594,6 +596,9 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON */ static bool isFocusStealingPossible(); + PanelWidget* getCurrentPanelFocus() const; + + Q_SIGNALS: @@ -718,7 +723,8 @@ public Q_SLOTS: private: - + void setCurrentPanelFocus(PanelWidget* widget); + AppInstance* openProjectInternal(const std::string & absoluteFileName) WARN_UNUSED_RETURN; void setupUi(); diff --git a/Gui/Gui20.cpp b/Gui/Gui20.cpp index b8f5f96159..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()); } @@ -383,6 +389,9 @@ Gui::registerTab(PanelWidget* tab, void 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); diff --git a/Gui/Gui50.cpp b/Gui/Gui50.cpp index f09f65a725..78d924833f 100644 --- a/Gui/Gui50.cpp +++ b/Gui/Gui50.cpp @@ -48,6 +48,11 @@ GCC_DIAG_UNUSED_PRIVATE_FIELD_ON #include #include #include +#include +#include +#include +#include +#include #include "Engine/GroupOutput.h" #include "Engine/Node.h" @@ -60,6 +65,7 @@ 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" @@ -452,20 +458,23 @@ static RightClickableWidget* isParentSettingsPanelRecursive(QWidget* w) void Gui::keyPressEvent(QKeyEvent* e) { + if (_imp->currentPanelFocusEventRecursion > 0) { + return; + } + QWidget* w = qApp->widgetAt( QCursor::pos() ); - if (e->key() == Qt::Key_Escape) { + + Qt::Key key = (Qt::Key)e->key(); + Qt::KeyboardModifiers modifiers = e->modifiers(); + + if (key == Qt::Key_Escape) { RightClickableWidget* panel = isParentSettingsPanelRecursive(w); if (panel) { panel->getPanel()->closePanel(); } - } - - Qt::Key key = (Qt::Key)e->key(); - Qt::KeyboardModifiers modifiers = e->modifiers(); - - if ( isKeybind(kShortcutGroupPlayer, kShortcutIDActionPlayerPrevious, modifiers, key) ) { + } else if ( isKeybind(kShortcutGroupPlayer, kShortcutIDActionPlayerPrevious, modifiers, key) ) { if ( getNodeGraph()->getLastSelectedViewer() ) { getNodeGraph()->getLastSelectedViewer()->previousFrame(); } @@ -530,7 +539,18 @@ Gui::keyPressEvent(QKeyEvent* e) } else if (isKeybind(kShortcutGroupGlobal, kShortcutIDActionConnectViewerToInput10, modifiers, key) ) { connectInput(9); } else { - QMainWindow::keyPressEvent(e); + if (_imp->currentPanelFocus) { + + ++_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); + } } } @@ -870,19 +890,29 @@ Gui::isFocusStealingPossible() 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" || + bool focusStealingNotPossible = + dynamic_cast(currentFocus) || + dynamic_cast(currentFocus) || + dynamic_cast(currentFocus) || + dynamic_cast(currentFocus) || + dynamic_cast(currentFocus) || dynamic_cast(currentFocus) || - dynamic_cast(currentFocus); - - return canSetFocus; + 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; } \ No newline at end of file diff --git a/Gui/GuiPrivate.cpp b/Gui/GuiPrivate.cpp index 68b8d7957b..a0bc06ad98 100644 --- a/Gui/GuiPrivate.cpp +++ b/Gui/GuiPrivate.cpp @@ -248,6 +248,8 @@ GuiPrivate::GuiPrivate(GuiAppInstance* app, , _lastEnteredTabWidget(0) , pythonCommands() , statsDialog(0) +, currentPanelFocus(0) +, currentPanelFocusEventRecursion(0) { } diff --git a/Gui/GuiPrivate.h b/Gui/GuiPrivate.h index 96f7c812d8..e821b8593e 100644 --- a/Gui/GuiPrivate.h +++ b/Gui/GuiPrivate.h @@ -266,6 +266,11 @@ struct GuiPrivate RenderStatsDialog* statsDialog; + PanelWidget* currentPanelFocus; + + //To prevent recursion when we forward an uncaught event to the click focus widget + int currentPanelFocusEventRecursion; + GuiPrivate(GuiAppInstance* app, Gui* gui); diff --git a/Gui/Histogram.cpp b/Gui/Histogram.cpp index 748bbcb765..73a223b64d 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" @@ -1227,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) ) { @@ -1422,17 +1425,23 @@ 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(); + } } void @@ -1446,7 +1455,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/NodeGraph10.cpp b/Gui/NodeGraph10.cpp index 9602dc69f9..6a53c9ac42 100644 --- a/Gui/NodeGraph10.cpp +++ b/Gui/NodeGraph10.cpp @@ -56,6 +56,9 @@ void NodeGraph::mousePressEvent(QMouseEvent* e) { assert(e); + + takeClickFocus(); + _imp->_hasMovedOnce = false; _imp->_deltaSinceMousePress = QPointF(0,0); if ( buttonDownIsMiddle(e) ) { diff --git a/Gui/NodeGraph25.cpp b/Gui/NodeGraph25.cpp index daa0160dd8..e070b24e55 100644 --- a/Gui/NodeGraph25.cpp +++ b/Gui/NodeGraph25.cpp @@ -235,14 +235,9 @@ NodeGraph::keyPressEvent(QKeyEvent* e) 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) ) { + if ( isKeybind(kShortcutGroupNodegraph, kShortcutIDActionGraphCreateReader, modifiers, key) ) { getGui()->createReader(); } else if ( isKeybind(kShortcutGroupNodegraph, kShortcutIDActionGraphCreateWriter, modifiers, key) ) { getGui()->createWriter(); @@ -277,8 +272,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 && modCASIsNone(e)) { _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 +422,13 @@ NodeGraph::keyPressEvent(QKeyEvent* e) if (!intercepted) { + accept = false; QGraphicsView::keyPressEvent(e); } } + if (accept) { + takeClickFocus(); + } } // keyPressEvent void diff --git a/Gui/NodeGraph30.cpp b/Gui/NodeGraph30.cpp index 9adeb7d1da..88eed4fdf3 100644 --- a/Gui/NodeGraph30.cpp +++ b/Gui/NodeGraph30.cpp @@ -151,6 +151,7 @@ NodeGraph::enterEvent(QEvent* e) void NodeGraph::leaveEvent(QEvent* e) { + leaveEventBase(); QGraphicsView::leaveEvent(e); _imp->_nodeCreationShortcutEnabled = false; diff --git a/Gui/PanelWidget.cpp b/Gui/PanelWidget.cpp index f5a183eb43..4b23ae02d6 100644 --- a/Gui/PanelWidget.cpp +++ b/Gui/PanelWidget.cpp @@ -25,6 +25,8 @@ #include "PanelWidget.h" #include +#include "Gui/TabWidget.h" + PanelWidget::PanelWidget(QWidget* thisWidget, Gui* gui) : ScriptObject() @@ -52,11 +54,75 @@ PanelWidget::notifyGuiClosingPublic() 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(); } +} + +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); + } } \ No newline at end of file diff --git a/Gui/PanelWidget.h b/Gui/PanelWidget.h index a18680f3a3..885c2118cf 100644 --- a/Gui/PanelWidget.h +++ b/Gui/PanelWidget.h @@ -36,6 +36,7 @@ CLANG_DIAG_ON(uninitialized) #include "Engine/ScriptObject.h" class Gui; +class TabWidget; class PanelWidget : public ScriptObject { QWidget* _thisWidget; @@ -55,6 +56,12 @@ class PanelWidget : public ScriptObject return _thisWidget; } + TabWidget* getParentPane() const; + + void removeClickFocus(); + + void takeClickFocus(); + protected: virtual void notifyGuiClosing() {} @@ -63,6 +70,12 @@ class PanelWidget : public ScriptObject * @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(); + }; diff --git a/Gui/PropertiesBinWrapper.cpp b/Gui/PropertiesBinWrapper.cpp index 6fb37802ad..c6324b48e4 100644 --- a/Gui/PropertiesBinWrapper.cpp +++ b/Gui/PropertiesBinWrapper.cpp @@ -42,4 +42,11 @@ PropertiesBinWrapper::enterEvent(QEvent* e) { enterEventBase(); QWidget::enterEvent(e); +} + +void +PropertiesBinWrapper::leaveEvent(QEvent* e) +{ + leaveEventBase(); + QWidget::leaveEvent(e); } \ No newline at end of file diff --git a/Gui/PropertiesBinWrapper.h b/Gui/PropertiesBinWrapper.h index bf3b4af944..03b714dc1e 100644 --- a/Gui/PropertiesBinWrapper.h +++ b/Gui/PropertiesBinWrapper.h @@ -40,6 +40,7 @@ class PropertiesBinWrapper : public QWidget, public PanelWidget private: virtual void enterEvent(QEvent* e) OVERRIDE FINAL; + virtual void leaveEvent(QEvent* e) OVERRIDE FINAL; }; #endif // Gui_PropertiesBinWrapper_h diff --git a/Gui/PythonPanels.cpp b/Gui/PythonPanels.cpp index 0aef0ba32f..4985e71ef4 100644 --- a/Gui/PythonPanels.cpp +++ b/Gui/PythonPanels.cpp @@ -438,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) { diff --git a/Gui/PythonPanels.h b/Gui/PythonPanels.h index eff5bcf87e..41497d4d34 100644 --- a/Gui/PythonPanels.h +++ b/Gui/PythonPanels.h @@ -137,8 +137,11 @@ class PyPanel : public QWidget, public UserParamHolder, public PanelWidget 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/Resources/Stylesheets/mainstyle.qss b/Gui/Resources/Stylesheets/mainstyle.qss index 43fad56f72..d6bdfe40bf 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; diff --git a/Gui/ScriptEditor.cpp b/Gui/ScriptEditor.cpp index 82ac602d34..a9f1d11b3c 100644 --- a/Gui/ScriptEditor.cpp +++ b/Gui/ScriptEditor.cpp @@ -447,9 +447,19 @@ 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)) { onUndoClicked(); @@ -460,9 +470,12 @@ ScriptEditor::keyPressEvent(QKeyEvent* e) } else if (key == Qt::Key_Backspace && modifierHasControl(e)) { onClearOutputClicked(); } else { + accept = false; QWidget::keyPressEvent(e); } - + if (accept) { + takeClickFocus(); + } } void @@ -509,4 +522,11 @@ ScriptEditor::enterEvent(QEvent *e) { enterEventBase(); QWidget::enterEvent(e); -} \ No newline at end of file +} + +void +ScriptEditor::leaveEvent(QEvent *e) +{ + leaveEventBase(); + QWidget::leaveEvent(e); +} diff --git a/Gui/ScriptEditor.h b/Gui/ScriptEditor.h index cc30f9cc5f..270ddfe12b 100644 --- a/Gui/ScriptEditor.h +++ b/Gui/ScriptEditor.h @@ -95,7 +95,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/TabWidget.cpp b/Gui/TabWidget.cpp index 349958f565..250d79a104 100644 --- a/Gui/TabWidget.cpp +++ b/Gui/TabWidget.cpp @@ -1082,6 +1082,13 @@ TabWidget::removeTab(int index,bool userAction) } } } + + QWidget* w = tab->getWidget(); + + if (_imp->tabBar->hasClickFocus() && _imp->currentWidget == tab) { + tab->removeClickFocus(); + } + { QMutexLocker l(&_imp->tabWidgetStateMutex); @@ -1115,8 +1122,7 @@ TabWidget::removeTab(int index,bool userAction) _imp->removeTabToPython(tab, obj->getScriptName()); - - QWidget* w = tab->getWidget(); + if (userAction) { if (isViewer) { @@ -1194,6 +1200,17 @@ TabWidget::makeCurrentTab(int index) 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); @@ -1206,7 +1223,9 @@ TabWidget::makeCurrentTab(int index) _imp->mainLayout->removeWidget(w); } _imp->currentWidget = tab; + curWidget = _imp->currentWidget; } + _imp->mainLayout->addWidget(tabW); QObject::connect(tabW, SIGNAL(destroyed()), this, SLOT(onCurrentTabDeleted())); @@ -1215,6 +1234,10 @@ TabWidget::makeCurrentTab(int index) _imp->modifyingTabBar = true; _imp->tabBar->setCurrentIndex(index); _imp->modifyingTabBar = false; + + if (hadFocus) { + curWidget->takeClickFocus(); + } } void @@ -1754,38 +1777,52 @@ 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 @@ -1810,12 +1847,20 @@ TabWidget::moveTab(PanelWidget* 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(); } @@ -1882,6 +1927,26 @@ TabWidget::currentWidget(PanelWidget** 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 diff --git a/Gui/TabWidget.h b/Gui/TabWidget.h index a911c766f5..40a9f2f2f0 100644 --- a/Gui/TabWidget.h +++ b/Gui/TabWidget.h @@ -235,6 +235,9 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON void currentWidget(PanelWidget** 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 **/ @@ -271,6 +274,8 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON ///MT-Safe bool isFullScreen() const; + + void togglePaneFullScreen(); ///MT-Safe bool isAnchor() const; diff --git a/Gui/ViewerGL.cpp b/Gui/ViewerGL.cpp index f939582ddd..157226f507 100644 --- a/Gui/ViewerGL.cpp +++ b/Gui/ViewerGL.cpp @@ -1580,6 +1580,8 @@ ViewerGL::mousePressEvent(QMouseEvent* e) if ( !_imp->viewerTab->getGui() ) { return; } + + _imp->viewerTab->onMousePressCalledInViewer(); _imp->hasMovedSincePress = false; _imp->pressureOnRelease = 1.; @@ -2876,52 +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) diff --git a/Gui/ViewerGL.h b/Gui/ViewerGL.h index f3ffc65e68..885d382ab4 100644 --- a/Gui/ViewerGL.h +++ b/Gui/ViewerGL.h @@ -419,7 +419,6 @@ public Q_SLOTS: virtual void focusInEvent(QFocusEvent* e) OVERRIDE FINAL; virtual void focusOutEvent(QFocusEvent* 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.h b/Gui/ViewerTab.h index 3d24c91d4d..5653173f6a 100644 --- a/Gui/ViewerTab.h +++ b/Gui/ViewerTab.h @@ -281,6 +281,8 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON bool isPickerEnabled() const; void setPickerEnabled(bool enabled); + + void onMousePressCalledInViewer(); public Q_SLOTS: @@ -441,6 +443,7 @@ public Q_SLOTS: virtual bool eventFilter(QObject *target, QEvent* e) OVERRIDE FINAL; virtual void keyPressEvent(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 f29c3e5dac..e7d56b1e55 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" @@ -485,19 +486,24 @@ ViewerTab::enterEvent(QEvent* e) 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); @@ -672,9 +678,45 @@ ViewerTab::keyPressEvent(QKeyEvent* e) connectToInput(8); } else if (isKeybind(kShortcutGroupGlobal, kShortcutIDActionConnectViewerToInput10, modifiers, key) ) { connectToInput(9); + } 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 { + accept = false; QWidget::keyPressEvent(e); } + if (accept) { + takeClickFocus(); + } } // keyPressEvent void diff --git a/Gui/ViewerTab40.cpp b/Gui/ViewerTab40.cpp index 6552e9bb8b..02a9b2dbaa 100644 --- a/Gui/ViewerTab40.cpp +++ b/Gui/ViewerTab40.cpp @@ -581,6 +581,12 @@ ViewerTab::setPickerEnabled(bool enabled) setInfobarVisible(true); } +void +ViewerTab::onMousePressCalledInViewer() +{ + takeClickFocus(); +} + void ViewerTab::onPickerButtonClicked(bool clicked) { From e27bf89e6614b6dc53fe70e9181dce456fbc80db Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Wed, 30 Sep 2015 15:42:04 +0200 Subject: [PATCH 052/178] Call Event::accept() if shortcut was intercepted so it is not propagated --- Gui/CurveEditor.cpp | 14 +++++++++----- Gui/CurveWidget.cpp | 31 ++++++++++++++++++------------- Gui/DopeSheetEditor.cpp | 1 + Gui/Histogram.cpp | 1 + Gui/NodeGraph25.cpp | 1 + Gui/ScriptEditor.cpp | 1 + Gui/ViewerTab10.cpp | 1 + 7 files changed, 32 insertions(+), 18 deletions(-) diff --git a/Gui/CurveEditor.cpp b/Gui/CurveEditor.cpp index 66433aabeb..c2d996090d 100644 --- a/Gui/CurveEditor.cpp +++ b/Gui/CurveEditor.cpp @@ -1534,16 +1534,20 @@ RotoCurveEditorContext::findElement(KnobGui* knob,int dimension) const void CurveEditor::keyPressEvent(QKeyEvent* e) { - //Qt::KeyboardModifiers modifiers = e->modifiers(); - //Qt::Key key = (Qt::Key)e->key(); + Qt::KeyboardModifiers modifiers = e->modifiers(); + Qt::Key key = (Qt::Key)e->key(); - onInputEventCalled(); - - if (e->key() == Qt::Key_F && modCASIsControl(e)) { + bool accept = true; + if (isKeybind(kShortcutGroupViewer, kShortcutIDActionFitViewer, modifiers, key)) { _imp->filterEdit->setFocus(); } else { + accept = false; QWidget::keyPressEvent(e); } + if (accept) { + takeClickFocus(); + e->accept(); + } } void diff --git a/Gui/CurveWidget.cpp b/Gui/CurveWidget.cpp index f1fabfe85e..0034396355 100644 --- a/Gui/CurveWidget.cpp +++ b/Gui/CurveWidget.cpp @@ -1301,19 +1301,7 @@ CurveWidget::keyPressEvent(QKeyEvent* 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(); - } - + bool accept = true; Qt::KeyboardModifiers modifiers = e->modifiers(); Qt::Key key = (Qt::Key)e->key(); @@ -1348,8 +1336,25 @@ 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); } + if (accept) { + CurveEditor* ce = 0; + if ( parentWidget() ) { + QWidget* parent = parentWidget()->parentWidget(); + if (parent) { + if (parent->objectName() == "CurveEditor") { + ce = dynamic_cast(parent); + } + } + } + if (ce) { + ce->onInputEventCalled(); + } + + e->accept(); + } } // keyPressEvent diff --git a/Gui/DopeSheetEditor.cpp b/Gui/DopeSheetEditor.cpp index 37f44ccb60..68fa069486 100644 --- a/Gui/DopeSheetEditor.cpp +++ b/Gui/DopeSheetEditor.cpp @@ -234,6 +234,7 @@ DopeSheetEditor::keyPressEvent(QKeyEvent* e) } if (accept) { takeClickFocus(); + e->accept(); } } diff --git a/Gui/Histogram.cpp b/Gui/Histogram.cpp index 73a223b64d..6a4d199037 100644 --- a/Gui/Histogram.cpp +++ b/Gui/Histogram.cpp @@ -1441,6 +1441,7 @@ Histogram::keyPressEvent(QKeyEvent* e) } if (accept) { takeClickFocus(); + e->accept(); } } diff --git a/Gui/NodeGraph25.cpp b/Gui/NodeGraph25.cpp index e070b24e55..ab3162490e 100644 --- a/Gui/NodeGraph25.cpp +++ b/Gui/NodeGraph25.cpp @@ -428,6 +428,7 @@ NodeGraph::keyPressEvent(QKeyEvent* e) } if (accept) { takeClickFocus(); + e->accept(); } } // keyPressEvent diff --git a/Gui/ScriptEditor.cpp b/Gui/ScriptEditor.cpp index a9f1d11b3c..8f94287c39 100644 --- a/Gui/ScriptEditor.cpp +++ b/Gui/ScriptEditor.cpp @@ -475,6 +475,7 @@ ScriptEditor::keyPressEvent(QKeyEvent* e) } if (accept) { takeClickFocus(); + e->accept(); } } diff --git a/Gui/ViewerTab10.cpp b/Gui/ViewerTab10.cpp index e7d56b1e55..2a5781eb87 100644 --- a/Gui/ViewerTab10.cpp +++ b/Gui/ViewerTab10.cpp @@ -716,6 +716,7 @@ ViewerTab::keyPressEvent(QKeyEvent* e) } if (accept) { takeClickFocus(); + e->accept(); } } // keyPressEvent From 3344c9f1ab6d2cd5a4cf89257aaa555b8434aca6 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Wed, 30 Sep 2015 15:51:08 +0200 Subject: [PATCH 053/178] Bug fix --- Gui/ViewerTab10.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gui/ViewerTab10.cpp b/Gui/ViewerTab10.cpp index 2a5781eb87..2ab97d472f 100644 --- a/Gui/ViewerTab10.cpp +++ b/Gui/ViewerTab10.cpp @@ -678,7 +678,7 @@ ViewerTab::keyPressEvent(QKeyEvent* e) connectToInput(8); } else if (isKeybind(kShortcutGroupGlobal, kShortcutIDActionConnectViewerToInput10, modifiers, key) ) { connectToInput(9); - } if ( isKeybind(kShortcutGroupViewer, kShortcutIDActionHideOverlays, modifiers, key) ) { + } else if ( isKeybind(kShortcutGroupViewer, kShortcutIDActionHideOverlays, modifiers, key) ) { _imp->viewer->toggleOverlays(); } else if (isKeybind(kShortcutGroupViewer, kShortcutIDToggleWipe, modifiers, key)) { _imp->viewer->toggleWipe(); From ef15812e1630eac8a4e551ee010352d320290482 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Wed, 30 Sep 2015 15:53:19 +0200 Subject: [PATCH 054/178] Tab menu: take focus --- Gui/NodeGraph25.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gui/NodeGraph25.cpp b/Gui/NodeGraph25.cpp index ab3162490e..786619846c 100644 --- a/Gui/NodeGraph25.cpp +++ b/Gui/NodeGraph25.cpp @@ -177,7 +177,7 @@ NodeGraph::event(QEvent* e) QObject::connect( nodeCreation,SIGNAL( rejected() ),this,SLOT( onNodeCreationDialogFinished() ) ); nodeCreation->show(); - + takeClickFocus(); ke->accept(); return true; From 3131b24f36e104cc8743736fbd436ed6b7b8381c Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Wed, 30 Sep 2015 16:57:54 +0200 Subject: [PATCH 055/178] Properties bin: when a widget takes focus, take click focus --- Gui/PropertiesBinWrapper.cpp | 37 +++++++++++++++++++++++++++++++++++- Gui/PropertiesBinWrapper.h | 7 ++++++- Gui/SpinBox.cpp | 9 +-------- Gui/SpinBox.h | 1 - 4 files changed, 43 insertions(+), 11 deletions(-) diff --git a/Gui/PropertiesBinWrapper.cpp b/Gui/PropertiesBinWrapper.cpp index c6324b48e4..2a2db92134 100644 --- a/Gui/PropertiesBinWrapper.cpp +++ b/Gui/PropertiesBinWrapper.cpp @@ -23,13 +23,16 @@ // ***** END PYTHON BLOCK ***** #include "PropertiesBinWrapper.h" + +#include + #include "Gui/Gui.h" PropertiesBinWrapper::PropertiesBinWrapper(Gui* parent) : QWidget(parent) , PanelWidget(this,parent) { - + QObject::connect(qApp, SIGNAL(focusChanged(QWidget*,QWidget*)), this, SLOT(onFocusChanged(QWidget*, QWidget*))); } PropertiesBinWrapper::~PropertiesBinWrapper() @@ -37,6 +40,38 @@ PropertiesBinWrapper::~PropertiesBinWrapper() } +void +PropertiesBinWrapper::mousePressEvent(QMouseEvent* e) +{ + takeClickFocus(); + QWidget::mousePressEvent(e); +} + +static PropertiesBinWrapper* isPropertiesBinChild(QWidget* w, int recursionLevel) +{ + if (!w) { + return 0; + } + PropertiesBinWrapper* 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 isPropertiesBinChild(w->parentWidget(), recursionLevel + 1); +} + +void +PropertiesBinWrapper::onFocusChanged(QWidget* /*old*/, QWidget* newFocus) +{ + PropertiesBinWrapper* pw = isPropertiesBinChild(newFocus,0); + if (pw) { + assert(pw == this); + takeClickFocus(); + } +} + void PropertiesBinWrapper::enterEvent(QEvent* e) { diff --git a/Gui/PropertiesBinWrapper.h b/Gui/PropertiesBinWrapper.h index 03b714dc1e..fe1850ff90 100644 --- a/Gui/PropertiesBinWrapper.h +++ b/Gui/PropertiesBinWrapper.h @@ -30,6 +30,7 @@ class PropertiesBinWrapper : public QWidget, public PanelWidget { + Q_OBJECT public: @@ -37,8 +38,12 @@ class PropertiesBinWrapper : public QWidget, public PanelWidget virtual ~PropertiesBinWrapper(); -private: +public Q_SLOTS: + + void onFocusChanged(QWidget* old, QWidget*); +private: + virtual void mousePressEvent(QMouseEvent* e) OVERRIDE FINAL; virtual void enterEvent(QEvent* e) OVERRIDE FINAL; virtual void leaveEvent(QEvent* e) OVERRIDE FINAL; }; 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; From 64179e8d20ec62ad348af720ea1926793b024211 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Wed, 30 Sep 2015 17:02:38 +0200 Subject: [PATCH 056/178] Generalise sub widgets focus to all panes --- Gui/Gui.cpp | 3 +++ Gui/Gui.h | 3 +++ Gui/Gui50.cpp | 24 ++++++++++++++++++++++++ Gui/PropertiesBinWrapper.cpp | 24 ------------------------ Gui/PropertiesBinWrapper.h | 6 +----- 5 files changed, 31 insertions(+), 29 deletions(-) diff --git a/Gui/Gui.cpp b/Gui/Gui.cpp index ace60d9162..4df32d9e64 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() diff --git a/Gui/Gui.h b/Gui/Gui.h index b3a100978b..2ba04589d9 100644 --- a/Gui/Gui.h +++ b/Gui/Gui.h @@ -720,6 +720,9 @@ public Q_SLOTS: void onRenderProgressDialogFinished(); + void onFocusChanged(QWidget* old, QWidget*); + + private: diff --git a/Gui/Gui50.cpp b/Gui/Gui50.cpp index 78d924833f..b54e61122f 100644 --- a/Gui/Gui50.cpp +++ b/Gui/Gui50.cpp @@ -915,4 +915,28 @@ 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/PropertiesBinWrapper.cpp b/Gui/PropertiesBinWrapper.cpp index 2a2db92134..8e2e99bc40 100644 --- a/Gui/PropertiesBinWrapper.cpp +++ b/Gui/PropertiesBinWrapper.cpp @@ -32,7 +32,6 @@ PropertiesBinWrapper::PropertiesBinWrapper(Gui* parent) : QWidget(parent) , PanelWidget(this,parent) { - QObject::connect(qApp, SIGNAL(focusChanged(QWidget*,QWidget*)), this, SLOT(onFocusChanged(QWidget*, QWidget*))); } PropertiesBinWrapper::~PropertiesBinWrapper() @@ -47,30 +46,7 @@ PropertiesBinWrapper::mousePressEvent(QMouseEvent* e) QWidget::mousePressEvent(e); } -static PropertiesBinWrapper* isPropertiesBinChild(QWidget* w, int recursionLevel) -{ - if (!w) { - return 0; - } - PropertiesBinWrapper* 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 isPropertiesBinChild(w->parentWidget(), recursionLevel + 1); -} -void -PropertiesBinWrapper::onFocusChanged(QWidget* /*old*/, QWidget* newFocus) -{ - PropertiesBinWrapper* pw = isPropertiesBinChild(newFocus,0); - if (pw) { - assert(pw == this); - takeClickFocus(); - } -} void PropertiesBinWrapper::enterEvent(QEvent* e) diff --git a/Gui/PropertiesBinWrapper.h b/Gui/PropertiesBinWrapper.h index fe1850ff90..2daa697753 100644 --- a/Gui/PropertiesBinWrapper.h +++ b/Gui/PropertiesBinWrapper.h @@ -30,17 +30,13 @@ class PropertiesBinWrapper : public QWidget, public PanelWidget { - Q_OBJECT public: PropertiesBinWrapper(Gui* parent); virtual ~PropertiesBinWrapper(); - -public Q_SLOTS: - - void onFocusChanged(QWidget* old, QWidget*); + private: virtual void mousePressEvent(QMouseEvent* e) OVERRIDE FINAL; From 1de4c5427f7a9b6e5acce215afc170224d3a7eb3 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Wed, 30 Sep 2015 17:39:18 +0200 Subject: [PATCH 057/178] Fix #926 --- Engine/AppInstance.cpp | 11 ++------- Engine/AppManager.cpp | 2 +- Engine/Settings.cpp | 4 +++ Gui/Gui50.cpp | 56 +++++++++++++++++------------------------- 4 files changed, 30 insertions(+), 43 deletions(-) diff --git a/Engine/AppInstance.cpp b/Engine/AppInstance.cpp index 1e7d05e541..83ae367858 100644 --- a/Engine/AppInstance.cpp +++ b/Engine/AppInstance.cpp @@ -774,15 +774,8 @@ 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) { - findId = PLUGINID_NATRON_ROTO; - } else { - findId = pluginID; - } try { - plugin = appPTR->getPluginBinary(findId,majorVersion,minorVersion,_imp->_projectCreatedWithLowerCaseIDs); + plugin = appPTR->getPluginBinary(pluginID,majorVersion,minorVersion,_imp->_projectCreatedWithLowerCaseIDs); } catch (const std::exception & e1) { ///Ok try with the old Ids we had in Natron prior to 1.0 @@ -996,7 +989,7 @@ AppInstance::loadNode(const LoadNodeArgs & args) INT_MIN,INT_MIN, false, true, - true, + false, true, QString(), CreateNodeArgs::DefaultValuesList(), diff --git a/Engine/AppManager.cpp b/Engine/AppManager.cpp index 27669ff96d..2c59f4313d 100644 --- a/Engine/AppManager.cpp +++ b/Engine/AppManager.cpp @@ -1363,7 +1363,7 @@ AppManager::getPluginBinaryFromOldID(const QString & pluginId,int majorVersion,i return _imp->findPluginById(PLUGINID_NATRON_DISKCACHE, majorVersion, minorVersion); } else if (pluginId == "BackDrop") { return _imp->findPluginById(PLUGINID_NATRON_BACKDROP, majorVersion, minorVersion); - } else if (pluginId == "RotoOFX [Draw]") { + } else if (pluginId == "RotoOFX [Draw]" || pluginId == PLUGINID_OFX_ROTO) { return _imp->findPluginById(PLUGINID_NATRON_ROTO, majorVersion, minorVersion); } diff --git a/Engine/Settings.cpp b/Engine/Settings.cpp index bea81cdf21..3163c36d92 100644 --- a/Engine/Settings.cpp +++ b/Engine/Settings.cpp @@ -2241,6 +2241,8 @@ Settings::populatePluginsTab(std::vector& pluginsToIgnore) } + restoreKnobsFromSettings(knobsToRestore); + for (std::map::iterator it = pluginsMap.begin(); it != pluginsMap.end(); ++it) { if (!it->second.enabled->getValue()) { pluginsToIgnore.push_back(it->first); @@ -2248,6 +2250,8 @@ Settings::populatePluginsTab(std::vector& pluginsToIgnore) _perPluginRenderScaleSupport.insert(std::make_pair(it->first->getPluginID().toStdString(), it->second.renderScaleSupport)); } } + + } void diff --git a/Gui/Gui50.cpp b/Gui/Gui50.cpp index b54e61122f..97ea612f23 100644 --- a/Gui/Gui50.cpp +++ b/Gui/Gui50.cpp @@ -622,42 +622,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() ); } } } + } } From 1c716cdd8fc19112c50b8bf02e8bff5117b6e888 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Wed, 30 Sep 2015 18:38:57 +0200 Subject: [PATCH 058/178] Deprecated plug-ins are disabled in Preferences by default but can be activated by the user. Projects containing deactivated plug-ins can load fine those nodes, but user cannot create them. --- Engine/AppManager.cpp | 82 +++---------- Engine/AppManager.h | 2 +- Engine/Node.cpp | 2 +- Engine/OfxHost.cpp | 5 +- Engine/Plugin.cpp | 13 +++ Engine/Plugin.h | 21 ++-- Engine/Settings.cpp | 199 +++++++++++++++----------------- Engine/Settings.h | 28 ++++- Gui/GuiApplicationManager10.cpp | 6 +- 9 files changed, 171 insertions(+), 187 deletions(-) diff --git a/Engine/AppManager.cpp b/Engine/AppManager.cpp index 2c59f4313d..979f82571e 100644 --- a/Engine/AppManager.cpp +++ b/Engine/AppManager.cpp @@ -717,59 +717,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); @@ -779,6 +726,9 @@ AppManager::loadAllPlugins() //Should be done after settings are declared loadPythonGroups(); + _imp->_settings->populatePluginsTab(); + + onAllPluginsLoaded(); } @@ -801,7 +751,7 @@ AppManager::onAllPluginsLoaded() assert(!it->second.empty()); PluginMajorsOrdered::iterator first = it->second.begin(); - if (!(*first)->getIsUserCreatable() || (*first)->getIsForInternalUseOnly()) { + if (!(*first)->getIsUserCreatable()) { continue; } @@ -813,7 +763,7 @@ AppManager::onAllPluginsLoaded() continue; } PluginMajorsOrdered::iterator other = it2->second.begin(); - if (!(*other)->getIsUserCreatable() || (*other)->getIsForInternalUseOnly()) { + if (!(*other)->getIsUserCreatable()) { continue; } @@ -858,7 +808,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() ) ); @@ -874,7 +824,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() ) ); @@ -890,7 +840,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() ) ); @@ -906,7 +856,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() ) ); @@ -922,7 +872,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() ) ); @@ -938,7 +888,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() ) ); @@ -955,7 +905,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() ) ); @@ -972,7 +922,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() ) ); @@ -1227,7 +1177,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); @@ -1248,7 +1198,7 @@ AppManager::registerPlugin(const QStringList & groups, bool mustCreateMutex, int major, int minor, - bool canBeUserCreated) + bool isDeprecated) { QMutex* pluginMutex = 0; @@ -1256,7 +1206,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()) { diff --git a/Engine/AppManager.h b/Engine/AppManager.h index 9538c81c4c..0b340fde9f 100644 --- a/Engine/AppManager.h +++ b/Engine/AppManager.h @@ -306,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/Node.cpp b/Engine/Node.cpp index 6980be648e..9305055639 100644 --- a/Engine/Node.cpp +++ b/Engine/Node.cpp @@ -597,7 +597,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; diff --git a/Engine/OfxHost.cpp b/Engine/OfxHost.cpp index 749c3e987b..88a4168c33 100644 --- a/Engine/OfxHost.cpp +++ b/Engine/OfxHost.cpp @@ -665,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(), @@ -677,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/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/Settings.cpp b/Engine/Settings.cpp index 3163c36d92..7504b14196 100644 --- a/Engine/Settings.cpp +++ b/Engine/Settings.cpp @@ -2101,36 +2101,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,91 +2148,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->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); + + ///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))); + } - pluginsMap.insert(std::make_pair(plugin, PerPluginKnobs(pluginActivation,zoomSupport))); - } restoreKnobsFromSettings(knobsToRestore); + +} - 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)); - } +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; + } + 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) { @@ -2750,15 +2752,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 diff --git a/Engine/Settings.h b/Engine/Settings.h index d3cde68777..8de848cf2b 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); @@ -236,7 +236,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 +307,8 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON std::string getUserStyleSheetFilePath() const; + bool isPluginDeactivated(const Natron::Plugin* p) const; + Q_SIGNALS: void settingChanged(KnobI* knob); @@ -479,7 +481,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/Gui/GuiApplicationManager10.cpp b/Gui/GuiApplicationManager10.cpp index ba83d8f78f..4675f9c732 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); } From 660b0b6a6c428d0fb7865bf2910dd2bde36b53e3 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Thu, 1 Oct 2015 08:57:52 +0200 Subject: [PATCH 059/178] fix #929 --- Gui/ViewerTab.h | 2 ++ Gui/ViewerTab40.cpp | 21 ++++++++++++++------- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/Gui/ViewerTab.h b/Gui/ViewerTab.h index 5653173f6a..98ac7c478e 100644 --- a/Gui/ViewerTab.h +++ b/Gui/ViewerTab.h @@ -433,6 +433,8 @@ public Q_SLOTS: private: + void onPickerButtonClickedInternal(ViewerTab* caller,bool); + void onCompositingOperatorChangedInternal(Natron::ViewerCompositingOperatorEnum oldOp,Natron::ViewerCompositingOperatorEnum newOp); diff --git a/Gui/ViewerTab40.cpp b/Gui/ViewerTab40.cpp index 02a9b2dbaa..bec0eb2838 100644 --- a/Gui/ViewerTab40.cpp +++ b/Gui/ViewerTab40.cpp @@ -588,22 +588,29 @@ ViewerTab::onMousePressCalledInViewer() } void -ViewerTab::onPickerButtonClicked(bool clicked) +ViewerTab::onPickerButtonClickedInternal(ViewerTab* caller,bool clicked) { - const std::list &viewers = getGui()->getViewersList(); - for (std::list::const_iterator it = viewers.begin(); it!=viewers.end(); ++it) { - if ((*it) != this) { - (*it)->onPickerButtonClicked(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() { From 9d2ea9cd4ea66aa2c1f421e87d32c5dceb2af685 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Thu, 1 Oct 2015 09:07:13 +0200 Subject: [PATCH 060/178] take click focus when calling actions from menu bar too --- Gui/Gui50.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Gui/Gui50.cpp b/Gui/Gui50.cpp index 97ea612f23..0c09152b47 100644 --- a/Gui/Gui50.cpp +++ b/Gui/Gui50.cpp @@ -709,7 +709,12 @@ Gui::onPrevTabTriggered() if (t) { t->moveToPreviousTab(); + PanelWidget* pw = t->currentWidget(); + if (pw) { + pw->takeClickFocus(); + } } + } @@ -720,6 +725,10 @@ Gui::onNextTabTriggered() if (t) { t->moveToNextTab(); + PanelWidget* pw = t->currentWidget(); + if (pw) { + pw->takeClickFocus(); + } } } @@ -730,6 +739,10 @@ Gui::onCloseTabTriggered() if (t) { t->closeCurrentWidget(); + PanelWidget* pw = t->currentWidget(); + if (pw) { + pw->takeClickFocus(); + } } } From bb54e0c799e639b05426696b421d1b0cb0c8592e Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Thu, 1 Oct 2015 09:21:28 +0200 Subject: [PATCH 061/178] Remove unneeded calls to updateGL by update() --- Gui/CustomParamInteract.cpp | 14 +++++++------- Gui/Gui05.cpp | 4 ++-- Gui/ViewerGL.cpp | 4 ++-- Gui/ViewerTab10.cpp | 2 +- Gui/ViewerTab30.cpp | 2 +- Gui/ViewerTab40.cpp | 4 ++-- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Gui/CustomParamInteract.cpp b/Gui/CustomParamInteract.cpp index a4c91fc85f..b7b6ca4955 100644 --- a/Gui/CustomParamInteract.cpp +++ b/Gui/CustomParamInteract.cpp @@ -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/Gui05.cpp b/Gui/Gui05.cpp index 3e47b94fc5..5ea1d7fa2a 100644 --- a/Gui/Gui05.cpp +++ b/Gui/Gui05.cpp @@ -165,12 +165,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/ViewerGL.cpp b/Gui/ViewerGL.cpp index 157226f507..bdde9ff02a 100644 --- a/Gui/ViewerGL.cpp +++ b/Gui/ViewerGL.cpp @@ -213,7 +213,7 @@ ViewerGL::resizeGL(int w, if (!_imp->persistentMessages.empty()) { updatePersistentMessageToWidth(w - 20); } else { - updateGL(); + update(); } } } @@ -2822,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*/ diff --git a/Gui/ViewerTab10.cpp b/Gui/ViewerTab10.cpp index 2ab97d472f..216ab7deba 100644 --- a/Gui/ViewerTab10.cpp +++ b/Gui/ViewerTab10.cpp @@ -403,7 +403,7 @@ ViewerTab::centerViewer() if ( _imp->viewer->displayingImage() ) { _imp->viewerNode->renderCurrentFrame(false); } else { - _imp->viewer->updateGL(); + _imp->viewer->update(); } } diff --git a/Gui/ViewerTab30.cpp b/Gui/ViewerTab30.cpp index 8125936335..dd7d96af84 100644 --- a/Gui/ViewerTab30.cpp +++ b/Gui/ViewerTab30.cpp @@ -744,7 +744,7 @@ ViewerTab::onCompositingOperatorChangedInternal(Natron::ViewerCompositingOperato _imp->infoWidget[1]->show(); } - _imp->viewer->updateGL(); + _imp->viewer->update(); } void diff --git a/Gui/ViewerTab40.cpp b/Gui/ViewerTab40.cpp index bec0eb2838..307fe38fa5 100644 --- a/Gui/ViewerTab40.cpp +++ b/Gui/ViewerTab40.cpp @@ -706,8 +706,8 @@ ViewerTab::setTurboButtonDown(bool down) void ViewerTab::redrawGLWidgets() { - _imp->viewer->updateGL(); - _imp->timeLineGui->updateGL(); + _imp->viewer->update(); + _imp->timeLineGui->update(); } void From fa3691278b9c291b281cf7791d2e18018f956f50 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Thu, 1 Oct 2015 09:44:01 +0200 Subject: [PATCH 062/178] Bug fix --- Engine/NodeGroup.cpp | 3 +++ Gui/NodeGraph.cpp | 10 ++++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/Engine/NodeGroup.cpp b/Engine/NodeGroup.cpp index 3e6226c1e8..e9c7aed963 100644 --- a/Engine/NodeGroup.cpp +++ b/Engine/NodeGroup.cpp @@ -1052,6 +1052,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/Gui/NodeGraph.cpp b/Gui/NodeGraph.cpp index 5a36cad34b..3a67aadfa6 100644 --- a/Gui/NodeGraph.cpp +++ b/Gui/NodeGraph.cpp @@ -177,10 +177,12 @@ NodeGraph::NodeGraph(Gui* gui, _imp->_menu = new Natron::Menu(this); //_imp->_menu->setFont( QFont(appFont,appFontSize) ); - - boost::shared_ptr timeline = getGui()->getApp()->getTimeLine(); - QObject::connect( timeline.get(),SIGNAL( frameChanged(SequenceTime,int) ), this,SLOT( onTimeChanged(SequenceTime,int) ) ); - QObject::connect( timeline.get(),SIGNAL( frameAboutToChange() ), this,SLOT( onTimelineTimeAboutToChange() ) ); + + if (!isGrp) { + boost::shared_ptr timeline = getGui()->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() From 88734a8907eae086ec144130d662f5098f1cef25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole-Andr=C3=A9=20Rodlie?= Date: Thu, 1 Oct 2015 10:05:58 +0200 Subject: [PATCH 063/178] Linux: fontfix --- tools/linux/include/scripts/build-installer.sh | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tools/linux/include/scripts/build-installer.sh b/tools/linux/include/scripts/build-installer.sh index c4c127939e..53b236eb00 100644 --- a/tools/linux/include/scripts/build-installer.sh +++ b/tools/linux/include/scripts/build-installer.sh @@ -216,9 +216,10 @@ mv $IO_LIBS/{libOpenColor*,libgomp*} $CLIBS_PATH/data/lib/ || exit 1 ln -sf ../../../lib/libgomp.so.1 . ) -mkdir -p $CLIBS_PATH/data/share/etc || exit 1 -cp -a $INSTALL_PATH/etc/fonts $CLIBS_PATH/data/share/etc/ || exit 1 -sed -i "s#${SDK_PATH}/Natron-${SDK_VERSION}/#/#" $CLIBS_PATH/data/share/etc/fonts/fonts.conf || exit 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 From e2359f76c30794afb30a688a9b94a462cc6d138a Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Thu, 1 Oct 2015 10:45:00 +0200 Subject: [PATCH 064/178] Update only visible knobs during playback when turbo is turned off --- Engine/EffectInstance.cpp | 6 ++-- Engine/Knob.cpp | 8 +++-- Engine/NodeGroup.cpp | 2 +- Engine/OutputSchedulerThread.cpp | 38 +++++++++++++---------- Gui/DockablePanel.cpp | 4 +++ Gui/Gui.h | 3 ++ Gui/Gui05.cpp | 8 ++++- Gui/Gui40.cpp | 38 +++++++++++++++++++++++ Gui/NodeGraph.cpp | 6 ---- Gui/NodeGraph.h | 7 +---- Gui/NodeGraph45.cpp | 53 -------------------------------- Gui/NodeGraphPrivate.cpp | 1 - Gui/NodeGraphPrivate.h | 1 - 13 files changed, 85 insertions(+), 90 deletions(-) diff --git a/Engine/EffectInstance.cpp b/Engine/EffectInstance.cpp index 23fe844e8e..a2bb5901f7 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; @@ -642,7 +642,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) ) { diff --git a/Engine/Knob.cpp b/Engine/Knob.cpp index fd5a14ee88..a018ecf807 100644 --- a/Engine/Knob.cpp +++ b/Engine/Knob.cpp @@ -2603,12 +2603,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 ); } diff --git a/Engine/NodeGroup.cpp b/Engine/NodeGroup.cpp index e9c7aed963..c41aa5823c 100644 --- a/Engine/NodeGroup.cpp +++ b/Engine/NodeGroup.cpp @@ -1052,7 +1052,7 @@ NodeCollection::invalidateParallelRenderArgs() NodeList nodes = getNodes(); for (NodeList::iterator it = nodes.begin(); it != nodes.end(); ++it) { - if (*it || !(*it)->getLiveInstance()) { + if (!(*it) || !(*it)->getLiveInstance()) { continue; } (*it)->getLiveInstance()->invalidateParallelRenderArgsTLS(); diff --git a/Engine/OutputSchedulerThread.cpp b/Engine/OutputSchedulerThread.cpp index 99f4996dfe..26b6711a89 100644 --- a/Engine/OutputSchedulerThread.cpp +++ b/Engine/OutputSchedulerThread.cpp @@ -921,6 +921,7 @@ OutputSchedulerThread::stopRender() QMutexLocker abortBeingProcessedLocker(&_imp->abortBeingProcessedMutex); _imp->abortBeingProcessed = true; + bool wasAborted; { QMutexLocker l(&_imp->abortedRequestedMutex); @@ -929,6 +930,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,22 +958,7 @@ 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); + } diff --git a/Gui/DockablePanel.cpp b/Gui/DockablePanel.cpp index 73d494d375..6e69a99e66 100644 --- a/Gui/DockablePanel.cpp +++ b/Gui/DockablePanel.cpp @@ -813,6 +813,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(); diff --git a/Gui/Gui.h b/Gui/Gui.h index 2ba04589d9..96f07e503a 100644 --- a/Gui/Gui.h +++ b/Gui/Gui.h @@ -610,6 +610,9 @@ 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 reloadStylesheet(); diff --git a/Gui/Gui05.cpp b/Gui/Gui05.cpp index 5ea1d7fa2a..d61c5da2ef 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,7 +134,12 @@ 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) ) ); /*Searches recursively for all child objects of the given object, diff --git a/Gui/Gui40.cpp b/Gui/Gui40.cpp index fc3880247a..8d8ac78d46 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; @@ -842,3 +843,40 @@ Gui::onEnableRenderStatsActionTriggered() _imp->statsDialog->show(); } } + +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/NodeGraph.cpp b/Gui/NodeGraph.cpp index 3a67aadfa6..2a8e4d0d95 100644 --- a/Gui/NodeGraph.cpp +++ b/Gui/NodeGraph.cpp @@ -176,13 +176,7 @@ NodeGraph::NodeGraph(Gui* gui, setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); _imp->_menu = new Natron::Menu(this); - //_imp->_menu->setFont( QFont(appFont,appFontSize) ); - if (!isGrp) { - boost::shared_ptr timeline = getGui()->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() diff --git a/Gui/NodeGraph.h b/Gui/NodeGraph.h index 435a855f98..16b3718f81 100644 --- a/Gui/NodeGraph.h +++ b/Gui/NodeGraph.h @@ -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/NodeGraph45.cpp b/Gui/NodeGraph45.cpp index ddeec726f3..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 = getGui()->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 (!getGui()) { - return; - } - boost::shared_ptr project = getGui()->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) { diff --git a/Gui/NodeGraphPrivate.cpp b/Gui/NodeGraphPrivate.cpp index 2338207218..a0e2006504 100644 --- a/Gui/NodeGraphPrivate.cpp +++ b/Gui/NodeGraphPrivate.cpp @@ -87,7 +87,6 @@ NodeGraphPrivate::NodeGraphPrivate(NodeGraph* p, , _deltaSinceMousePress(0,0) , _hasMovedOnce(false) , lastSelectedViewer(0) -, wasLaskUserSeekDuringPlayback(false) { } diff --git a/Gui/NodeGraphPrivate.h b/Gui/NodeGraphPrivate.h index 5bb7b76bc2..6b8275995d 100644 --- a/Gui/NodeGraphPrivate.h +++ b/Gui/NodeGraphPrivate.h @@ -227,7 +227,6 @@ struct NodeGraphPrivate bool _hasMovedOnce; ViewerTab* lastSelectedViewer; - bool wasLaskUserSeekDuringPlayback; NodeGraphPrivate(NodeGraph* p, const boost::shared_ptr& group); From c27f179446239af43016d8aec05bb8610173481d Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Thu, 1 Oct 2015 12:02:12 +0200 Subject: [PATCH 065/178] Fix a bug where the playback could stall when zooming --- Engine/OutputSchedulerThread.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Engine/OutputSchedulerThread.cpp b/Engine/OutputSchedulerThread.cpp index 26b6711a89..66073689f5 100644 --- a/Engine/OutputSchedulerThread.cpp +++ b/Engine/OutputSchedulerThread.cpp @@ -614,7 +614,7 @@ OutputSchedulerThread::pushFramesToRenderInternal(int startingFrame,int nThreads firstFrame = _imp->livingRunArgs.firstFrame; lastFrame = _imp->livingRunArgs.lastFrame; } - + PlaybackModeEnum pMode = _imp->engine->getPlaybackMode(); if (firstFrame == lastFrame) { @@ -747,7 +747,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 +842,6 @@ OutputSchedulerThread::startRender() Natron::SchedulingPolicyEnum policy = getSchedulingPolicy(); - if (policy == Natron::eSchedulingPolicyFFA) { @@ -963,7 +961,6 @@ OutputSchedulerThread::stopRender() } - { QMutexLocker l(&_imp->startRequestsMutex); while (_imp->startRequests <= 0) { @@ -1003,6 +1000,7 @@ OutputSchedulerThread::run() bufferEmpty = _imp->buf.empty(); } + int expectedTimeToRender; while (!bufferEmpty) { if ( _imp->checkForExit() ) { @@ -1020,7 +1018,7 @@ OutputSchedulerThread::run() } } - int expectedTimeToRender = timelineGetTime(); + expectedTimeToRender = timelineGetTime(); BufferedFrames framesToRender; { @@ -1171,9 +1169,11 @@ OutputSchedulerThread::run() } 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 (blocking) { //Move the timeline to the last rendered frame to keep it in sync with what is displayed @@ -1181,7 +1181,7 @@ OutputSchedulerThread::run() } break; } - } + } // for (;;) stopRender(); From 9e29e37756946dd351fb9db732c3b1503e80ea9a Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Thu, 1 Oct 2015 14:46:15 +0200 Subject: [PATCH 066/178] Fix timeline seek --- Engine/OutputSchedulerThread.cpp | 42 ++++++++++++++++++++++++++------ Gui/Gui.h | 2 ++ Gui/Gui05.cpp | 2 +- Gui/Gui40.cpp | 13 ++++++++++ Gui/GuiPrivate.cpp | 1 + Gui/GuiPrivate.h | 2 ++ Gui/TimeLineGui.cpp | 17 ++++++------- Gui/TimeLineGui.h | 1 - 8 files changed, 62 insertions(+), 18 deletions(-) diff --git a/Engine/OutputSchedulerThread.cpp b/Engine/OutputSchedulerThread.cpp index 66073689f5..868c6ff4e5 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 { @@ -616,7 +621,6 @@ OutputSchedulerThread::pushFramesToRenderInternal(int startingFrame,int nThreads } 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); } + } @@ -867,7 +871,6 @@ OutputSchedulerThread::startRender() return; } } - ///Push as many frames as there are threads pushFramesToRender(startingFrame,nThreads); } @@ -1001,6 +1004,9 @@ OutputSchedulerThread::run() } int expectedTimeToRender; + bool isAbortRequested; + bool blocking; + while (!bufferEmpty) { if ( _imp->checkForExit() ) { @@ -1092,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); @@ -1160,13 +1174,14 @@ 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); @@ -1174,6 +1189,15 @@ OutputSchedulerThread::run() 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 @@ -1522,6 +1546,10 @@ OutputSchedulerThread::abortRendering(bool autoRestart,bool blocking) _imp->framesToRender.clear(); } + { + QMutexLocker k(&_imp->bufMutex); + _imp->buf.clear(); + } if (isMainThread) { diff --git a/Gui/Gui.h b/Gui/Gui.h index 96f07e503a..6d6551021a 100644 --- a/Gui/Gui.h +++ b/Gui/Gui.h @@ -613,6 +613,8 @@ public Q_SLOTS: ///Called whenever the time changes on the timeline void onTimeChanged(SequenceTime time,int reason); + + void onTimelineTimeAboutToChange(); void reloadStylesheet(); diff --git a/Gui/Gui05.cpp b/Gui/Gui05.cpp index d61c5da2ef..9d91a08f5d 100644 --- a/Gui/Gui05.cpp +++ b/Gui/Gui05.cpp @@ -140,7 +140,7 @@ Gui::setupUi() 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: diff --git a/Gui/Gui40.cpp b/Gui/Gui40.cpp index 8d8ac78d46..bc7fa85838 100644 --- a/Gui/Gui40.cpp +++ b/Gui/Gui40.cpp @@ -844,6 +844,19 @@ Gui::onEnableRenderStatsActionTriggered() } } + +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) diff --git a/Gui/GuiPrivate.cpp b/Gui/GuiPrivate.cpp index a0bc06ad98..2b5e48639e 100644 --- a/Gui/GuiPrivate.cpp +++ b/Gui/GuiPrivate.cpp @@ -250,6 +250,7 @@ GuiPrivate::GuiPrivate(GuiAppInstance* app, , statsDialog(0) , currentPanelFocus(0) , currentPanelFocusEventRecursion(0) +, wasLaskUserSeekDuringPlayback(false) { } diff --git a/Gui/GuiPrivate.h b/Gui/GuiPrivate.h index e821b8593e..cb06041135 100644 --- a/Gui/GuiPrivate.h +++ b/Gui/GuiPrivate.h @@ -271,6 +271,8 @@ struct GuiPrivate //To prevent recursion when we forward an uncaught event to the click focus widget int currentPanelFocusEventRecursion; + bool wasLaskUserSeekDuringPlayback; + GuiPrivate(GuiAppInstance* app, Gui* gui); diff --git a/Gui/TimeLineGui.cpp b/Gui/TimeLineGui.cpp index 69865f444c..52b80815b2 100644 --- a/Gui/TimeLineGui.cpp +++ b/Gui/TimeLineGui.cpp @@ -207,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; @@ -670,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(); } @@ -680,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(); } } @@ -746,7 +745,7 @@ TimeLineGui::mouseMoveEvent(QMouseEvent* e) 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; @@ -860,7 +859,7 @@ TimeLineGui::mouseReleaseEvent(QMouseEvent* e) if ( (tseq != _imp->timeline->currentFrame()) ) { _imp->gui->getApp()->setLastViewerUsingTimeline(_imp->viewer->getNode()); - Q_EMIT frameChanged(tseq); + _imp->timeline->onFrameChanged(tseq); } } else if (autoProxyEnabled && wasScrubbing) { diff --git a/Gui/TimeLineGui.h b/Gui/TimeLineGui.h index 75031dd0db..b339d93b2a 100644 --- a/Gui/TimeLineGui.h +++ b/Gui/TimeLineGui.h @@ -161,7 +161,6 @@ public Q_SLOTS: Q_SIGNALS: - void frameChanged(SequenceTime); void boundariesChanged(SequenceTime,SequenceTime); private: From d0364eff871ecc6c7430a12ecb683b5e4479cca9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Devernay?= Date: Thu, 1 Oct 2015 15:34:48 +0200 Subject: [PATCH 067/178] default font size should be at least 11 --- Engine/Settings.cpp | 2 +- Gui/GuiDefines.h | 2 ++ libs/OpenFX | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Engine/Settings.cpp b/Engine/Settings.cpp index 7504b14196..97820bef7a 100644 --- a/Engine/Settings.cpp +++ b/Engine/Settings.cpp @@ -1169,7 +1169,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); 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/libs/OpenFX b/libs/OpenFX index 5b79aab822..1ec2e6879c 160000 --- a/libs/OpenFX +++ b/libs/OpenFX @@ -1 +1 @@ -Subproject commit 5b79aab822b378814e1967236e3576b677ce3b96 +Subproject commit 1ec2e6879cddcd466315e44430361d6c19965a2a From 4a7e4010a702d66fcc03ea3c5ad54b68f5930236 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Devernay?= Date: Thu, 1 Oct 2015 15:38:06 +0200 Subject: [PATCH 068/178] fix warning --- Gui/PythonPanels.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Gui/PythonPanels.h b/Gui/PythonPanels.h index 41497d4d34..a6fbe3d4f4 100644 --- a/Gui/PythonPanels.h +++ b/Gui/PythonPanels.h @@ -103,7 +103,9 @@ class PyModalDialog : public QDialog, public UserParamHolder struct PyPanelPrivate; class PyPanel : public QWidget, public UserParamHolder, public PanelWidget { +GCC_DIAG_SUGGEST_OVERRIDE_OFF Q_OBJECT +GCC_DIAG_SUGGEST_OVERRIDE_ON public: From c7a5a9e7448bb578493d33bd9f0cd4529e23ab9a Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Thu, 1 Oct 2015 16:14:11 +0200 Subject: [PATCH 069/178] Call updateGL() only during playback, not when just refreshing the current frame --- Engine/OfxOverlayInteract.h | 4 ++-- Engine/OpenGLViewerI.h | 5 +++++ Engine/OutputSchedulerThread.cpp | 6 ++++-- Engine/ViewerInstance.cpp | 8 ++++++++ Engine/ViewerInstance.h | 2 ++ Gui/CurveWidget.cpp | 12 ++++++------ Gui/CustomParamInteract.cpp | 2 +- Gui/ViewerGL.cpp | 8 ++++++++ Gui/ViewerGL.h | 7 ++++++- 9 files changed, 42 insertions(+), 12 deletions(-) diff --git a/Engine/OfxOverlayInteract.h b/Engine/OfxOverlayInteract.h index 1fde026bc2..4ef171dfa1 100644 --- a/Engine/OfxOverlayInteract.h +++ b/Engine/OfxOverlayInteract.h @@ -103,7 +103,7 @@ class OfxOverlayInteract return n_swapBuffers(); } - /*Calls updateGL() on the attached viewer*/ + /*Calls update() on the attached viewer*/ virtual OfxStatus redraw() OVERRIDE FINAL WARN_UNUSED_RETURN; /// hooks to kOfxInteractPropViewportSize in the property set @@ -269,7 +269,7 @@ class OfxParamOverlayInteract return n_swapBuffers(); } - /*Calls updateGL() on all viewers*/ + /*Calls update() on all viewers*/ virtual OfxStatus redraw(); 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 868c6ff4e5..e252c43901 100644 --- a/Engine/OutputSchedulerThread.cpp +++ b/Engine/OutputSchedulerThread.cpp @@ -1563,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 { @@ -2468,8 +2468,10 @@ ViewerDisplayScheduler::processFrame(const BufferedFrames& frames) assert(params); _viewer->updateViewer(params); } + _viewer->redrawViewerNow(); + } else { + _viewer->redrawViewer(); } - _viewer->redrawViewer(); } diff --git a/Engine/ViewerInstance.cpp b/Engine/ViewerInstance.cpp index df436c8faa..dc366e0836 100644 --- a/Engine/ViewerInstance.cpp +++ b/Engine/ViewerInstance.cpp @@ -2708,6 +2708,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 diff --git a/Engine/ViewerInstance.h b/Engine/ViewerInstance.h index 867f8c3545..5a022ff049 100644 --- a/Engine/ViewerInstance.h +++ b/Engine/ViewerInstance.h @@ -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/Gui/CurveWidget.cpp b/Gui/CurveWidget.cpp index 0034396355..dc9b7a46a0 100644 --- a/Gui/CurveWidget.cpp +++ b/Gui/CurveWidget.cpp @@ -156,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, @@ -666,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; } @@ -724,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))) || @@ -768,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; } } @@ -836,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; } @@ -1542,7 +1542,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) ); } diff --git a/Gui/CustomParamInteract.cpp b/Gui/CustomParamInteract.cpp index b7b6ca4955..500db75e51 100644 --- a/Gui/CustomParamInteract.cpp +++ b/Gui/CustomParamInteract.cpp @@ -155,7 +155,7 @@ CustomParamInteract::swapOpenGLBuffers() void CustomParamInteract::redraw() { - updateGL(); + update(); } void diff --git a/Gui/ViewerGL.cpp b/Gui/ViewerGL.cpp index bdde9ff02a..3355e61e82 100644 --- a/Gui/ViewerGL.cpp +++ b/Gui/ViewerGL.cpp @@ -3296,6 +3296,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 885d382ab4..bdd221b2fc 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. From 710e5409df851686c5c690dc2fbcdbd545208420 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Thu, 1 Oct 2015 16:18:48 +0200 Subject: [PATCH 070/178] Missing include --- Gui/PanelWidget.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Gui/PanelWidget.cpp b/Gui/PanelWidget.cpp index 4b23ae02d6..b2bbd2e9fc 100644 --- a/Gui/PanelWidget.cpp +++ b/Gui/PanelWidget.cpp @@ -26,6 +26,7 @@ #include #include "Gui/TabWidget.h" +#include "Gui/Gui.h" PanelWidget::PanelWidget(QWidget* thisWidget, Gui* gui) From eefb9d63d038a434ac7becdd3ac2cd90fdf0b19a Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Thu, 1 Oct 2015 16:22:20 +0200 Subject: [PATCH 071/178] Update global.pi --- global.pri | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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) { From 05f3b485a95d09197da1e03008976e234cb29df9 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Thu, 1 Oct 2015 16:30:30 +0200 Subject: [PATCH 072/178] Fix overlay interact entitlement when plugin only has a default overlay --- Engine/Node.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Engine/Node.cpp b/Engine/Node.cpp index 9305055639..5ef1040ddc 100644 --- a/Engine/Node.cpp +++ b/Engine/Node.cpp @@ -1865,6 +1865,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(); } From 3891158f3292f4fda671351461f5a3f04aa49d33 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Thu, 1 Oct 2015 17:04:58 +0200 Subject: [PATCH 073/178] Default overlay: don't draw if secret recursive. Pages: set secret when tab is not current --- Engine/Knob.cpp | 13 +++++++++++++ Engine/Knob.h | 6 ++++++ Gui/DefaultOverlays.cpp | 15 +++++++++++++-- Gui/DockablePanel.cpp | 35 +++++++++++++++++++++++++++++++++-- Gui/DockablePanel.h | 2 ++ Gui/DockablePanelPrivate.cpp | 30 ++++++++++++++++++------------ Gui/DockablePanelPrivate.h | 2 +- 7 files changed, 86 insertions(+), 17 deletions(-) diff --git a/Engine/Knob.cpp b/Engine/Knob.cpp index a018ecf807..666a4f46ec 100644 --- a/Engine/Knob.cpp +++ b/Engine/Knob.cpp @@ -2271,6 +2271,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 { diff --git a/Engine/Knob.h b/Engine/Knob.h index 4ce9f952bf..d5cdd64636 100644 --- a/Engine/Knob.h +++ b/Engine/Knob.h @@ -786,6 +786,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. @@ -1192,6 +1197,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; diff --git a/Gui/DefaultOverlays.cpp b/Gui/DefaultOverlays.cpp index 7937598c2d..332b852e25 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; diff --git a/Gui/DockablePanel.cpp b/Gui/DockablePanel.cpp index 6e69a99e66..055af871d3 100644 --- a/Gui/DockablePanel.cpp +++ b/Gui/DockablePanel.cpp @@ -432,11 +432,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 +461,36 @@ 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; + } + + std::string stdName = name.toStdString(); + + const std::vector >& knobs = _imp->_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) { + isPage->setSecret(false); + } else { + isPage->setSecret(true); + } + } + Natron::EffectInstance* isEffect = dynamic_cast(_imp->_holder); + if (isEffect && isEffect->getNode()->hasOverlay()) { + isEffect->getApp()->redrawAllViewers(); + } +} + void DockablePanel::turnOffPages() { @@ -469,7 +500,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()); } 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..2142453fdb 100644 --- a/Gui/DockablePanelPrivate.cpp +++ b/Gui/DockablePanelPrivate.cpp @@ -242,7 +242,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 +256,7 @@ DockablePanelPrivate::ensureDefaultPageKnobCreated() pk = boost::dynamic_pointer_cast(knob); } assert(pk); - addPage(pk.get(), _defaultPageName); + getOrCreatePage(pk.get()); return pk; } @@ -269,7 +269,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 +288,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 +323,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 +355,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 +393,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 +410,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 +442,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 +534,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 +646,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() ) { diff --git a/Gui/DockablePanelPrivate.h b/Gui/DockablePanelPrivate.h index e15798043c..c770e8b68c 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() ; From 3c57dc0cde865e204a0f0ef2d2e796ada8ab393f Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Thu, 1 Oct 2015 17:28:48 +0200 Subject: [PATCH 074/178] Fix build --- Gui/DefaultOverlays.cpp | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/Gui/DefaultOverlays.cpp b/Gui/DefaultOverlays.cpp index 332b852e25..f54aa5c1e5 100644 --- a/Gui/DefaultOverlays.cpp +++ b/Gui/DefaultOverlays.cpp @@ -199,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() ); @@ -221,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); } } @@ -243,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; @@ -279,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::eValueChangedReasonUserEdited); + } } if (didSomething || valuesChanged) { return true; @@ -307,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::eValueChangedReasonUserEdited); + } } it->state = ePositionInteractStateInactive; @@ -404,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; } } @@ -415,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; } From 22a4d5e19e6fb18f00fd513b45acf20a0ad746e9 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Thu, 1 Oct 2015 17:33:23 +0200 Subject: [PATCH 075/178] Fix reasons --- Gui/DefaultOverlays.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gui/DefaultOverlays.cpp b/Gui/DefaultOverlays.cpp index f54aa5c1e5..3572fbc2d3 100644 --- a/Gui/DefaultOverlays.cpp +++ b/Gui/DefaultOverlays.cpp @@ -286,7 +286,7 @@ DefaultOverlay::penMotion(double time, double y = fround(_imp->lastPenPos.y(), pscale.y); boost::shared_ptr param = it->param.lock(); if (param) { - param->setValues(x, y, Natron::eValueChangedReasonUserEdited); + param->setValues(x, y, Natron::eValueChangedReasonNatronGuiEdited); } } if (didSomething || valuesChanged) { @@ -316,7 +316,7 @@ DefaultOverlay::penUp(double time, double y = fround(_imp->lastPenPos.y(), pscale.y); boost::shared_ptr param = it->param.lock(); if (param) { - param->setValues(x, y, Natron::eValueChangedReasonUserEdited); + param->setValues(x, y, Natron::eValueChangedReasonNatronGuiEdited); } } From 14b1b5f8835e7e8d993b549165115b79a64f7c27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Devernay?= Date: Thu, 1 Oct 2015 17:35:16 +0200 Subject: [PATCH 076/178] getFramesNeeded_public: only set persistent message if none is present --- Engine/EffectInstance.cpp | 10 +++++++++- Engine/EffectInstance.h | 5 +++++ Engine/OfxEffectInstance.cpp | 2 +- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/Engine/EffectInstance.cpp b/Engine/EffectInstance.cpp index a2bb5901f7..799098ff3c 100644 --- a/Engine/EffectInstance.cpp +++ b/Engine/EffectInstance.cpp @@ -2520,6 +2520,12 @@ EffectInstance::setPersistentMessage(Natron::MessageTypeEnum type, getNode()->setPersistentMessage(type, content); } +bool +EffectInstance::hasPersistentMessage() +{ + return getNode()->hasPersistentMessage(); +} + void EffectInstance::clearPersistentMessage(bool recurse) { @@ -3165,7 +3171,9 @@ EffectInstance::getFramesNeeded_public(U64 hash, try { framesNeeded = getFramesNeeded(time, view); } catch (std::exception &e) { - setPersistentMessage(Natron::eMessageTypeError, e.what()); + if (!hasPersistentMessage()) { // plugin may already have set a message + setPersistentMessage(Natron::eMessageTypeError, e.what()); + } } _imp->actionsCache.setFramesNeededResult(hash, time, view, mipMapLevel, framesNeeded); diff --git a/Engine/EffectInstance.h b/Engine/EffectInstance.h index aa71e57efc..fc9c838aa2 100644 --- a/Engine/EffectInstance.h +++ b/Engine/EffectInstance.h @@ -1088,6 +1088,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. **/ diff --git a/Engine/OfxEffectInstance.cpp b/Engine/OfxEffectInstance.cpp index 31a470bb09..eff756deed 100644 --- a/Engine/OfxEffectInstance.cpp +++ b/Engine/OfxEffectInstance.cpp @@ -1644,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); From 4ce0d971bb3878a101c886a0e59e2b6b644a0552 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Thu, 1 Oct 2015 20:15:55 +0200 Subject: [PATCH 077/178] PyPlugs are now locked for editing when instantiated. The user must explicitly unlock them with a button --- .../PythonReference/NatronEngine/Effect.rst | 2 + Engine/AppInstance.cpp | 41 ++++++++------ Engine/AppInstance.h | 1 - Engine/Node.cpp | 55 ++++++++++--------- Engine/Node.h | 10 ++-- Engine/NodeGroupSerialization.cpp | 3 - Engine/NodeGuiI.h | 2 + Engine/NodeSerialization.cpp | 7 ++- Engine/NodeWrapper.cpp | 19 +++++++ Engine/NodeWrapper.h | 20 ++----- Gui/DockablePanel.cpp | 15 ++++- Gui/NodeGraph.cpp | 37 +++++++++++++ Gui/NodeGraph10.cpp | 53 ++++++++++++++++-- Gui/NodeGraph20.cpp | 27 ++++++++- Gui/NodeGraph25.cpp | 15 ++++- Gui/NodeGraphPrivate.cpp | 7 +++ Gui/NodeGraphPrivate.h | 4 ++ Gui/NodeGui.cpp | 16 ++++++ Gui/NodeGui.h | 2 + 19 files changed, 256 insertions(+), 80 deletions(-) diff --git a/Documentation/source/PythonReference/NatronEngine/Effect.rst b/Documentation/source/PythonReference/NatronEngine/Effect.rst index efb3fe6c64..44d0d1b0a0 100644 --- a/Documentation/source/PythonReference/NatronEngine/Effect.rst +++ b/Documentation/source/PythonReference/NatronEngine/Effect.rst @@ -473,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/Engine/AppInstance.cpp b/Engine/AppInstance.cpp index 83ae367858..a9d39bc2fb 100644 --- a/Engine/AppInstance.cpp +++ b/Engine/AppInstance.cpp @@ -635,18 +635,30 @@ 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); + } else { + + LoadNodeArgs groupArgs(PLUGINID_NATRON_GROUP, + "", + -1,-1, + &serialization, + false, + group); + containerNode = loadNode(groupArgs); + } if (!containerNode) { return containerNode; } @@ -677,14 +689,11 @@ AppInstance::createNodeFromPythonModule(Natron::Plugin* plugin, } node = containerNode; } - if (requestedByLoad) { - containerNode->loadSerializationForPyPlug(serialization); - node = containerNode; - } if (!moduleName.isEmpty()) { setGroupLabelIDAndVersion(node,modulePath, moduleName); } + } //FlagSetter fs(true,&_imp->_creatingGroup,&_imp->creatingGroupMutex); diff --git a/Engine/AppInstance.h b/Engine/AppInstance.h index 1efd38c1da..246e374a76 100644 --- a/Engine/AppInstance.h +++ b/Engine/AppInstance.h @@ -217,7 +217,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; diff --git a/Engine/Node.cpp b/Engine/Node.cpp index 5ef1040ddc..212edb7b4d 100644 --- a/Engine/Node.cpp +++ b/Engine/Node.cpp @@ -258,6 +258,7 @@ struct Node::Implementation , pluginPythonModuleMutex() , pluginPythonModule() , pluginPythonModuleVersion(0) + , pyplugChangedSinceScript(false) , nodeCreated(false) , createdComponentsMutex() , createdComponents() @@ -474,6 +475,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; @@ -607,17 +611,7 @@ 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) { @@ -674,7 +668,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()) { @@ -1337,19 +1341,6 @@ Node::setValuesFromSerialization(const std::listliveInstance->refreshKnobs(); - } - - //Also restore script name/label (overwriting what is written in the PyPlug) - setScriptName_no_error_check(serialization.getNodeScriptName()); - setLabel(serialization.getNodeLabel()); -} - void Node::loadKnobs(const NodeSerialization & serialization,bool updateKnobGui) { @@ -5737,6 +5728,20 @@ Node::setPluginIDAndVersionForGui(const std::string& pluginLabel,const std::stri } +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; +} + void Node::setPluginPythonModule(const std::string& pythonModule, unsigned int version) { diff --git a/Engine/Node.h b/Engine/Node.h index c998cca424..d18df5177c 100644 --- a/Engine/Node.h +++ b/Engine/Node.h @@ -132,12 +132,7 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON const QString& fixedName, const std::list >& paramValues); - - /* - * @brief To be called on all nodes inside a PyPlug and the group node itself to load remaining user changes made on top of - * the default configuration that is coded in the Python script. This may for example include - */ - void loadSerializationForPyPlug(const NodeSerialization & serialization); + ///called by load() and OfxEffectInstance, do not call this! void loadKnobs(const NodeSerialization & serialization,bool updateKnobGui = false); @@ -1015,6 +1010,9 @@ 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; diff --git a/Engine/NodeGroupSerialization.cpp b/Engine/NodeGroupSerialization.cpp index 26eca37616..20d028791f 100644 --- a/Engine/NodeGroupSerialization.cpp +++ b/Engine/NodeGroupSerialization.cpp @@ -216,9 +216,6 @@ NodeCollectionSerialization::restoreFromSerialization(const std::list< boost::sh ///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->loadSerializationForPyPlug(**it); - } } int majorVersion,minorVersion; 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/NodeWrapper.cpp b/Engine/NodeWrapper.cpp index 26967fa1e7..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) 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/Gui/DockablePanel.cpp b/Gui/DockablePanel.cpp index 055af871d3..e62e53ca79 100644 --- a/Gui/DockablePanel.cpp +++ b/Gui/DockablePanel.cpp @@ -695,6 +695,17 @@ DockablePanel::initializeKnobsInternal() 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); + } + } + } + } void @@ -1473,9 +1484,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/NodeGraph.cpp b/Gui/NodeGraph.cpp index 2a8e4d0d95..0ba8279300 100644 --- a/Gui/NodeGraph.cpp +++ b/Gui/NodeGraph.cpp @@ -283,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(); @@ -302,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 diff --git a/Gui/NodeGraph10.cpp b/Gui/NodeGraph10.cpp index 6a53c9ac42..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" @@ -66,7 +67,7 @@ NodeGraph::mousePressEvent(QMouseEvent* e) return; } - + bool didSomething = false; _imp->_lastMousePos = e->pos(); @@ -81,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; @@ -98,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; @@ -133,6 +173,7 @@ NodeGraph::mousePressEvent(QMouseEvent* e) } } + if (selected) { didSomething = true; if ( buttonDownIsLeft(e) ) { @@ -152,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 @@ -171,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 @@ -242,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; @@ -266,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/NodeGraph20.cpp b/Gui/NodeGraph20.cpp index 219f24b7b1..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; diff --git a/Gui/NodeGraph25.cpp b/Gui/NodeGraph25.cpp index 786619846c..c2aefbf9c1 100644 --- a/Gui/NodeGraph25.cpp +++ b/Gui/NodeGraph25.cpp @@ -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); @@ -232,6 +232,19 @@ 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(); diff --git a/Gui/NodeGraphPrivate.cpp b/Gui/NodeGraphPrivate.cpp index a0e2006504..888e0165a3 100644 --- a/Gui/NodeGraphPrivate.cpp +++ b/Gui/NodeGraphPrivate.cpp @@ -88,6 +88,13 @@ NodeGraphPrivate::NodeGraphPrivate(NodeGraph* p, , _hasMovedOnce(false) , lastSelectedViewer(0) { + 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 6b8275995d..3359e81856 100644 --- a/Gui/NodeGraphPrivate.h +++ b/Gui/NodeGraphPrivate.h @@ -228,9 +228,13 @@ struct NodeGraphPrivate ViewerTab* lastSelectedViewer; + QPixmap unlockIcon; + NodeGraphPrivate(NodeGraph* p, const boost::shared_ptr& group); + QPoint getPyPlugUnlockPos() const; + void resetAllClipboards(); QRectF calcNodesBoundingRect(); diff --git a/Gui/NodeGui.cpp b/Gui/NodeGui.cpp index 693d9e5747..847a2c39f7 100644 --- a/Gui/NodeGui.cpp +++ b/Gui/NodeGui.cpp @@ -421,6 +421,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() { diff --git a/Gui/NodeGui.h b/Gui/NodeGui.h index e3671a23d7..d669d260ae 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. From ec21afa831a43d13fcba95ea4ffd880577113a24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole-Andr=C3=A9=20Rodlie?= Date: Thu, 1 Oct 2015 22:03:01 +0200 Subject: [PATCH 078/178] Linux: qt build options --- tools/linux/include/scripts/build-sdk.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/linux/include/scripts/build-sdk.sh b/tools/linux/include/scripts/build-sdk.sh index 437651cd60..3e51ee9d63 100644 --- a/tools/linux/include/scripts/build-sdk.sh +++ b/tools/linux/include/scripts/build-sdk.sh @@ -927,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 From 7ca3727de3d70e7057f4b90b5f063c501a39db04 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Fri, 2 Oct 2015 11:06:53 +0200 Subject: [PATCH 079/178] Add auto contrast icon --- Global/Enums.h | 2 ++ Gui/GuiApplicationManager.cpp | 6 ++++++ Gui/GuiResources.qrc | 2 ++ Gui/Resources/Images/AutoContrast.png | Bin 0 -> 1667 bytes Gui/Resources/Images/AutoContrastON.png | Bin 0 -> 2225 bytes Gui/ViewerTab.cpp | 19 ++++++++++++------- Gui/ViewerTab30.cpp | 2 ++ Gui/ViewerTabPrivate.cpp | 1 - Gui/ViewerTabPrivate.h | 3 +-- 9 files changed, 25 insertions(+), 10 deletions(-) create mode 100644 Gui/Resources/Images/AutoContrast.png create mode 100644 Gui/Resources/Images/AutoContrastON.png diff --git a/Global/Enums.h b/Global/Enums.h index 311f344335..3e4b534d5c 100644 --- a/Global/Enums.h +++ b/Global/Enums.h @@ -156,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/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/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/Resources/Images/AutoContrast.png b/Gui/Resources/Images/AutoContrast.png new file mode 100644 index 0000000000000000000000000000000000000000..c8082a4dddaef277de7ac3b2caf10d211c9cf136 GIT binary patch literal 1667 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 literal 0 HcmV?d00001 diff --git a/Gui/ViewerTab.cpp b/Gui/ViewerTab.cpp index 73258495e7..06db85cd90 100644 --- a/Gui/ViewerTab.cpp +++ b/Gui/ViewerTab.cpp @@ -403,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); @@ -943,8 +950,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) ) ); diff --git a/Gui/ViewerTab30.cpp b/Gui/ViewerTab30.cpp index dd7d96af84..6ac81cde98 100644 --- a/Gui/ViewerTab30.cpp +++ b/Gui/ViewerTab30.cpp @@ -292,6 +292,8 @@ ViewerTab::discardInternalNodePointer() 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); diff --git a/Gui/ViewerTabPrivate.cpp b/Gui/ViewerTabPrivate.cpp index db700a6213..3674a29bbd 100644 --- a/Gui/ViewerTabPrivate.cpp +++ b/Gui/ViewerTabPrivate.cpp @@ -82,7 +82,6 @@ ViewerTabPrivate::ViewerTabPrivate(ViewerTab* publicInterface,ViewerInstance* no , gainBox(NULL) , gainSlider(NULL) , lastFstopValue(0.) -, autoConstrastLabel(NULL) , autoContrast(NULL) , gammaBox(NULL) , lastGammaValue(1.) diff --git a/Gui/ViewerTabPrivate.h b/Gui/ViewerTabPrivate.h index 79213b0035..e45ada8b2a 100644 --- a/Gui/ViewerTabPrivate.h +++ b/Gui/ViewerTabPrivate.h @@ -127,8 +127,7 @@ struct ViewerTabPrivate SpinBox* gainBox; ScaleSliderQWidget* gainSlider; double lastFstopValue; - ClickableLabel* autoConstrastLabel; - QCheckBox* autoContrast; + Button* autoContrast; SpinBox* gammaBox; double lastGammaValue; Button* toggleGammaButton; From 063f352cff12fdac8e2803f46a1f1f5bf2fc4fca Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Fri, 2 Oct 2015 11:26:46 +0200 Subject: [PATCH 080/178] Roto: accept event when pressing tools shortcuts --- Gui/RotoGui.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Gui/RotoGui.cpp b/Gui/RotoGui.cpp index 4beb705907..8fb6320639 100644 --- a/Gui/RotoGui.cpp +++ b/Gui/RotoGui.cpp @@ -3557,29 +3557,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 +3614,7 @@ RotoGui::keyDown(double /*time*/, didSomething = true; } else if ( isKeybind(kShortcutGroupRoto, kShortcutIDActionRotoLockCurve, modifiers, key) ) { lockSelectedCurves(); + didSomething = true; } return didSomething; From 24d1aad2d274cb637b26359bc97aa5003c28ce68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole-Andr=C3=A9=20Rodlie?= Date: Fri, 2 Oct 2015 11:30:35 +0200 Subject: [PATCH 081/178] Linux: added rpm spec --- tools/linux/include/natron/Natron.spec | 39 ++++++++++++++++++++++ tools/linux/include/natron/Natron2.desktop | 12 +++++++ 2 files changed, 51 insertions(+) create mode 100644 tools/linux/include/natron/Natron.spec create mode 100644 tools/linux/include/natron/Natron2.desktop diff --git a/tools/linux/include/natron/Natron.spec b/tools/linux/include/natron/Natron.spec new file mode 100644 index 0000000000..03e7911739 --- /dev/null +++ b/tools/linux/include/natron/Natron.spec @@ -0,0 +1,39 @@ +%define _binary_payload w7.xzdio + +Summary: open-source compositing software +Name: Natron + +Version: 2.0.0 +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 . + +%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; + From ac8854a39a8146cb465487da094bf5114e6ded12 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Fri, 2 Oct 2015 11:49:16 +0200 Subject: [PATCH 082/178] Fix compilation --- Gui/PanelWidget.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gui/PanelWidget.h b/Gui/PanelWidget.h index 885c2118cf..f3d2ea0744 100644 --- a/Gui/PanelWidget.h +++ b/Gui/PanelWidget.h @@ -25,14 +25,14 @@ #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 "Global/Macros.h" - #include "Engine/ScriptObject.h" class Gui; From 2c992882c5656f4dda2a2f44e15e1be79132bf6b Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Fri, 2 Oct 2015 11:49:27 +0200 Subject: [PATCH 083/178] Fix smear for multi-stroke + show focus rect in roto tree --- Engine/RotoSmear.cpp | 13 ++++++++++--- Gui/Gui50.cpp | 4 +--- Gui/MultiInstancePanel.cpp | 2 +- Gui/RotoPanel.cpp | 2 +- 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/Engine/RotoSmear.cpp b/Engine/RotoSmear.cpp index b33e6eb0f0..c3e56a5d53 100644 --- a/Engine/RotoSmear.cpp +++ b/Engine/RotoSmear.cpp @@ -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,9 @@ RotoSmear::render(const RenderActionArgs& args) bool didPaint = false; double distToNext = 0.; + ImagePtr 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 +324,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) { 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) { //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 +371,7 @@ RotoSmear::render(const RenderActionArgs& args) cur = _imp->lastCur; } + isFirstStrokeTick = false; while (it!=visiblePortion.end()) { diff --git a/Gui/Gui50.cpp b/Gui/Gui50.cpp index 0c09152b47..01d0363264 100644 --- a/Gui/Gui50.cpp +++ b/Gui/Gui50.cpp @@ -898,9 +898,7 @@ Gui::isFocusStealingPossible() dynamic_cast(currentFocus) || dynamic_cast(currentFocus) || dynamic_cast(currentFocus) || - dynamic_cast(currentFocus) || - dynamic_cast(currentFocus) || - dynamic_cast(currentFocus); + dynamic_cast(currentFocus); return !focusStealingNotPossible; 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/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" From f164aa88294727074f39077d83e0c06ce10aa681 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Fri, 2 Oct 2015 12:32:56 +0200 Subject: [PATCH 084/178] Really fix Smear for multi-stroke when drawing --- Engine/Node.cpp | 11 ++++++++--- Engine/Node.h | 5 +++-- Engine/RotoSmear.cpp | 15 ++++++++++----- Engine/RotoStrokeItem.cpp | 4 +++- Engine/RotoStrokeItem.h | 3 ++- Engine/ViewerInstance.cpp | 5 +++-- 6 files changed, 29 insertions(+), 14 deletions(-) diff --git a/Engine/Node.cpp b/Engine/Node.cpp index 212edb7b4d..2081c9e23c 100644 --- a/Engine/Node.cpp +++ b/Engine/Node.cpp @@ -276,6 +276,7 @@ struct Node::Implementation , lastStrokeMovementBbox() , strokeBitmapCleared(false) , lastStrokeIndex(-1) + , multiStrokeIndex(0) , strokeImage() , lastStrokePoints() , distToNextIn(0.) @@ -497,7 +498,7 @@ struct Node::Implementation mutable QMutex lastStrokeMovementMutex; RectD lastStrokeMovementBbox; bool strokeBitmapCleared; - int lastStrokeIndex; + int lastStrokeIndex,multiStrokeIndex; ImagePtr strokeImage; std::list > lastStrokePoints; double distToNextIn,distToNextOut; @@ -867,7 +868,8 @@ Node::refreshDynamicProperties() void Node::updateLastPaintStrokeData(int newAge,const std::list >& points, - const RectD& lastPointsBbox) + const RectD& lastPointsBbox, + int strokeIndex) { { @@ -877,6 +879,7 @@ Node::updateLastPaintStrokeData(int newAge,const std::listlastStrokeIndex = newAge; _imp->distToNextIn = _imp->distToNextOut; _imp->strokeBitmapCleared = false; + _imp->multiStrokeIndex = strokeIndex; } _imp->liveInstance->clearActionsCache(); } @@ -951,16 +954,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; } } diff --git a/Engine/Node.h b/Engine/Node.h index d18df5177c..8b3916b1e9 100644 --- a/Engine/Node.h +++ b/Engine/Node.h @@ -424,7 +424,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); @@ -439,7 +440,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, diff --git a/Engine/RotoSmear.cpp b/Engine/RotoSmear.cpp index c3e56a5d53..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; @@ -314,7 +314,12 @@ RotoSmear::render(const RenderActionArgs& args) bool didPaint = false; double distToNext = 0.; - ImagePtr bgImg = getImage(0, args.time, args.mappedScale, args.view, 0, foundBg->second.front(), Natron::eImageBitDepthFloat, par, false, &bgImgRoI); + 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(); @@ -324,7 +329,7 @@ RotoSmear::render(const RenderActionArgs& args) int nComps = plane->first.getNumComponents(); - if (!bgImg && !bgInitialized) { + if (!bgImg && !bgInitialized && strokeIndex == 0) { plane->second->fillZero(args.roi); bgInitialized = true; continue; @@ -332,7 +337,7 @@ RotoSmear::render(const RenderActionArgs& args) //First copy the source image if this is the first stroke tick - if ((isFirstStrokeTick || !duringPainting) && !bgInitialized) { + if ((isFirstStrokeTick || !duringPainting) && !bgInitialized && strokeIndex == 0) { //Make sure all areas are black and transparant plane->second->fillZero(args.roi); diff --git a/Engine/RotoStrokeItem.cpp b/Engine/RotoStrokeItem.cpp index 4eafb673c5..8d771ba10e 100644 --- a/Engine/RotoStrokeItem.cpp +++ b/Engine/RotoStrokeItem.cpp @@ -608,7 +608,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 +626,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/ViewerInstance.cpp b/Engine/ViewerInstance.cpp index dc366e0836..6706fdd4b5 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); From e7ee36ad5695c250e8bb37fae230a57ca02561d4 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Fri, 2 Oct 2015 12:40:59 +0200 Subject: [PATCH 085/178] Fix spinbox style --- Gui/Resources/Stylesheets/mainstyle.qss | 7 +------ Gui/ViewerTab.cpp | 2 +- Gui/ViewerTab40.cpp | 2 +- 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/Gui/Resources/Stylesheets/mainstyle.qss b/Gui/Resources/Stylesheets/mainstyle.qss index d6bdfe40bf..065929d594 100644 --- a/Gui/Resources/Stylesheets/mainstyle.qss +++ b/Gui/Resources/Stylesheets/mainstyle.qss @@ -554,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/ViewerTab.cpp b/Gui/ViewerTab.cpp index 06db85cd90..f03f4a4f00 100644 --- a/Gui/ViewerTab.cpp +++ b/Gui/ViewerTab.cpp @@ -750,7 +750,7 @@ 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->fpsBox->setIncrement(0.1); diff --git a/Gui/ViewerTab40.cpp b/Gui/ViewerTab40.cpp index 307fe38fa5..52050b3a91 100644 --- a/Gui/ViewerTab40.cpp +++ b/Gui/ViewerTab40.cpp @@ -220,7 +220,7 @@ ViewerTab::onCanSetFPSLabelClicked(bool toggled) void ViewerTab::onCanSetFPSClicked(bool toggled) { - _imp->fpsBox->setReadOnly(!toggled); + _imp->fpsBox->setEnabled(toggled); { QMutexLocker l(&_imp->fpsLockedMutex); _imp->fpsLocked = !toggled; From bd2f4a13a37f3d12248fba6104f9f8f72caba9bb Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Fri, 2 Oct 2015 12:51:46 +0200 Subject: [PATCH 086/178] Fix #927 --- Gui/ViewerTab.cpp | 3 ++- Gui/ViewerTab.h | 4 ++++ Gui/ViewerTab30.cpp | 37 +++++++++++++++++++++---------------- Gui/ViewerTab40.cpp | 26 +++++++++++++++++++++++--- Gui/ViewerTabPrivate.cpp | 1 + Gui/ViewerTabPrivate.h | 1 + 6 files changed, 52 insertions(+), 20 deletions(-) diff --git a/Gui/ViewerTab.cpp b/Gui/ViewerTab.cpp index f03f4a4f00..ab422e41e2 100644 --- a/Gui/ViewerTab.cpp +++ b/Gui/ViewerTab.cpp @@ -752,7 +752,8 @@ ViewerTab::ViewerTab(const std::list & existingRotoNodes, _imp->fpsBox = new SpinBox(_imp->playerButtonsContainer,SpinBox::eSpinBoxTypeDouble); _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( "Viewer playback framerate, in frames per second.") ); diff --git a/Gui/ViewerTab.h b/Gui/ViewerTab.h index 98ac7c478e..90d1a7e4be 100644 --- a/Gui/ViewerTab.h +++ b/Gui/ViewerTab.h @@ -433,6 +433,10 @@ public Q_SLOTS: private: + void refreshFPSBoxFromClipPreferences(); + + void onSpinboxFpsChangedInternal(double fps); + void onPickerButtonClickedInternal(ViewerTab* caller,bool); void onCompositingOperatorChangedInternal(Natron::ViewerCompositingOperatorEnum oldOp,Natron::ViewerCompositingOperatorEnum newOp); diff --git a/Gui/ViewerTab30.cpp b/Gui/ViewerTab30.cpp index 6ac81cde98..e78159554d 100644 --- a/Gui/ViewerTab30.cpp +++ b/Gui/ViewerTab30.cpp @@ -1007,26 +1007,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 52050b3a91..da0c56049f 100644 --- a/Gui/ViewerTab40.cpp +++ b/Gui/ViewerTab40.cpp @@ -225,6 +225,12 @@ ViewerTab::onCanSetFPSClicked(bool toggled) QMutexLocker l(&_imp->fpsLockedMutex); _imp->fpsLocked = !toggled; } + + if (toggled) { + onSpinboxFpsChangedInternal(_imp->userFps); + } else { + refreshFPSBoxFromClipPreferences(); + } } @@ -640,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 @@ -660,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); diff --git a/Gui/ViewerTabPrivate.cpp b/Gui/ViewerTabPrivate.cpp index 3674a29bbd..c807ea7755 100644 --- a/Gui/ViewerTabPrivate.cpp +++ b/Gui/ViewerTabPrivate.cpp @@ -120,6 +120,7 @@ ViewerTabPrivate::ViewerTabPrivate(ViewerTab* publicInterface,ViewerInstance* no , fpsLockedMutex() , fpsLocked(true) , fpsBox(NULL) +, userFps(24) , turboButton(NULL) , timeLineGui(NULL) , rotoNodes() diff --git a/Gui/ViewerTabPrivate.h b/Gui/ViewerTabPrivate.h index e45ada8b2a..d7e6187a18 100644 --- a/Gui/ViewerTabPrivate.h +++ b/Gui/ViewerTabPrivate.h @@ -173,6 +173,7 @@ struct ViewerTabPrivate mutable QMutex fpsLockedMutex; bool fpsLocked; SpinBox* fpsBox; + double userFps; Button* turboButton; /*frame seeker*/ From f0179d1a2288728f7cd879891579dd180f83eb4b Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Fri, 2 Oct 2015 15:19:51 +0200 Subject: [PATCH 087/178] Call instanceChanged at the time the change occurred not the time on the timeline. --- Engine/Knob.cpp | 79 ++++++++++++++++++++----------------- Engine/Knob.h | 14 ++++--- Engine/KnobImpl.h | 49 +++++++++++++++++------ Engine/Node.cpp | 5 ++- Engine/OfxClipInstance.cpp | 2 +- Engine/OfxParamInstance.cpp | 2 +- Engine/RotoDrawableItem.cpp | 2 +- Gui/CurveEditorUndoRedo.cpp | 8 ++-- Gui/KnobGui.h | 26 ++++++++++++ Gui/KnobGuiButton.cpp | 4 +- Gui/KnobGuiColor.cpp | 2 +- Gui/KnobGuiFile.cpp | 4 +- Gui/KnobUndoCommand.cpp | 53 +++++++++++++++++-------- 13 files changed, 166 insertions(+), 84 deletions(-) diff --git a/Engine/Knob.cpp b/Engine/Knob.cpp index 666a4f46ec..c589d5c64a 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); } } @@ -2544,7 +2547,7 @@ KnobHelper::slaveTo(int dimension, } if (hasChanged) { - evaluateValueChange(dimension, reason); + evaluateValueChange(dimension, getCurrentTime(), reason); } ///Register this as a listener of the master @@ -2687,7 +2690,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()) { @@ -2825,7 +2828,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; @@ -2851,6 +2854,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(); @@ -2858,7 +2862,7 @@ KnobHelper::onExprDependencyChanged(KnobI* knob,int dimensionChanged) _imp->mustClearExprResults[*it] = true; } else { clearExpressionsResults(*it); - evaluateValueChange(*it, Natron::eValueChangedReasonSlaveRefresh); + evaluateValueChange(*it, time, Natron::eValueChangedReasonSlaveRefresh); } } } @@ -3739,7 +3743,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); @@ -3751,9 +3755,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); } } @@ -3812,25 +3816,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 d5cdd64636..c602aa454f 100644 --- a/Engine/Knob.h +++ b/Engine/Knob.h @@ -427,7 +427,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 @@ -1121,7 +1121,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; @@ -1447,8 +1447,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. @@ -1459,8 +1463,6 @@ class Knob Natron::ValueChangedReasonEnum reason, KeyFrame* newKey); -public: - /** * @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 @@ -1979,7 +1981,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; diff --git a/Engine/KnobImpl.h b/Engine/KnobImpl.h index 94619a23db..e49d989a0a 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; } @@ -1392,7 +1416,7 @@ Knob::unSlave(int dimension, getHolder()->onKnobSlaved( this, master.second.get(),dimension,false ); } if (hasChanged) { - evaluateValueChange(dimension, reason); + evaluateValueChange(dimension, getCurrentTime(), reason); } } @@ -1736,7 +1760,7 @@ Knob::onKeyFrameSet(SequenceTime time, if (!useGuiCurve) { guiCurveCloneInternalCurve(Natron::eCurveChangeReasonInternal,dimension, Natron::eValueChangedReasonUserEdited); - evaluateValueChange(dimension, Natron::eValueChangedReasonUserEdited); + evaluateValueChange(dimension, time, Natron::eValueChangedReasonUserEdited); } return ret; } @@ -1764,7 +1788,7 @@ Knob::onKeyFrameSet(SequenceTime /*time*/,const KeyFrame& key,int dimension) if (!useGuiCurve) { guiCurveCloneInternalCurve(Natron::eCurveChangeReasonInternal,dimension, Natron::eValueChangedReasonUserEdited); - evaluateValueChange(dimension, Natron::eValueChangedReasonUserEdited); + evaluateValueChange(dimension, key.getTime(), Natron::eValueChangedReasonUserEdited); } return ret; } @@ -2549,8 +2573,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/Node.cpp b/Engine/Node.cpp index 2081c9e23c..0eeecf9001 100644 --- a/Engine/Node.cpp +++ b/Engine/Node.cpp @@ -747,11 +747,12 @@ Node::load(const std::string & parentMultiInstanceName, ///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, Natron::eValueChangedReasonUserEdited); + knob->evaluateValueChange(i, time, Natron::eValueChangedReasonUserEdited); } } else { qDebug() << "WARNING: No such parameter " << (*it)->getName().c_str(); @@ -761,7 +762,7 @@ Node::load(const std::string & parentMultiInstanceName, if (hasUsedFileDialog) { boost::shared_ptr fileNameKnob = getKnobByName(kOfxImageEffectFileParamName); if (fileNameKnob) { - fileNameKnob->evaluateValueChange(0,Natron::eValueChangedReasonUserEdited); + fileNameKnob->evaluateValueChange(0, time, Natron::eValueChangedReasonUserEdited); } } diff --git a/Engine/OfxClipInstance.cpp b/Engine/OfxClipInstance.cpp index 4a41c59fed..e7857dd383 100644 --- a/Engine/OfxClipInstance.cpp +++ b/Engine/OfxClipInstance.cpp @@ -1002,7 +1002,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(); diff --git a/Engine/OfxParamInstance.cpp b/Engine/OfxParamInstance.cpp index e7a56e1a69..495396ff20 100644 --- a/Engine/OfxParamInstance.cpp +++ b/Engine/OfxParamInstance.cpp @@ -228,7 +228,7 @@ copyFrom(const boost::shared_ptr & 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(); } 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/Gui/CurveEditorUndoRedo.cpp b/Gui/CurveEditorUndoRedo.cpp index 571f521de5..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); } @@ -562,7 +562,7 @@ SetKeysInterpolationCommand::setNewInterpolation(bool undo) if (keyframeIndex != -1) { it->key->key = it->key->curve->setKeyFrameInterpolation(interp, keyframeIndex); } - isParametric->evaluateValueChange(isKnobCurve->getDimension(), Natron::eValueChangedReasonUserEdited); + 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/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..5b2b330490 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->evaluateValueChange(0, k->getCurrentTime(), Natron::eValueChangedReasonUserEdited); } void diff --git a/Gui/KnobGuiColor.cpp b/Gui/KnobGuiColor.cpp index e551b77ea8..eb504ff148 100644 --- a/Gui/KnobGuiColor.cpp +++ b/Gui/KnobGuiColor.cpp @@ -864,7 +864,7 @@ KnobGuiColor::showColorDialog() knob->endChanges(); } - knob->evaluateValueChange(0, eValueChangedReasonNatronGuiEdited); + knob->evaluateValueChange(0, knob->getCurrentTime(), eValueChangedReasonNatronGuiEdited); } // showColorDialog void 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/KnobUndoCommand.cpp b/Gui/KnobUndoCommand.cpp index e862681c74..43489118d4 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); + } } } } @@ -583,7 +602,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 +625,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 From cb2e898f9ba64471b092c1f3885984e1a25032b2 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Fri, 2 Oct 2015 16:08:28 +0200 Subject: [PATCH 088/178] PySide: QDialog::exec_() --- .../PythonReference/NatronGui/PyModalDialog.rst | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) 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() From 96819f502ad4a397135da1bfcd78a47c54bbdf62 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Fri, 2 Oct 2015 16:10:47 +0200 Subject: [PATCH 089/178] Fix getPluginID() return value --- Engine/Node.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Engine/Node.cpp b/Engine/Node.cpp index 0eeecf9001..0e94b17a3f 100644 --- a/Engine/Node.cpp +++ b/Engine/Node.cpp @@ -4723,7 +4723,11 @@ Node::getPluginID() const if (!_imp->plugin) { return std::string(); } - return _imp->plugin->getPluginID().toStdString(); + if (!_imp->pluginPythonModule.empty()) { + return _imp->pluginPythonModule; + } else { + return _imp->plugin->getPluginID().toStdString(); + } } std::string From 9d93b7892aa9ad8d2f057871455b91878972fbab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Devernay?= Date: Fri, 2 Oct 2015 16:14:49 +0200 Subject: [PATCH 090/178] CONFIG+=noexpat compiles using expat sources bundled with HostSupport (for debugging purposes) --- App/App.pro | 4 +++- Engine/Engine.pro | 4 +++- HostSupport/HostSupport.pro | 18 +++++++++++++++++- Renderer/Renderer.pro | 3 ++- Tests/Tests.pro | 4 +++- libs/OpenFX | 2 +- 6 files changed, 29 insertions(+), 6 deletions(-) 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/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/HostSupport/HostSupport.pro b/HostSupport/HostSupport.pro index af52d68b00..803642c7b3 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,21 @@ win32 { } } +noexpat { + SOURCES += \ + ../libs/OpenFX/HostSupport/expat-2.1.0/lib/xmlparse.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/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/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/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/libs/OpenFX b/libs/OpenFX index 1ec2e6879c..8722c0816a 160000 --- a/libs/OpenFX +++ b/libs/OpenFX @@ -1 +1 @@ -Subproject commit 1ec2e6879cddcd466315e44430361d6c19965a2a +Subproject commit 8722c0816aa429c29fc793f47a151452d631c0a2 From 8bccabf066d47fce4d50577c8fd0193c9fda96b4 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Fri, 2 Oct 2015 16:44:15 +0200 Subject: [PATCH 091/178] Fix pyplug ID --- Engine/Node.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Engine/Node.cpp b/Engine/Node.cpp index 0e94b17a3f..1744d89df5 100644 --- a/Engine/Node.cpp +++ b/Engine/Node.cpp @@ -376,6 +376,7 @@ 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 bool computingPreview; mutable QMutex computingPreviewMutex; @@ -4723,8 +4724,8 @@ Node::getPluginID() const if (!_imp->plugin) { return std::string(); } - if (!_imp->pluginPythonModule.empty()) { - return _imp->pluginPythonModule; + if (!_imp->pyPlugID.empty()) { + return _imp->pyPlugID; } else { return _imp->plugin->getPluginID().toStdString(); } @@ -5730,10 +5731,15 @@ 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->pyPlugID = pluginID; + nodeGui->setPluginIDAndVersion(pluginLabel,pluginID, version); } From a5b97523d1324f55d4e413f4d37b3df3d87e498d Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Fri, 2 Oct 2015 16:45:45 +0200 Subject: [PATCH 092/178] When making pyplug editable, also clear PyPlug ID --- Engine/Node.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Engine/Node.cpp b/Engine/Node.cpp index 1744d89df5..2dd4421b67 100644 --- a/Engine/Node.cpp +++ b/Engine/Node.cpp @@ -5756,6 +5756,7 @@ Node::setPyPlugEdited(bool edited) { QMutexLocker k(&_imp->pluginPythonModuleMutex); _imp->pyplugChangedSinceScript = edited; + _imp->pyPlugID.clear(); } void From 8f561db2e46350199ce49399a6920fcadae87d78 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Fri, 2 Oct 2015 17:04:19 +0200 Subject: [PATCH 093/178] Bug fix --- Engine/AppInstance.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Engine/AppInstance.cpp b/Engine/AppInstance.cpp index a9d39bc2fb..a9d224bb80 100644 --- a/Engine/AppInstance.cpp +++ b/Engine/AppInstance.cpp @@ -649,6 +649,11 @@ AppInstance::createNodeFromPythonModule(Natron::Plugin* plugin, CreateNodeArgs::DefaultValuesList(), group); containerNode = createNode(groupArgs); + std::string containerName; + group->initNodeName(plugin->getLabelWithoutSuffix().toStdString(),&containerName); + containerNode->setScriptName(containerName); + + } else { LoadNodeArgs groupArgs(PLUGINID_NATRON_GROUP, @@ -662,10 +667,6 @@ AppInstance::createNodeFromPythonModule(Natron::Plugin* plugin, if (!containerNode) { return containerNode; } - std::string containerName; - group->initNodeName(plugin->getLabelWithoutSuffix().toStdString(),&containerName); - containerNode->setScriptName(containerName); - std::string containerFullySpecifiedName = containerNode->getFullyQualifiedName(); From 012458c8e3d34938cc2be170c27df8a14aa0959f Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Fri, 2 Oct 2015 17:07:37 +0200 Subject: [PATCH 094/178] Fix compil --- Gui/TabWidget.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Gui/TabWidget.cpp b/Gui/TabWidget.cpp index 250d79a104..43e1314040 100644 --- a/Gui/TabWidget.cpp +++ b/Gui/TabWidget.cpp @@ -48,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" From 3fdd1d310d753f96adffb851226b2a342880eff8 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Fri, 2 Oct 2015 17:58:09 +0200 Subject: [PATCH 095/178] Fix serialisation of peplums --- Engine/AppInstance.cpp | 8 +++--- Engine/AppInstance.h | 2 +- Engine/Node.cpp | 22 +++++++++++++--- Engine/NodeSerialization.h | 13 +++++----- Gui/GuiAppInstance.cpp | 53 +++++++++++++++++++------------------- Gui/GuiAppInstance.h | 2 +- 6 files changed, 59 insertions(+), 41 deletions(-) diff --git a/Engine/AppInstance.cpp b/Engine/AppInstance.cpp index a9d224bb80..36bff6bb04 100644 --- a/Engine/AppInstance.cpp +++ b/Engine/AppInstance.cpp @@ -699,7 +699,7 @@ AppInstance::createNodeFromPythonModule(Natron::Plugin* plugin, } //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; } @@ -959,7 +959,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); } } @@ -1387,10 +1387,10 @@ 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()) { + if (!_imp->_currentProject->isLoadingProject() && !requestedByLoad) { NodeGroup* isGrp = dynamic_cast(node->getLiveInstance()); assert(isGrp); if (!isGrp) { diff --git a/Engine/AppInstance.h b/Engine/AppInstance.h index 246e374a76..c73a188a75 100644 --- a/Engine/AppInstance.h +++ b/Engine/AppInstance.h @@ -433,7 +433,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/Node.cpp b/Engine/Node.cpp index 2dd4421b67..bba6b88e13 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) @@ -377,6 +379,7 @@ struct Node::Implementation 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; @@ -3132,14 +3135,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 @@ -3893,7 +3905,10 @@ Node::disconnectInput(int inputNumber) NodePtr inputShared; bool useGuiValues = isNodeRendering(); - _imp->liveInstance->abortAnyEvaluation(); + + if (!_imp->isBeingDestroyed) { + _imp->liveInstance->abortAnyEvaluation(); + } { QMutexLocker l(&_imp->inputsMutex); @@ -5738,6 +5753,7 @@ Node::setPluginIDAndVersionForGui(const std::string& pluginLabel,const std::stri return; } + _imp->pyPlugVersion = version; _imp->pyPlugID = pluginID; nodeGui->setPluginIDAndVersion(pluginLabel,pluginID, version); 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/Gui/GuiAppInstance.cpp b/Gui/GuiAppInstance.cpp index f4fb103ecd..9d0fe3b6e1 100644 --- a/Gui/GuiAppInstance.cpp +++ b/Gui/GuiAppInstance.cpp @@ -1074,39 +1074,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(); - } - assert(graph); - std::list > selectedNodes = graph->getSelectedNodes(); - boost::shared_ptr selectedNode; - if (!selectedNodes.empty()) { - selectedNode = selectedNodes.front(); - if (dynamic_cast(selectedNode.get())) { - selectedNode.reset(); +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); } - 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); - 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..9696f962a4 100644 --- a/Gui/GuiAppInstance.h +++ b/Gui/GuiAppInstance.h @@ -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, From 49e30ad3df89cfdf04b8348fc7480842fcb68c13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Devernay?= Date: Fri, 2 Oct 2015 18:20:26 +0200 Subject: [PATCH 096/178] check for NaNs --- Engine/EffectInstance.cpp | 5 +++++ Engine/OfxClipInstance.cpp | 21 ++++++++++++++++++--- libs/OpenFX | 2 +- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/Engine/EffectInstance.cpp b/Engine/EffectInstance.cpp index 799098ff3c..9b2a020a8a 100644 --- a/Engine/EffectInstance.cpp +++ b/Engine/EffectInstance.cpp @@ -551,6 +551,11 @@ EffectInstance::getImage(int inputNb, const bool dontUpscale, RectI* roiPixel) { + if (time != time) { + // time is NaN + + return ImagePtr(); + } ///The input we want the image from EffectInstance* n = getInput(inputNb); diff --git a/Engine/OfxClipInstance.cpp b/Engine/OfxClipInstance.cpp index e7857dd383..4e30760a0d 100644 --- a/Engine/OfxClipInstance.cpp +++ b/Engine/OfxClipInstance.cpp @@ -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 { @@ -830,8 +840,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; diff --git a/libs/OpenFX b/libs/OpenFX index 8722c0816a..6bc3b6a612 160000 --- a/libs/OpenFX +++ b/libs/OpenFX @@ -1 +1 @@ -Subproject commit 8722c0816aa429c29fc793f47a151452d631c0a2 +Subproject commit 6bc3b6a612dd9aeb65ff3f0ab4ead1cb891ecb2b From 4105b5274460d5094a1c7bcd9eed7f638d89a2f6 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Fri, 2 Oct 2015 20:18:13 +0200 Subject: [PATCH 097/178] Remove debug call --- Gui/DopeSheetView.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/Gui/DopeSheetView.cpp b/Gui/DopeSheetView.cpp index c11969e3de..2cf66dd34e 100644 --- a/Gui/DopeSheetView.cpp +++ b/Gui/DopeSheetView.cpp @@ -3408,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; From 8558806b8b31c3334e2e61a6793d82d0d9ca70e7 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Fri, 2 Oct 2015 20:47:51 +0200 Subject: [PATCH 098/178] Bug fix where analysis effects would not get supported components --- Engine/OfxClipInstance.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Engine/OfxClipInstance.cpp b/Engine/OfxClipInstance.cpp index 4e30760a0d..f0de76e7a6 100644 --- a/Engine/OfxClipInstance.cpp +++ b/Engine/OfxClipInstance.cpp @@ -830,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); } } From aba9f3bc045c9f4e97ae25fa1a4e025ea1700bca Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Fri, 2 Oct 2015 21:07:26 +0200 Subject: [PATCH 099/178] bug fix --- Engine/AppInstance.cpp | 10 +++++++++- Engine/AppManager.cpp | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/Engine/AppInstance.cpp b/Engine/AppInstance.cpp index 36bff6bb04..b9d96c3209 100644 --- a/Engine/AppInstance.cpp +++ b/Engine/AppInstance.cpp @@ -784,8 +784,16 @@ 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) { + findId = PLUGINID_NATRON_ROTO; + } else { + findId = pluginID; + } + try { - plugin = appPTR->getPluginBinary(pluginID,majorVersion,minorVersion,_imp->_projectCreatedWithLowerCaseIDs); + plugin = appPTR->getPluginBinary(findId,majorVersion,minorVersion,_imp->_projectCreatedWithLowerCaseIDs); } catch (const std::exception & e1) { ///Ok try with the old Ids we had in Natron prior to 1.0 diff --git a/Engine/AppManager.cpp b/Engine/AppManager.cpp index 979f82571e..15c3691af1 100644 --- a/Engine/AppManager.cpp +++ b/Engine/AppManager.cpp @@ -1313,7 +1313,7 @@ AppManager::getPluginBinaryFromOldID(const QString & pluginId,int majorVersion,i return _imp->findPluginById(PLUGINID_NATRON_DISKCACHE, majorVersion, minorVersion); } else if (pluginId == "BackDrop") { return _imp->findPluginById(PLUGINID_NATRON_BACKDROP, majorVersion, minorVersion); - } else if (pluginId == "RotoOFX [Draw]" || pluginId == PLUGINID_OFX_ROTO) { + } else if (pluginId == "RotoOFX [Draw]") { return _imp->findPluginById(PLUGINID_NATRON_ROTO, majorVersion, minorVersion); } From 13b97a3f5c0af1b7a6d61fdc7e18c0bac0158861 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Fri, 2 Oct 2015 21:59:35 +0200 Subject: [PATCH 100/178] Fix loading of older projects --- Engine/AppInstance.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Engine/AppInstance.cpp b/Engine/AppInstance.cpp index b9d96c3209..cb622762e6 100644 --- a/Engine/AppInstance.cpp +++ b/Engine/AppInstance.cpp @@ -783,17 +783,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 From 15b00752e88be72e8fd0da3949878abbe810af84 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Fri, 2 Oct 2015 23:10:47 +0200 Subject: [PATCH 101/178] fix --- Gui/Gui50.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gui/Gui50.cpp b/Gui/Gui50.cpp index 01d0363264..6effc93ab6 100644 --- a/Gui/Gui50.cpp +++ b/Gui/Gui50.cpp @@ -445,7 +445,7 @@ Gui::resizeEvent(QResizeEvent* e) static RightClickableWidget* isParentSettingsPanelRecursive(QWidget* w) { if (!w) { - return false; + return 0; } RightClickableWidget* panel = qobject_cast(w); if (panel) { From 1213d248901f42e81530d1b4a640f795d2d2c8a5 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Sat, 3 Oct 2015 09:53:28 +0200 Subject: [PATCH 102/178] Do not take ReadAccess for source clip images: - 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 - deadlock --- Engine/OfxClipInstance.cpp | 9 +++++---- Engine/OfxClipInstance.h | 4 +--- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/Engine/OfxClipInstance.cpp b/Engine/OfxClipInstance.cpp index f0de76e7a6..69b93098d3 100644 --- a/Engine/OfxClipInstance.cpp +++ b/Engine/OfxClipInstance.cpp @@ -1256,7 +1256,6 @@ OfxImage::OfxImage(boost::shared_ptr internalImage, assert(internalImage); - unsigned int mipMapLevel = internalImage->getMipMapLevel(); RenderScale scale; @@ -1279,11 +1278,10 @@ OfxImage::OfxImage(boost::shared_ptr internalImage, const RectD & rod = internalImage->getRoD(); // Not the OFX RoD!!! Natron::Image::getRoD() is in *CANONICAL* coordinates if (isSrcImage) { - boost::shared_ptr access(new Natron::Image::ReadAccess(internalImage.get())); - const unsigned char* ptr = access->pixelAt( pluginsSeenBounds.left(), pluginsSeenBounds.bottom() ); + Natron::Image::ReadAccess access(internalImage.get()); + const unsigned char* ptr = access.pixelAt( pluginsSeenBounds.left(), pluginsSeenBounds.bottom() ); assert(ptr); setPointerProperty( kOfxImagePropData, const_cast(ptr)); - _imgAccess = access; } else { boost::shared_ptr access(new Natron::Image::WriteAccess(internalImage.get())); unsigned char* ptr = access->pixelAt( pluginsSeenBounds.left(), pluginsSeenBounds.bottom() ); @@ -1346,6 +1344,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 { From aaf89e0702e5aefdeda78d3332b639f080cb1c9c Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Sat, 3 Oct 2015 10:11:34 +0200 Subject: [PATCH 103/178] Fix #934 --- Engine/Node.cpp | 27 ++++++++++++++++++++++++++- Gui/DopeSheet.cpp | 6 +++--- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/Engine/Node.cpp b/Engine/Node.cpp index bba6b88e13..9fd4342514 100644 --- a/Engine/Node.cpp +++ b/Engine/Node.cpp @@ -2612,7 +2612,7 @@ Node::initializeKnobs(int renderScaleSupportPref) _imp->maskSelectors[i] = sel; } - } + } // for (int i = 0; i < inputsCount; ++i) { bool isReaderOrWriterOrTrackerOrGroup = _imp->liveInstance->isReader() || _imp->liveInstance->isWriter() || _imp->liveInstance->isTrackerNode() || dynamic_cast(_imp->liveInstance.get()); @@ -3165,6 +3165,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); @@ -3191,6 +3195,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(); } diff --git a/Gui/DopeSheet.cpp b/Gui/DopeSheet.cpp index d86c215509..5e9da5a9b8 100644 --- a/Gui/DopeSheet.cpp +++ b/Gui/DopeSheet.cpp @@ -1383,7 +1383,7 @@ DSNodePrivate::~DSNodePrivate() void DSNodePrivate::initGroupNode() { - boost::shared_ptr node = nodeGui.lock(); + /* boost::shared_ptr node = nodeGui.lock(); if (!node) { return; } @@ -1400,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, From 522e50a94b89321ce96e1f25f4e43f9a389f37e6 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Sat, 3 Oct 2015 10:25:16 +0200 Subject: [PATCH 104/178] Bring back auto wipe disabling in Preferences. --- Engine/Settings.cpp | 13 +++++++++++++ Engine/Settings.h | 3 +++ Gui/ViewerTab30.cpp | 9 +++++++-- 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/Engine/Settings.cpp b/Engine/Settings.cpp index 97820bef7a..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"); @@ -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); @@ -3110,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 8de848cf2b..101c5b7844 100644 --- a/Engine/Settings.h +++ b/Engine/Settings.h @@ -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; @@ -395,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; diff --git a/Gui/ViewerTab30.cpp b/Gui/ViewerTab30.cpp index e78159554d..f363d15412 100644 --- a/Gui/ViewerTab30.cpp +++ b/Gui/ViewerTab30.cpp @@ -995,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 From 34fa37cf5e1c04b5bb2ad02d8ebec7b33bb6616a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Devernay?= Date: Sat, 3 Oct 2015 19:04:52 +0200 Subject: [PATCH 105/178] update openfx --- libs/OpenFX | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/OpenFX b/libs/OpenFX index 6bc3b6a612..002cf0b8a0 160000 --- a/libs/OpenFX +++ b/libs/OpenFX @@ -1 +1 @@ -Subproject commit 6bc3b6a612dd9aeb65ff3f0ab4ead1cb891ecb2b +Subproject commit 002cf0b8a0c6022e3abc13e00fa5f5197b8e1099 From 9ac4ddcf7c8f440b140e7802d63aeb4322284c13 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Sat, 3 Oct 2015 19:18:57 +0200 Subject: [PATCH 106/178] Re-enable focus for slider --- Gui/ScaleSliderQWidget.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gui/ScaleSliderQWidget.cpp b/Gui/ScaleSliderQWidget.cpp index efb738346e..fc4e4e1a9f 100644 --- a/Gui/ScaleSliderQWidget.cpp +++ b/Gui/ScaleSliderQWidget.cpp @@ -118,7 +118,7 @@ ScaleSliderQWidget::ScaleSliderQWidget(double min, setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); QSize sizeh = sizeHint(); _imp->zoomCtx.setScreenSize(sizeh.width(), sizeh.height()); - setFocusPolicy(Qt::NoFocus); + setFocusPolicy(Qt::ClickFocus); } QSize From 72e8f7e05c24cc1fa29393bf12a88dcab505d558 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Devernay?= Date: Sat, 3 Oct 2015 20:13:02 +0200 Subject: [PATCH 107/178] add SVG sources for grouping icons, and high-resolution versions --- .../GroupingIcons/Set3/3D_grouping_3.svg | 10 ++++ .../GroupingIcons/Set3/3D_grouping_3_512.png | Bin 0 -> 23837 bytes .../GroupingIcons/Set3/channel_grouping_3.svg | 11 ++++ .../Set3/channel_grouping_3_512.png | Bin 0 -> 17344 bytes .../GroupingIcons/Set3/color_grouping_3.svg | 11 ++++ .../Set3/color_grouping_3_512.png | Bin 0 -> 23231 bytes .../GroupingIcons/Set3/deep_grouping_3.svg | 10 ++++ .../Set3/deep_grouping_3_512.png | Bin 0 -> 22392 bytes .../GroupingIcons/Set3/filter_grouping_3.svg | 50 ++++++++++++++++++ .../Set3/filter_grouping_3_512.png | Bin 0 -> 34786 bytes .../GroupingIcons/Set3/image_grouping_3.svg | 11 ++++ .../Set3/image_grouping_3_512.png | Bin 0 -> 19583 bytes .../GroupingIcons/Set3/keyer_grouping_3.svg | 8 +++ .../Set3/keyer_grouping_3_512.png | Bin 0 -> 22775 bytes .../GroupingIcons/Set3/merge_grouping_3.svg | 9 ++++ .../Set3/merge_grouping_3_512.png | Bin 0 -> 18533 bytes .../GroupingIcons/Set3/misc_grouping_3.svg | 10 ++++ .../Set3/misc_grouping_3_512.png | Bin 0 -> 16847 bytes .../Set3/multiview_grouping_3.svg | 15 ++++++ .../Set3/multiview_grouping_3_512.png | Bin 0 -> 21549 bytes .../GroupingIcons/Set3/other_grouping_3.svg | 13 +++++ .../Set3/other_grouping_3_512.png | Bin 0 -> 25456 bytes .../GroupingIcons/Set3/paint_grouping_3.svg | 10 ++++ .../Set3/paint_grouping_3_512.png | Bin 0 -> 21193 bytes .../GroupingIcons/Set3/time_grouping_3.svg | 31 +++++++++++ .../Set3/time_grouping_3_512.png | Bin 0 -> 22756 bytes .../Set3/toolsets_grouping_3.svg | 9 ++++ .../Set3/toolsets_grouping_3_512.png | Bin 0 -> 19187 bytes .../Set3/transform_grouping_3.svg | 12 +++++ .../Set3/transform_grouping_3_512.png | Bin 0 -> 22470 bytes 30 files changed, 220 insertions(+) create mode 100644 Gui/Resources/Images/GroupingIcons/Set3/3D_grouping_3.svg create mode 100644 Gui/Resources/Images/GroupingIcons/Set3/3D_grouping_3_512.png create mode 100644 Gui/Resources/Images/GroupingIcons/Set3/channel_grouping_3.svg create mode 100644 Gui/Resources/Images/GroupingIcons/Set3/channel_grouping_3_512.png create mode 100644 Gui/Resources/Images/GroupingIcons/Set3/color_grouping_3.svg create mode 100644 Gui/Resources/Images/GroupingIcons/Set3/color_grouping_3_512.png create mode 100644 Gui/Resources/Images/GroupingIcons/Set3/deep_grouping_3.svg create mode 100644 Gui/Resources/Images/GroupingIcons/Set3/deep_grouping_3_512.png create mode 100644 Gui/Resources/Images/GroupingIcons/Set3/filter_grouping_3.svg create mode 100644 Gui/Resources/Images/GroupingIcons/Set3/filter_grouping_3_512.png create mode 100644 Gui/Resources/Images/GroupingIcons/Set3/image_grouping_3.svg create mode 100644 Gui/Resources/Images/GroupingIcons/Set3/image_grouping_3_512.png create mode 100644 Gui/Resources/Images/GroupingIcons/Set3/keyer_grouping_3.svg create mode 100644 Gui/Resources/Images/GroupingIcons/Set3/keyer_grouping_3_512.png create mode 100644 Gui/Resources/Images/GroupingIcons/Set3/merge_grouping_3.svg create mode 100644 Gui/Resources/Images/GroupingIcons/Set3/merge_grouping_3_512.png create mode 100644 Gui/Resources/Images/GroupingIcons/Set3/misc_grouping_3.svg create mode 100644 Gui/Resources/Images/GroupingIcons/Set3/misc_grouping_3_512.png create mode 100644 Gui/Resources/Images/GroupingIcons/Set3/multiview_grouping_3.svg create mode 100644 Gui/Resources/Images/GroupingIcons/Set3/multiview_grouping_3_512.png create mode 100644 Gui/Resources/Images/GroupingIcons/Set3/other_grouping_3.svg create mode 100644 Gui/Resources/Images/GroupingIcons/Set3/other_grouping_3_512.png create mode 100644 Gui/Resources/Images/GroupingIcons/Set3/paint_grouping_3.svg create mode 100644 Gui/Resources/Images/GroupingIcons/Set3/paint_grouping_3_512.png create mode 100644 Gui/Resources/Images/GroupingIcons/Set3/time_grouping_3.svg create mode 100644 Gui/Resources/Images/GroupingIcons/Set3/time_grouping_3_512.png create mode 100644 Gui/Resources/Images/GroupingIcons/Set3/toolsets_grouping_3.svg create mode 100644 Gui/Resources/Images/GroupingIcons/Set3/toolsets_grouping_3_512.png create mode 100644 Gui/Resources/Images/GroupingIcons/Set3/transform_grouping_3.svg create mode 100644 Gui/Resources/Images/GroupingIcons/Set3/transform_grouping_3_512.png 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 0000000000000000000000000000000000000000..a9cc9d6374191dae5cc75e251b2ccd81e6fba7c6 GIT binary patch literal 23837 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 LfG)PZ{#X73It(hL literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..e6e68ab26b37969776126155eacbd330f5f4053d GIT binary patch literal 17344 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 + + + + + + + + + + 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 0000000000000000000000000000000000000000..3358d6f89034eaf6ab067f150690c02386d94cdd GIT binary patch literal 23231 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ℑY4~h|o$xPhyH8Bbt!O6>w>LrT-DN48YoJLchh5 zi%^K?Md8f%ExOrRr;f*)y;!Io5YY=3P&J}xrGW~qG$1+^pf3-4c7;N`$4Cn0^Iri< zE;olIS&Bz3Kvt@n$q?sH=C?y)y@}4m+M3L}KqySF zG4mc?IAm1IA6laNR3ZIiBzE+e|Gd_b0K9=c{kVdztuESqu>+A3BUM+GkLQWIPO9#>Q1V0yD~Gc;AZ2{> zIfAHd>9)=+i2o0+GLs@l5X*VW2e=LZB?|V^if%{)fw}j=cvch^cYas`epZ$vqx_uEJHo8`G2eY>-^Y7#=xdBh-*w&XsqkqR zcx{p19(?-!)(bvN<%h)TMjf)oZ2;^vfHVzr9YimX6af!*rgm~fd+>P(|IZOyzM34d z;*A|+r;M^>v*~8^!6EVy*Q9&oKwl4+MV#2@*8l{uh%*h)pnKxSlfj8~xo9fQSNjE| z_M?O5W<$~0C0}?g9m-QL99NBfcnphX6}ad7UXeRBs%S{5PJzI~TyOWSd-?miO3|CJ zMgRHpTkEr%Q;_$uUE}61YOapjhBAPc?jimj?4`B_>(4{nL~PRq8xqIT1I8iG3PPwh z{Gm*Rl zPkYw)Friv&_qO2|T@|fZxwpBLYoH;%ekgK!?<|-1%&gJ8YuROsZlBqR^2FBpaShG9 zhY~_KXuIiedQ1cJWX;N9v;*RL#Tze)s2{x#@s?+8cjoD83P@5x3LO7z9JQR%Y-^Yu zvD4c2WHy`5u6s~ufA_~OMu20oXJJ^LgMbqPK>wdS9}MJ-xM|+5(1mS~C8*ND3ySJN z@GMGw7w6p=Dr4Q9zfsbiu&O<7u;8Ke{V&JFG!lV01y{v{FIVdNcbY)?;>W_r&u9p? ziQA0Um$si2R=9{59=PfJNV-jHH^WQPfLHsXwgWJr9uvelZwQ*@VZxj0C8Re5<)O7M`4xNCKoL+HLluC#e&#dVH_YdF}kT0mud@MxVgl{7@2O>I^ z0o8a)iXRwe$2f83xZH*~e|{Uq1ZBAYbO!jCsSiw!6i*D3H$*nnSogghE!87!z|*X^ zm^ac3`{nUsg>HTh?{J4SRvquIoD}q>%gUTh=n~#~qxNo6AU!BNA{982Oiz8Y3>}S6 zIz3$co49-YX->ZnN~O!e3(K!E%BfF*lu~tn77)OyikZi2!Nh(4?FD%I22TIHS6B6M zw20h<>toH#BL=9OhbLfUkBhgqOuYOM9mqykA9=77{@?2os1AIfwcKl&W3#$tWDm-1 z*6Ep3FcX+GFHvy zXW1-%RR(E#Sf2jSz52ggOjmZ|sIBycgh=~D zjB=!uzUCx41FHWnQ8DmJ4x?7%rPZJlw93IAm}TGnxpX$6nELgIJ~O`QXGh{@{UYk( zmDgi`3Df&Yqn_s$GtC=0KY0=MfeoUGnOK{@^;9X~b?_^i8`kWxYO#REi7K`e zGeApb+FJIx7yTb|w2M>TCMD19l=H4w_HaYIl^^DT#rsfVZJj4Y8_<6}f~{*7CrM=! zo4?!z4k!@SUh&KWv{8lviKGUfwiS9AZ#=6jX%=ie8*r~0vpmb}Ugx)`LnrB1 zil|a_G~flW@}(_wDDP`O8dc6tBk`uN{*#(($KeNQNZ)niooNEA(_^98SmS@$*z8MT z8Iy*GM={ZZlRwEC@NmA=R`UpRJw&fA08qv67FFnRu6wx-Y={6Cg+>BU0cqy?h044G znwArsPC84wySY~yzh53D_2dAtWN3uQ3~K}~L)dj;GO0(9&fmRpXNR}@oO6^7Qik&^ zMpUm9QHXb#2vSsfnE9AV!vS8_uI8@-MKv|z#?abObSSc2e02%R-GcE5f7+?|`>oGl zpp01s*t8j6SEnIc76h=e|0lmccQ4kdWdeMT$PYPM?mse#W>!KZNgq~ zINFD0OjzG$I{30ylwVOo~x7xxDduZ1ktn&FF8iRA$*RbV<1?Y(-JzT2Pz2gP7?a^4sykJd{Cnp~+B z+5Jaf;HIXfO$asdp;_&5I{-9SRrRCY3fd>FGLrpBcUeXB-7%c~aa?9YP5pZTG){N- zrcAhPmQ&%y>nGpSo~;`;292=&s)^X@9>koc2Ir?Z*+PZZSFm=9++|BNA;*wJXd@?l zgc2xhXWEb8Ri*mtK<8y>^qci1%Mb^-{oL>ebIv>dOt;dIBhZng4H#jZrRFdDfJQM9 zwq2DDx?K!`_nA0?4w+>DU^!KVB5stWm>{h$Ue^X_R{O%*_vXdjOQi8^(8BAtio-6} z=NhPumVQdiK2@<$DC$sx{CD?Qb!F0F%dX6t?wE|N?&J=u^Qy~Ri;$7IYXX@LkP&}} z_aE##%c&oI3Dd*A3|3hph--Xkb1;{~#0G&K*x519(t@fW-r?(`Rg@t}Z4(^5+ysP- zw)V8vRQ0e3Vxpdx8}nq)w)`Eh5)eGYIpN}$oF~R65GJe04}&*jp!NT|7KHP39mn+w zT?ZN$vTdF7bsMv@5}J`1$zptcMP0)R@g7f-1-&DR)~&%oP04tKa&#m(l8qBD z=knsaTyTCj4NAgvm5yyju~&Qj)+q)!=cR;iaAH@gG2-0TRJoM9_Bbvc!U0W9)1{cL zIh4DZI*oNEBU3#ySjsdpHV(>NylJ+nYVf4p)Ns736^lfDWuf(jsx-pMoWTAro2Yg34aF?=zx)QMm9Lbx=AC)V6>fPFyy6DN__2 z0HXZn+Lp=UU?M0Y9E8iQSQKkq>OHZru=F-L@S(1bJ|@MpKluHU(i#3SMzt}=2WVi? zKigY!Rxvn{E4UgZw;gm%>hms>IhK=?kx_^&0J^-@ zIZk2e^M+@-HR`yqc&l#s?Ae}A_Is9JB0-!$8hZlE7g_S@X;;qa$QSugC>W7#^oik>Q^i(Skqx~lO^g{blAkNTCp&-MLy zPC5hNfTrf;4xY;QIX^himzF^H6XJ($!WseGua~Qg6&QffyeN2o6SFU03<*qS zeh6-MD3G^*`-U0X(cdgw<=@AoaildPyVLz=hlTzM04!@Z0>GIOg?E0K`m#r*xd7z9 zB?+{UH{!wfA>2|g%H-EP*`%gh!k_A@UsVdxXCWwF?}hez7Avo(vMT(%1nR3^tT=y- zjBnz!m=H4pRnT{4-~A>K6__9s#zE`}=QrrVd3?A;=%wZ`9Pso*<-b&W4vJTn6z`@8 z@_)V5E6`xw9G#w~>0oAheSKcqpYW&b1eu|>62d}ne*O`Z^l@sK=M9)(J(=8%#ef@Y z#7ACMAKaS<1=i{Vjrr_BDM5&T`OZ_pL-T&Cac(EN5_Uf7 zvYqZj&b&43G4lrEa$jD*&vlrF70cjyAX=j8Y0UN8oQKLrhbp}9wto=Sh9xxH0;#(- zXIE6nIc~jZomF$G`#GOzUwhF3IaF6iXM$Vn5IL%EeJpv#q9{g|Kxx4z3 z43y)(bR{JvaPN|{dT*xagcjTx_PL+o_f}SrhZwe-gG#As-BWiq61P=0Zr%-4kJFww zWPiIL?sfn+ei2BIU@U%>gs{=Kd#J4-7NMjXDsOB(GBMM3e!W2l6%q1?o3L)(pZNBt z7tfSgNqzfha%z&2lA>gCvp%j#s9VPUYD)o5&s_hkvwI+1*06p~z3V>8mi09AwVxLJ z29y#%5@H5WH-t%ZRW_f$Ga@4+^Og7%yKHwUlJ+$!ZGDTy{=rAxnpTW!UN<7?+50c- zK8|2EA*LN!kpL+8=QVrqiw5yP3xdX*GnVt6A4IJVr6HrmyO!p=si{rJ>uQB5sei80 zt9iXQND2#(0e>o=j^xea!%V zg;3FO0uSYWb5~!c*~Z4f!fL8N)gqFX(lom_o41q~7Z;bN=xgE4u1*yqrW%>pg4fMH zHI?K0M>+d_c^-luYeyhLg#PraOH1}N_I$Ouzh6Ebj>Gfr*YVVW4B78L%Ul5s#IbDK zJi)cfRe|or2h-;Fc>A3yJ~8{}!cA!fdVHL+?v{ezxTBHg(*`J;_&3_Ex$@Uz95A*Z z+#ZfXZcU_3Tc1v!%una})Is;*v}4i?JDN$GI6iORxO(k?I5ov8SJxNMwJ(0yNXan2 zqplr(QTUfW?Q$+?rX>0sA@=u7uy@%rF$AZa(Dsf~ilJfA0CbnHQx53~B@E%n$;5M< zl2VKBV{Qx%4qoau(&P*6_|aQ{i2M^XG^;U{kc0il`;zKUnw&Gq^v_av4YJw^bOI)J-~S*HFzJ{PF!jk zkv>DkI$yvS@og{G_3I~J)GA#We2ftzZV3D6#mSFsa za~G`nT*SFm6c0B8K+v3T`_TkCQDk37!n&re8#`b zZWLM0MCAJZDFF`(NBH0Wd{k1CVh5ozUNEdDz>N8a1NI)fb|gtfcxEBd_)OAxQ1)31 zn7Y8M5`lT_%h^ch-H|yJ`d3z zAKCc(19G!I#ZzIkOf_0ILDCReTqn{kV~DFS*?r{}W!SnjH)3rA$>e|yAf2TXXRt3V z*I+|-PKVy0iz1Z6R0Zu=G`O`$QQT<7NFL2a@0)~4{-*aU1weYun+d=K=?1J%((Io| zen_?d9f(s|*r`3udjQ3Z<(zJ`*3~)4w3eXQLGx$p?c?_>3QKnGbS18*XlDw>&eq@C zwx`8AaRf5nq(0p;LzaH!m#LyF*((JxOo$iRp3S@T;)`5s?ya5iE)R*;ec!z& z*!Y0>rkn4JGaK8ah(uDXMi%BZsA5`c=>7K3|4W^T9>aHR8@4dzKpq>DgB9n4IaIGi z)w$6U^{3Q;x*;+yUCcYRh}ffQyK26Dn1ormFd)VjWKL5dZIOve_1UsFpCUENjMAv68RY3aouk| zvw>f{zKwpPOOKB+EhNB?eE9b8-lYrK`~o}qB|au67v-M4dAFq56e}&HQb^3?;FQrdh@(O7{Y1S`w6a+l z(Z<%AiBlU8;Pbw_tB46xf&Dc`o0(6&0>Ic~4-vz;luwycD|#=v^j%t8`;W*=3C>1* zdjMjt{4o~nXkNnTXF1DGOGj-QWG8kX#HoDtd>JKtzk6Ojz;KG|;+i{HcAV!KmVG6( z@dFVYkX_bQpTzaRu#YcB%VU>vtqAHE-DtD_6dUxUwnSz3XeFw}7bc4(T=zLvJ8k^2 zR#GtcqUfU`yK0SQjuLA$+{R z2V4_5jn{RpNppj>H1KltX|${1nf;KT@RLnIfeivsgrwi%cr(3l4z{i^y=R?k(zYvzag$}ZEWSDa2j$65i2$MgBoL3b@{ zYkN~8S3lYZ|B^-1NRQ;StkrSJt1UmkntBRa;v5Z2$ZMs~Iy>Sxw-p)L#x)jRw-n`I zzl76c&gyiXXsjKhz9K5t+&4hT1@)L{zpWPSs3cC-Yr-}q z$ik!+#iKLpO|+jX6|PcE>j;YSw4HXH`{;~}PX1U4I@d|i`IfIDhB)#&*7oG->aGsf zp08ZMzN&;z1KW1C8^{qS1g%5{qrS;`#KpP{?v?wyc0UL#15Bq?3B+Ts0d`Q0bG;gi zNXYK_Tpgpj^1Mo#0j7*IYeaE2x)9+jmFUCvQ=~%ly?@dzim=QQJ3W1qM9(m0Pm|YJ z*y5?D4DiurL4A$e#$;hhuE1jBAdBfkx&kHb1h^}m)W>Ik3=Iby5ufW``F$|^KKYF( zF5xtS9f|Q~L})9727-s730iGli$&03@8IjpRNn$-_47onj+p2wEzeVTbdaP5> z>VPN;FcT@Ur_Ju+dM+T+(Y}8^`M}H0_wV|%AA-x}_EzMIiq$PUGt;&E@mlh%r3!QA z6lYHmTp!DHZd6Y&G)$X?%dZGCo}RH#;gsTmO%|_hS+t=5+$Ia}gM`I$V=?`*ck7uc zbEN>VblPn;irFvhZj>V)QXgYBkZdI_Js3TN1wIjMdp)WNz<_R`KVmPkUz!ix(FA+C8ESE1RIlOAyr)HKxT{AHF3a!ucTok%1mmZPKdhh3f_a>Bo4$rY^E=PoO(KfoCo&QU zolI;Z;tQ#F+qD2(PKBT)PAW36j^(*uJIWu0SWL@_P@_PO0@IPR>yQw`_sTFfC;%=) zU*23-gaDEfC0)sTfu9jk^mLYwb0u5>vlUdu0Zu(qYr%l@;ht-1;pvviB1FStCl-ng zkKu$|a~hd5*$BU;ICV=fbbVSIA?ufgubXKbsL+DzNHh7$N5(YkG(95)@RkWsY7Xy` zvsj_J_!nkfQv7_6?tnTY8pkmG2DuKNBQ8HTBk*53Q~jjdO3WNH7IAs5cA(-$5%y|K zrL^Xl;9RB#fD|cHv@(}KT8UvEV2d>r!6SXAbfS_%^ElwWmv_t)jEMte`VCdfeNn# zXi_77t5IUycYY1fzPl(~4Umgaj$FpVg2I#+@T^~WY(IylyGI}Q$p1zx#IewibO;Sk z*3*?Y6!?SA5yO$jjZ|JVJoeD2XcgR6#L=E}jeTmgCfxrdspol9b7@DB#xvrRJAgzA zXPy`qDf&qiZ&fx>gB1F~a&9aB?adI_G0(T?!fh)ls}dQIAOX>98w)@{VR7}<{1Qpf zqHC*~mVo5`(oC?;q1zm=@PetiBA!`Lbd#QjyvmP}m|Qyp=AGpv?}OXbT@`%KrjD}8 z0t6Q&sE_T@`Fo1*HEcZ}eMzoxoWLYhe?fkA)HQn)nt?6KbfhS+%gz9D7_DSxTlRN; z<__FWPvAjAq-(usw@ge3n!GH1A1~$aK%8gjGFvNN+kRIofT z;35(_itlv$bF1@#I9S9lE-=vk(@n`tR9LLvH$&e<-5x}%q3QI=zLFb)Y0%`z%T~%o z&lP#e>hw7ggRE9#3#)h(OVgy0hX-vw_v@ay(JF%!P{OcvZdW~m*pxXrLurirVm*cO zhf=}Cz9iL`>YA$h0A4prOIK~6@?3>+Va?vv+F6^{)srhr0UgeW^Sd=|4-FV#;#SE? zd;!?LFMwJ&+2mI{fi-#b$etRL+8eNBY<`D=s$4cVO68+~K*Hc#swP^)V<{H$b)$Zy zIY9s#Lv4;h^T#ykeUHpf%&rQQx z4_R(~eemy}8Q`IL`wYKBx1T+G<}@sRm1M8zNWZCJe^>v9=M4FBZs;rWSPR&J8>j%s zI>O=ct;9%DNZ<)3A=Sz@c`Q)Oi|g;*72a_D)yc0jCHyM!gZ6FaM=0WOf=>LNiY9#U zn3DGR`LW9PLjU7YQ)2JaQbu~rs=|*lT$duxFUUV5T*sC2Ham`^SI~PSzH_{NS7H5ziCg(2jSv@2mr6L)+u2B zj1Yq~fwHO#(Y&KFNdutr2l|E`Ztl$l~JQ#Z~PGT#VdmSzjnr&CPUqW>; zE_~)aC4w!3!rbC$GHMQj&T1(FRjrp}-asp`ZL!5hk9~8@!DQt1p&Hk2_`y%h5{W8F z$&d;^$#Z7Jxm??eN4@tUj9K06%tN5Hzlip00;XnsZ+dBc)Vs8Gt=IyB?WGtv6Do-rs< zJ;V#lv-rdDxpPF9gg%O6i5Vw*R)w9mr&Ki(p1x*>qO-!H@8l*n#{T}9`wZ31k*l4j z6o)~Ugu}?bcTAqfFC_1;1mvZJ@tTU%A3q1>tl9WGwvY(5=wpZmwaVzcrNICzz0|Ma zJ^t@Ivm?b*gNDc?PgGYpNYF+sSm%cS@DdjeJ@+f8tFHM{XqlynDWGC zo-)K+^Yg~NwkGC#!r`yrt#_JcxDEuQmM-uPxu4KW1z=rzR~rf>^;CHKAbwC`vd2Yw8e4X-j^EOyPSYQ2RWDqt z`}%}h8OjEbW3Ao`!`FMjRAP*LdWMuC;!`9)LEsq}9DI?}_qX_Z(Kw&+>3#>VrrSw*%bd%H#Ea5f z_{`KFwN)7C<@SJ$U+0C4%#MDQ4H${;X#oP7M!Y7EoTLGso*4xt(uOtKqyY35z8Pu7 zzW&B_phL88zitVJhP)MA57#(c#CFBQY^I5wyn45k9-fc@$)`N0*|nbY&=ZA|WT<%c z5J+a|rX0DSD!s$J5GhA&8`tP{ANSw#rj`lld~=GLO}L=m;)+S5R7ZCvM^76l<9);7 zMG-BY2g6U56m_py8VYS}Y)stDw3#he0_AgV9r#!pk#O4F^<+R7H6&1E_Dg;X4YoU^`mYXLapI=IBZCcRO8u@0t7;(jsCp=%o&OE@AJH*n5yw# z{)7jPFvg(=c#Df0{;s<8v!j^qjp*lCbkP&CD6|yXZ(k5sQch1cd+Xu-?wy`}6o;QO ziqR=w<*9ZJVR^bzjSO6^5f%^HZs4tQF082TfBPWNl&wXs()C#GKXThksZu?(;1P}CScx+en6 z1r*mOnWc^dP);_yOSC{fpL7sfSzVjOFuIc7Hflg}c>h?O4F>P{s zXy=Xwg}GvEadLsq2KsJbDG#j71@_WHyizx$Q}Mh+?CikvP(01wfO9TtE{1rYd7bL}z_ zXa^jG%~m5drr)CB@t-pKY}@Yv89LWaK$4@X%=|(O=FBu@>7d8d zEAVH;fa<>;%4a+*VK0W|ukx#z?M~uDt7h0ru&wKDQZrLmsAK#>i&9bs^Xs@l3t&ym z7B*SIzOJJDJ{21FFVy*n$E++i5d1{-gkJF~(=YZE;8$@f1}p8(zk5*VR{DDEqh6No zB;HR*6||N^7aqY0XZZz1VOdk3`S7P`VF811w$E-&}raOrq zq8_c6e7xxxlrlq!29BI9Idcmt ziMaxX-S6__IWLp}wG8g*q14$xT=5;jRhwOw2Y^a$OD??vLztaf@K)1gP0|!w2t@tJ zG4RHo!n-;w-Th9|q76!>OGwjaOj>2uhF+a1T$Z}4Fo4YvYlo*7nX14KkxGJUv~g-c z(32xdB+IS%((`0z$<4_CH_|;_*)Du~01N+t5tM2}A3YR=njvL<14vRPlkm9bH03+SZdr1EQ zFzSZUG>b;7zCQ@d0^kqgnCP|yUFU|6y3^+4+kH-@MAP7eZty_PLyA*F+wRb?O|)U8 z=et53xG?;rCx7(_<^Jy}mO(wyh9JCOcG}*j+C)-^;@Z`L`+egoW8$8JfOL2c3c41i z8DOiFE9QN)C;>1=q(@uF8*$K*Q*(!Cz1>6Nlsw3ja%Dt8A4H%rBTuqs(ka&h^bHZ8 z?lG<=ZeFu=TY7}nXZr;%rzeUcbjS;zZxjG?`Cl^ue{WAJ;Rijfg><$#yc%L<5FkS; zW*(j0a=xZiL>h?V54w1G*l;4$3a8abI9coeSz36ws^`5Eo^z=wK^}|Y8HZ<6#TC3= zRF#SZozh@(+lRvy$Dguao0Rj}Q!uwwA9+URWDBxsB}op5fiZ=5A>uj|^ahW>p>O^e z&vW7*IVTqrj%z@2h7U~C1o_An@M^C; zP7zxsQ)#MWbR9DjChxp?l48G*c>EvFLyxeBgMM*fgpcP-z!WZdtR)pVKM^?&Zs-1t z8O*Mxo8ZAvIo;KkN~@6%K{E?m1(jCeA=BDIPZUbM!zXny&(8468Y0pxhyx} zcSmr~zx^}-X58!Y>MHd-H-K3wSy$@4^{3l&0qMn;wRLQA9Q@Axq7z3dA8U0gdMkYb$#RRGH=3RzH$ks& z$@;^#&c8Oc(yjPNIe9B+Mwj%+d?`=2fnu2vrgvA*3Xm7@TOkg6N8F*1H|djqm_uSU zBRYYtk50eeE@xUD+KFT7*3HoA&Ran7_3UrPKu#!EuvAV!Y$0V-OMm9}CUjK_neSB* z9wX?^vCj-Vz7g&5U_Pl#djg4`H2sSg-K;w6R%pD`Be{eH%vH5%zMWIZIckT*-+EMt z6sKgyisDQK-aN;}nH*vTi8zi|aIJJH7cA*!ci`*_X71-hOy#ibj#*?)u`@!)p7olD zD2aUr(Bp4PK;&r#yq|eWRZ?4z1>${9e%;sW9^1k+RFq)YG%hc+3I#^S0EOR?6|Wcq2XZ7nxI0k( zCI~EE$WGgQH74|9=@2YQwE;y*9;!$V-l33KB)VYrf*Q>Mmu2%u`H$neuYrnbV0qs0 zS_YW?-4A-zJ?*hDa4xQQ^KSKw>vF>l3!K(5EyDZug3aQs2X4ms0epb>vDsm%2~4n2 zdlkZ&1J?lTP3%D;oE_iau}Ye5QF>~2TSd5w;Q7iOOq_bU283nZIJnQMYp+zWHbxV? z{7_z{L$<9Q_5k;2Mx4LM&0?_At*4k0L}@T{%_Vu%<>?dM#BV}y81-?*rI^)EZI2tCNc?0)QyM55jD@t#1C@T&7FADN4(w5uQ=>{_DXU0 z*0t4JK7^NxQ6zfLV?Pppjtyq5O9Stj+Jc|@ue+`9 z1>~g3XrjY3rszJh)3*+tSM!5nH1JrsKticNa+__o&Ym%&y%}xVcPv_hW0HJrFZcsG zmyHa!a{GuiZ}F8BBJTK@>DWXav*pP@dPDKNyVkU2m}5atv-c9n(jzXp!XJcq!$&l^ zL_=H!;n{|+z06A|Z#NK&e9StQ&NFuDAqO7rnMCms7RmP@j!!VjKD7bWQadD0Uk+B+h7|Wl_u4^=Ut@bKg5;>l`6KM}-Fb)+Tna zlKYP)_N^z^cx@)U%(ut!-^hZ|&NcZC>EE{powVsIdtCnX<%qHb9Ymehu_a|`(zP0NJ$ScO@IIhBJ_yZX&VLY=`tb%QTqu7IJ04Yh` zx=B6lV1ih|s*g|0^LoyCdQm`j1p7gu!;$=Ydc*{&WlT~SCunBEoo-u%C(k#;re>*6 zYrDQKsx%KW5m-Ki4OlQ1x7W74p{&_MwwO@VmC8N8-P}PRB^Gz?1 zj0jt|;bS$h?BovGT}h#3N)-Q(r%#uP5TbN@NAq$~VS5wVaNXKH zz9sLVW1NNg@&mmJCWDf?clP*}rNE}ASnH6y^&qths*>cWaGJfWK%syUCM$TQhM}&1 z$qj^NRFf~7o3fev0-@Nkr+1CMgIZP|kPpq3+)5DK(<+B}W@pxt}$cMb6u56miq;-jTFa3aE^*sEDN z*X!TKs-#c#2?OG~tbN~BQ%eZMXD)GKHw*FDev;}b!by0wdEAn!18eB=y~Y5SE)O>i)Z`g*_p9`i0TXCH?8`QWAnADccI zS_sL}*rqNwj397;+GI~E02%A^sI_EN^Hw)A;1jzO}<2PxlzEg)}I_~&hR6Bky z*j%ZbQsHC&)FgLJo)@bfO+6|p%#;PH^)B(k@+MNVCYT{v(DZa)rXoU0*zkMTK~x!w zegQvKZHUdAS$gG^@!8E&H-|W{e&Rwm(yQXHWGFCIpvkl?8 zWB_w<&i*l-5PsHexDX%-!T$;JV0dBw)Mldkw2NzhQRSZ`%T&iu_hK|p*@eD}8iQ^; z*X|t}D2pMoe9V4a_+RE)O|Y8NaBDza#>CQw_V?jWES>i&&W_egk|i%z{;6Q0@IJIz zCmapd!kGYtq!b6}4ONBbN9>P1PMjrm^my@>e+^03JT{?4OA7l=m!QLBAHeopXBd`% zq1TGJn!Gx9{ZHe0z|KyW_u9qxR}C0unsipUFc|hgtuMHC(kb3xm;ES)eX;c3nVc>& z-tinK%5(`it!LB{KCjjuxyHdZeU~5f*VU|ADb3{fS)q%TgQ1&)V*5#DCwsJuiK~H# z6|ogYt7j~m?gxE|-m=iv#J+(Wbp+4^fWShf01)jf%Cilt(4>bxKc7|ff>`!5;p706 zj=@U0nAE?wbFp_d=B=E6Ykx%89%C7bH6*S)*%TO{9v9{I7UP9Djz9>TPCMDkd##q2}L zmrJ~4q%JvC6jR&hMuQoPXWa5z{UALp%5()gS&+do66I6AOpx4^tK6&nA0XPxt93)V zR(Mq7CHE>lVreY_8e+*UIJpfwqUJuX@|u3FPJVgok8*OiVhx@)lvJa5o9u6G4peJX z=5th&S3Fnf&Ric(DoevyCM3KknGo^^60qK?O;sISX9=OGF-Y+gOwf)>Dy1eSOaLPO zt0#H-`EH*~7vxIxAO9)~56#<;|IK@g2zh}h#P)S-#M=!hWSO9a=gykz`syjJ6|^oB znGYU`Ez1h-z2b@kwXy{0&^`*n*6U*xh13$zONk7@ z*9xteYY5QK3;ID}HhmJVPlLs7dyfAXxhz_{6o{4ND6lzf45LE*S^EGACA@F z_DDT|$;I_h+a=OWwb@J&h0;_5Pwzlgiw#)IMX1(dkhM4bpty-M zd|FYZGUSc+w8*J^B&PYNPTRdco&#a1w3pn$vO{-&n}d^GU|Z-3)32nc@xY9$oICUg zk${_?_yWakI09Ae3j&fCjGN^aRlZ`j{UV^peE^ps_Ta(YegGx8g}Y7q7GPQ?<|+@_ zjP?|K2MRV~s2^Ax9+&K!=}ibX0}2#^HLPph!=!_ZZ8CQQ3My>v@DJvAkkYK+mKyEX znQ)xD?CdfVbYb_^BP!MzX1?n&0)iKCpzR`$7qbJr2IZLdVli-?v!_);sb zIHwj5ZFD$bd9u4(LE@`(_M_c+tO~d1@;hh)2%*K`nvQjIo=0O7aeu_ZwD_}X4YlXNEXe_$06?3Q2B zf^aoEEqKcr2NsiBd?kcC{oO8(jVRM9q7Q~arJFr{+QAQWpboz+xk;vV;6|krzwP|_ z&)meXEZh3Ina5mq{XQ$(j{(#2GAM7TZF5`9&H06{5Q@0UaeGsJd0n}1^lW9*r%76t zx1gBL$N`@g1(H^`4izlllp3Ac>OIoP8F8#Ma`?1tvPeNQIzIgFB6$^@9g+>-F}uk% zl~hyu;SoOAL;Ok3Ch+KHR4i=8zdz(ij(kBoS0{vgd3VPl=;3D%PFB5!ard!-HQR-s zV?VLh9gjzHHgs^Y-4v9H#h|ZJkPncH1ac!0oG{U^6#5|q?0%2BcM$h6_u@wJup>MJ z5nRaVTc2xS4vGeF7huG7#D@U>WzY@(3w8|fF;DheYHq|%b-`sW`{^W3JDLD>n0m#? zEOb;&UhDNOfHc0c*1dJ;3V~JN4Gm+*+A~z`q5IRUEf!CM6=qwx7ava=pKO+;j!%%8 zN~XI$d;iLve;#YwPcKVpre@xnYvK6PMm_=YyePSaJno7+_)n%P)L{+UdGOKP;?&z| zn!h@3AURqYj1d?Gv2wJ(_Q;nOD{8;DfJ8DqLN2=mJSH8prmz6$9bujyZ$C=IP$!_*Gmm##5N87TVthWAT|MdEi>-pr + + + + 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 0000000000000000000000000000000000000000..513e2781c5457302321a276a51d0ed192a1c9f53 GIT binary patch literal 22392 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 g|Nr;njD2PP${Df$sG_@or*xq$!CMK + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 0000000000000000000000000000000000000000..72727c8f20cf236a2b72e3cbc1146bef4feaee52 GIT binary patch literal 34786 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..6754ae5ff3ab18631b06971c71a1146881eaeab9 GIT binary patch literal 19583 zcmd43i96Ko_Xqx-8HTc_$gZJ~HL_%h%9<=uiozrf*_Z5Lrk;uxo+;V4kX=IdZJw5g zG$>hPP+77KA(HSrqv!Lze!u_V_gvR=)s^@Ae&6@G&pEGiUgsRqmgYtrI|X+l2*P1v ztZ#)NX!t7{Va389%fa2h;Ex?X#4Q!&98YN8{8eQIQooLGxV0+-^{z>5!7*_q)5(1?n^^^T%M^I z@wI#q*c~zKd+eg^5UQH!gbeIzosSJO8tk7sbM{2e+)P;i<@(o*B+}TZ#c|-T?)dv$pt> z0{DHh>r2x{-3`=CIfyT))D@Hi`>feoZnOGAe~v*kd+7+$=e^wQU9%o;QuMIt30Cib zGl2`^z)ZzD#McP(B=rl9-lVV$Z3GqEBZpdJ#J?(6+`)L~UH=;C_r*^?M`wm;fyt&v zcuqUzq<15&BT5xUC^k|MN=s6~NdLh64CUk~8@;JE{D87P{Ma#Z;2zN;Yw{QF<~KG9 znZDW5eal!UAH_}eU>nVuj{KF*EpSmCcinaH=!F0@|bTY2om>QAsC_fbDy2ie`tr%?xRJ< z*KtSZVeza<152b*^aaLJ57&7XV(Ag*rjJYZzOv#MFQa+{jXHivX^x6Z-+8@3@Yntm z`dgzXUQ?@3et5%aJN+CR2e#CQb*#~(@Kptmhs+fAje}c=qZYp=i znjd%Q1zJfv20NTs$I4Ss8JjdPTyXF2y`Pvgn5Z5uqX+Qo-IYzn1?x=6Kq)f5m-`AM8hlp|^bTTzP*eo%~3DCu)Pt#ZP6!b%lWD!j9r=RL~hkqO7 z=vbg9aukk-nsQL^TEf8*(>VPc#WYt!tcm?!%Q1pP)Y-H(U8w*OTvxRyXX@iR)+rHH z95^NCcN5AC>MI7zB z@Mt?6f&OtPg?OlfA33)_s!8Z4YQY~*JFrRBAMsAxtafOmKkxfAw`H^1caJ)~{G*DQ zfYvf!a0wl+rktYf)i_^eY)`xvn`HJ>dxd{$m9lH}D}8fv)PwxQN@OnlfKo!g#ds{r z9N}XooQ%E7j3*`z)DyW_3?n!+sI*A2L>U}0Nre5naKDW7A_^}Sv}J3VzMQy%u(PTR z@@PmXEgFuhhA#1&yrL;$I8mbPcT=&cLUH?1-3WWkBa#uz6;EV%jP8DOQQujZzBv*X zJDSzdL3I#Jl+ww;;*rDwYvN_Jgzgu5A9fX1UZB;1f+sYxPC4F{DL&jdO0CQ9K_5-?jLi9yLpI?75G zp?Vy8Vk#9^DQ%QiArc#UNk4~~-h@iz&^MbI+@T>aZ5pc2p%ggJI0^r&l{zH$6N_4~0JTGFO;V$TA4yur8Az5UfcbD6JQwXs*&YoiREWssb$$WT<|7g;^E(v1&dlKnhtJ+DNBp^GqEI z+B&eHNFw`FXWxSGQ1Ci|pk?LtC3-PnvArR>y4Y6Yl#8I`b|;{na5kA9(#aKHwGi-;M_6D5Z$)AzHv75>Lpp(3a#n$#{*^SxB`mP_$TVG@c_P|)<09*IU8VVpDuV}#UYu zY_L%uuU+3lGPv$vbS2cYOuRjy&yh?v%a4DB^B$?^tI<#@m2b~lvvTT`4l?L%eNk&- z_uXOiJH-SNY-ZUd!($p-#ApUZ)BY_uF( z%O-s!+8U&Hp0e*ePv10k(v1@KBCud5-fDCslFo!Mgbn<{SNb`YiOLyn{8qO?)N<<- zB_>X`baMZ*E7~rYYV1UbT15dVq?|_;xhgM3c*`;B(AIO?&W*2K{{0Ij0ZW7Qd6tGS z>ekdKu2Z5V+&jL0E2*DLvJ)8=NR$#fjrlt&sS(kTQ(E*0YCfV=YVXbBRw;L|i^Cw@ z0@X1&Z?RS^x7gsNJ|2#(IiNJpt}=K?$>Lv2%-?#Iv#Nr7LsZH-S3h;hC4ZF3dl=20 zrLMFolabUto7*s*P8^`z&TKW})!I12c;R1i+&jn96ZPxhx#rrUXsj~3vRaoslWNu+ ztUNi3IfAx07r;&~Q)gvJAy%g`+{JcuO>nL=l&7Q+&#Qdxb0mX;8SW`EFR{gJVI(M1 zTfkEN9!cwFc@<&!{N4f71iOgTO9)ww997ctB*}89Em&7FMLgm7wD7Lsz zjji|Xn%-FWKG}8;JN)o_YYcae-LRd>(DIwg*Tc&_(FT-P*8KarJPp#Dg@f&d92aq; zKN<^iRp^^YnfmRc0*QMP)t9@G*GHoKl4RQo=n)t24i`WdxZ29{ahRu?pQ;OQD(Y0X zE|t{VcRJUsk5l%L$Wkc-9i)&GW*mA%H(U2B_^}5RW>QGnd9-V-1@^-_u~F-1U5j<6 zk&{2^$1&SMlneF>hmI<8DCWt<;k(M!c1=Z32_J@7f-g*K>erRDE(uTy9tB79-qp_d z)~%-z)XZ~UWN*k{Njl*DT?8XxmGp=mtHaKb`(9ku`El|1?|Xy~+Q?oL`#Z+fdqSFY$Nw&U8|qG; ze4x-^-dD4p5tY{W{o?2YIEx8~9#_)9=4*$*L(_C}OQ7cI77KOUBjtY@w#*;!g*a4? zbn;`!Eq4=A2CCrkJg*TvCflw_@M%~xx0(ybxU#*c>b(h5QbNwN6FDry?7uJbJI*7c z^7hvoP5f6`qIGgtHAa6@8lGfhGlMu?ezWO?ULn_)a!}n(D_qS{@lWBk!MoZcF^g{V zUKeb}LP{n;+C6dVSi>Z5LM#_m=%5=CnrT;cw;_DqZuB7q)CfJBQO8OaJLOnt<~0w(PZ`)E=`vVZ@>5EhoMbSU=FScu^j9#}}uyWq>_No($_6d{EtTHfU!z z;P&xZx8Z$+l`pWjC)mt)R1dB;tazJC;gwp=Fs*UgB(V(iANP-6xK8)VkY3vSlgbp%`td1B zJ$mUjsI`JJ#7lRW4Hb&wzfQ|ZtoTM5ckT%}`k-TCBgpM6c`{&~>37IzA*JDC`z#MB z1Y^OWmykGc#;N7X@Tqq7mc#cZR$g;*@kINvm&I0YqBIUFc~=aDW0Y8erKK0iuBv54 z-N?00GNlRdtLRFNrdG`29UEu-?vN%npOZJfJq6DbCm!@@XhU-p(3MI}2NZq}uaJ`F zjE^j;xAl=s!Q6Vm-167XJCQllOhVl4Ul={i{obGYh?p-RX-#@uCzYuMue7|SWZx)P zvlLw)hCIoF7)i=~{)X#ZHA&nG8ro2E5_*(?D&0X9I`4)YcuQT$ zy)L?U{CH4oY74CjYylJO&9H@nCK$}sE&1X59L_Fy}j>feJFp7M8@7!^}oB3Qys_R zbnl-wFNq&?E3!SjiW-iu<0N|>T)ZAO9gy&*ZCcvNvt~U3;kvK=fhsDLsO|%YYNh6$ zj`$VpC*gAp?~oUtH7_fmErqVnQE<91#k>Bk>}(5a=0M!DKsUi=X2R9M=Q*Gl*(X|-(+Om zyP=IXQ0D@ngkvvu^@C^p+MgckP#%C?Sf@0cR{0oa%d(u%o1`Kg`4d;1$JOysondHp z?Xvty#kfwcT!Ii?&Ci)Ux&BnaNiL|FSL3MC;t|RP^b+>AAW;pBnxch~Frsbb{@LH| z>B?g#ox8x+?)Hz*nx8@$x(CZGT&7fGmg19a?|wcN4t`Cj@N#5!obENAUIgIT?B9Kq z&4PQ}Q{@A%=}S}HB+8R*QH!Ao%!Ws?G_5W*Rb(y%^9Qg+Rh=p%+Oukze_ZF3=_ejkng7YW(3@>ssi1BA^XEXqiAt=QBx)CD&D~nzJgX6yjhNlS`-Ls)>JCB7AP|#8(W-R+U&HjH@Z`m zo1Wr(4rykE__8AZ`z%8S?(vw9H_Pe!s|LOqSGan`s?l0ULs%AyyA4jgOu4HaJCygr zmL4HC{uf1hJ@Z|wT^1IvLY=)uxkMtvmBz!BiWr6H*0$B-*WYi~nZ`XTg z3g`W{v>c0}6dbb+hXn6+4ATk+HD#W@$}|z+#Zp7op|16vc7On|^8kRZfuB=xm6Iw((_DQ2k<;PIA|4ttx%idSS zvptBqz#T!NnHF+0Af$-9r+Xc$Y9}jHnqj%m7n1{^QTjAJH z-U?&ksTU63j)EDV`5)Nr4%yVe`%Y`3iw{E7xGprbA*%-(b|dMk>wsZ5a{m(HV^rhj z5_WWh6!Pd0>twdUznVh*Y5_c`71Z#fx+q^NhR)UY>2|2O$*GeZl#yhE*&iAHv~YX8 zgHf`<(o>456$B#kJue#6^PWk(ziN=)=jM}8za^}psI=HlSQ(-;e6q$^GNNb}l720f z24VjUXp#A`vpmV2sp$~kK3j@6ddVgwX?XSEuX{g>RETpo%6mWRK3e8{ zk}7vVSvm$uPpXSz8on^L_R)&}P<;IehaCI;p^U{QmSY?ULa5SR=%BQo4r-QBD&4lb z@s3C`38L5-_W6kTf;b`cjN0BwYdxOCp!lR7x06l>PmCaES~d+y`C@lf8L8c)AAxuR zZ~%3#JA5%s)88_Pec@@26`j@v0!QQ8wvhehj5ca;%7q#oN6KE3K8cSjV6 zfV>CnRj+X%K9bM9@x#}Hnx)_?@s`8;6uMULYU^?bU!KOoubnJ#FYg?&P%6FLo)v;9J!Igk^rCj?Q*Z85Qi0NTMG8z!Vu?X9NgVCz z5dQ zxyZf!Rw`P~6e3`C*Wt)>vn;}7hIzBl+1frOhaTmesv~(L(h0HY!inmAv_9sE{{y>+ zec3(5UK$mxOCsB5w{^E)ef(AXEQI+FfKr^~Ba(od0tJ>_68tZCd(&4FQy|p0s}r z>86X*A~ytH+RM*3>qcL-mVlP-HO1NCeNRqG=) zke$k=`ZtGOz^u3G__aJ%_iv@+%lG|$CA5lb-0}Mwf32_Ro_6GwY23es*rN6<;TL?L zPrp8D$O-Qt2n)-^t-bkJ z3Ux(1FyUJsO~>yP<}5+JhfwOLT|v#{elDU>;y?v@DKvL}WH;4A%1Nud6qUh6O^E}z zBZdaf;XGv)uM7Qh9rx7JN$;PemuQMa)!hi+`8<>x62fqd-jSy=(-+r)i&A}1=m@+o zLRPS4osF+!z2r-tj5PzL>k+SNMY(LTHXKqamHJhO%Fk61`9JjLs1rT=%K@I0T8S$4 z_*B)sbo0%dxSIo6a;4bLA0esaF^h43ckSrx2cOYrp>~Mt}3FY!#Eh1U47df6HH>7aN)QHJ2rY>yE(;A0{#7hjAKC8%paL&!`#O^6!C5qBuGLwSM z)Lc5xrLyIb{}q8-PU=3HGZAG4eW|xP)gv9yDD=i0TN# zOwX7l>>{7{M$#b#zB&k^v$kf029aDpeeli%f(0AsM-zD4tEObrQzK7O5AMg2K&duu z6X+qD<8M3y{FN8-GK&i+g%Pz1m`kx6 zaquCfH4^Q>3$E@HAo@x$J_0`mP@4?wCkk#FSEg0{bY0YB_RyS-Drp9@WRqw+XLPQw z35CS-s$+fw`-SC+4iX)Y@tg8C0>UR~TQzFicig}3YI*MQfNTye1v zod-`P76(3F*yzZp{BiqIv(0;-K<(;HRv}hO&YP!r7x zQQ9VjTrl%@`ls~Jm@`{@7fL7%74348D+Jr6-GY{9}YUrD-<^JNPMo)xMDg|nrk zlS2H9rYv#0+<6ufyA6zM{2|NI{F7yoR4@{b?gT~99ZPuz9Ng+(``~zORTi_P&jgu| z#b|$bfbp(+>&|*n8%BclC-(BRf`{9(^-gO^_MJArxr-buE1husiiU{e*xIx0E18#A z(YYkXsHddFHGrkMptfyl-3)30U{jkXDG-92cwzwCUu!fW*Y_*UA65xFgCDMWsD|{E zHJ|{cy?Mdhi~{I5KHF7GdD*kEIDuAdoJN)XW4t;sRJrlqRfb7 z?z376i(6?5CMMOf7CiWX>$Gm(`V9=|z6BC!zgk5e%fv>S^Va_YS}n@{KbvV{OI}rs zoUx@0GUWG&qzn4MzPH5YQEczp)u3h|$r+S4mv|uKX}g#f=A%r%m6YbSRZNw|_Vw(f_~An> zdmH15IB>i2L;tPU3=pw$ZvmRF=zGn7MH(IBYln3ha^pwc!5XHIGXO2xcuJn)4+X_a zH4Y8+03IR#)lwt`KemIwcT{ZO3wXF7u4NMfRgUIbeXRWtkJVYYJI7#qutk6gjS18$ z6wOZD$Vm9MhnP)lz03s#l3{QFGHJ$Z_s`|*DPrI^wh8dt-$`*k^@vtieymxp2qO~JejRaH%T`^ zN5ZFgy8ZFpQY3Gti8Lsg=`;Ok0Lv0CVai?6EGp;IkdZdkKiJc-^%(51DSl7LW;fu< z*%HZ4&ikpyYVYJu;%v5!>+hw=_?mT3hKeL}XdemS>FUcx%ihzrP!kQ(rfG}u}OO`Irb_6Pg(*x|>7x-5p`?G}8o$lEd2qW3qb^gQ5-wve+6}vL)`|o-G zA;>M$qvATYV;h0JlR(#=1rO=BIsyd}Ls1Vx>GT!7&OZj`@*NCJ@jw6JPpr;+b^_a$ zP61}({^i|2ui}T>-1aw0h1Q~du)gZV5{VCTSIC!a{>1;vf14LRd-x23W-$#lCBFj; zO%9F07O-v@Uzs}bLEW}}S%qpXK;F};_bI0Tvv0{S+(HH^BrkE`zC(jgpAd1ne?*$Q z@}56OS?85)BWJ_#y~AiJ`I*&C22nv{8dGV8h%;Cn<;(pW*;q%cW9$y zTVM1;2(~%ra*=FRZN6Zgg-uFpHr1oqtJ(D&!};=^A8zj(x2(40Iu zxScCD-^`2-e>{n~6R^Zw-wROlm9$0+ZRETDi3Y4BL<&Wub5J|K-S&)f1H|}Mg0hNU z>Nr&KO!YYJB1(fLarjMNN~=!yQpz#s%(Jg-56?SVoj*UeM&I^TVv;MT`@ODxZ!DDs zGj3%48A%H+;gk1Yy#QV?=P-Y;)ilmo+Ds}i*4A`r;~=pcWVmEGked!b_y}8|Asy&uT2})NPO$bo%hEzN5~dP05U zg!hZqrTm%k3t#BXxWIrfN-AOhI6FF9!RmARvpKm%#7>q!j^3YC4Z#*uKUc63XqT@V z_nK>HKqY#l?Cmoo6|L>}u=GRW9cga{p9_ln|UiE?Ne(oQ_ z1o^UaZ1)(j7eh&cs*ym;EZi=G9xdE6J)qZp@<+Dxxi4B>Q#&dDP~K^A%uyf)mS-Fk zyn?y?U#%KdOda6x|NSow$!_*R?aHP>ENF_L#dA3do{BFSIrGf;WI?Vvgz8CTPC9hYWjJGK_TeV=`n4t%@E0|>n< zC+($V5uLX@Y9T}}MHIO1h8;|dKk}C@i{;Mfb^-A*( zda{4Tzo}cg7A1b`i3%x3wWRu`S3v~>8xYqV~&S6lrzp_Vns6*(N z*IZv085kN%h)Oa;wv;V9iqy)1r!3u1|6uoSz9+yPIXX`7V;YNU9nstwY{(#&Mt4wB z8B6UN{X|SnKa9vL%3k}nZ}cxwm#9`l0FI{K3pst}a*Myq{8k>5m6onA8-@H4ZlK7FxDBkKUtT4){z17oQqRkPGN6|G; zTS^simtBXO*-1SH=|-sz?k$~RzkkWABNtwL&sq(zG}dyfbmz^4>Oi)3{P`mPr&hY{ zSporDL46nBd80Tx1qXDWK6}b*kiJSA3DI@CfB)O6V-RiRYv24^Vd4cX=^PP@+3z#e z^6t(f`&S%X$j0YBtdAS>tX*hyF!Nq(tV_dY_L(lU zzUUuquuF!x+>rE3gwAUx3C*$gfA+)>Qh=GIq0OwTykT+5%xsO0Uth8u4S{Htm> zd(abU+VQ)ly>zPkAHqsuW^w$L2eX`RKckL2tH57RC3r?N721@$u)HaGGE~%CUL}YC ze>Usf@R-oMc>a|hKUx<$nT+p6FsDLkh`jH`4L<{|mo32GYB|1t8}ix$Ih_(kK+m(a zx!G1mAN&H>=B1(F?z{@gqI2RI*?txlLx&+c{|g#Yvo1I~GPqG^qVa~Bm7eiJ&fVFO zd6U7Yr97FpCgTZJ`=2W-EvC>nvjdy+KSt|I@dOEb0q|Mc)K7d|Pf63){2{J%VQJIg zrHEPW4UwnWzCWPtiY^*1OqW)lLyfI_N_f)|FF7W5drwaGXSfeFMnH1*7 z*rZe*{X8?eVsbrPD;FthFOFHY%3vOUGmTSSWO0hz0Z|=T=M3u%>2eXrFVC48l+UV` zbf>yu$Wfp3cn8yR!$5VP>)?ab%4;;s58{qdX9b`ZDKX+$(i-GkUcD=v>F2PdW46d- z;(!_{q~sUV_|;UdQP8(!_fWO` zPSi#icT#I{F?p(EC;9vxv?9d#v-HShD%+`F4o*SMJ8Q0$mwvo`i%=D|y&D@u%)}j!r8yPw00j zRfu4g?0$GtwiwaGzX?DuoN)ejR`z{1iCo<^ZZu~Pbjely3R3>S*VwGvA3F*MX8ehF zpjiYDFef>nYV%it3)qlj7BDYM^-Ft5sEsOWj76y*F|Y`Y*zkKM4(@&)a?sLz|;};hflB&r^Q85~Hq- zNs3P=|Kfm9zpkmR?-7*HR(H?@cI&!6QzDL}HeQtaYX%T3JCyyTU{fm2RW%@N(S z=8yOI={-A3ZR}i3tnql>+b6LFPoyyJ{tn( zq0f@MZyB1v@WVq83%i64!^&o_IZZWQQqx5e`yLjPX~wbjM+A177HLW8OGYKNiw91e zYAy7A4LUMKNzRlben=K&(@tCb7H=#SVK}=hCo0aIC6Kkvj!Ss(-sMOkHqgIt`RCHO zqQzKB3fO{`$iVbKjJZItKPX@1ALY}*1fRo?tKs(X(S zX$B4yK50tf|6}uy$9nX-?=H?Ub-drf5Zf`l%^A^!^G@@wUM2z8iUM2qJnikx4%x!V z2=5f$n^@gXdv!a^;2APX??W;mS#cp)uu|PkdRv>Y7n6ZG5h|)M%)MlVBT+qsMpG%R z^E_d(<{lVJTV#kgu5yo$7x*YXq^{W>Iyijn7(0HijRz9lw&!F>;sWS}H*-9+et8bwh|w2ZLb>SB(^WIgVxNK$dY4=A}(65l>#vWJ$ADbfvFtdPE~uf zRTCqj{j`-E%4?d^Ge_I@eD*N`C(@kY7qY(%C^OFO4Il0?JD@D{RSlL9{ELfp-V`iMCozxVdNP(4_j6d%=Nf1bh*kC=Qy&X}XKuwtj44soAv2}48q zXYtYJ6<((Vaf6&|yOboL(Md_mX?RQjp*lj3&?>8z+u&XJ1WTXE_}O7>e@9Z?2++VO z35-hY4a2A)_kA|Tp*N19#A22@R4S&x3EMIM)(+r#NFBG8m|5GCiCYZnz+}bT9WgOe zxK+de-WlQ%64`=j0y|MD3BZMFfexC7=Z!d;r3|oXNsT)-;7AO!!Ve$l$wDDMtBRN{ zp1rmGj5de9!bp}TXj|S5I1dQMxs)z2VJqfCT>l_RxZI7{B(^zPy zG+ZH6L7lefsPXJ~{K$dx*jK5=y0{FMp%Nq!IKsqJM`&r?}#W8V+Qv4 z{7?mkq|Rih5VY#ZJQ0wy!lAc6^yp>*VI`l^U}KM#Y(8$KS%q_V)@3}cbm%{3Xr~+( z6crs0BR$JGNA$W0cSQ<5*S5RiPmO&yo=X3=AqVUknqgl}^+ui!84L;<8Pb7;byEhQ zABh2808&88cwR_UEwBHdd5uyjt?tB5?1b6Lk;BMiYa)X{yAQp_-7oS3<(TNOhZnO# z6sc|HGe?Vx#8pB=f@8_VhDA;(c#|!Oab4FqV7mV3pSm^O_3^JkMA+JXNe<#2j{{|~ zr*x<3;gv>3rGmt^J0vlng4_wGjf_6|a{8D;a$cd(CTQ!pVZ|-LElZTYQBKdf{dG(| zZypwRw3xmATSr>zd(X+`$R*x=#o2$W(jKx(_4A-V~+bq6){4fl~iZg!*H4ZsO9Qxy82Y5&*nH~fX}sRok0R@)}FyyiNu*_2dCrOh8$ zd3npl%KUNR<7tPrPi6|jR;#>WLARoS=M?ifg9nx);soBI??(Vw*zWPn$I>n~AD>PW zRL2RsO=gCQ$p*s?GRn(^B+qQU?$N420V0=uIJ6j8C*bbrP!V&`bd!MQhNB_5Kio%w zq=L$KF7w0>{SkEp0S;~S3#D~{P<0OMw~6()%QWtGKSVh{-s~60$=fB? z5!(+{ct+8N|!sGwr)OK#8PsII>45LAW+SL##AkJm^$|y}prGgf1|MX{S zeI(pt9yS-1UyO|qOspzis1U!D(5kjE&!fi?`G&G%q zVxqbwc#nIO1oV=5L9RtlJLoE~K&+{$s^x>A{#^zx>V}4H8#gCx4q84Lv~i&aAI*MuhGc35@_t0zo~|IAm3@bC!3z=HPJV=Y1@;Z( zkRxas<^bQIhPzH@v8VG2N=Q^Xd{C}PdTk!j*h*W!#CO~XTZPKM$aGy%X+G8M;8|KK z^KXR!9*lDcxJL${DAZwp#=>#%+b!~G>_2y8kh~zQHe+<~KfOJv4*?iIBjv%qmXC9Z zHNd{T@o%efNkVP=|Lk>Rl@#(BTZcmObJfBXl>D9~&G7&%#$rZ*qCpZ9RLkwWAMUzp zJ-k<7jZqUJyN%k7{rr)_fX9b>e_}JsiHo(Ke@%5SG`RM*isDuV2E^-j)&VE_k z=lC?vZsBO#Kkla|>eeaV>7sYBnaRVuYdyjelNy!&;23rxx;tRG;0|>C0hVB|pbe@Z{SmQCuE6Cm_f&U58IVQWnJB3|ziGfTvSr zZ2CN1ooQmf>S0j2in!G(qa&}VfnXFpArw7(w-1-42oFwG%eINvifBL@##?|bdSJzA zch5(NfuZv3XCc(`Pc}G0lX)po@VVr=tweo--|M!n_U8ud2l2;9El)|WyGsB zg5v6jZ$qidXLTMew&T9`C+Z5se?84sbhm`)c82*W>9QP!;$8F^Ob_my^fSy+!NhZH zC;A}f%1K?^eTfZ!mPX%^A6U`3&r3B&%syGQda){{8NasKM*_YD&XE&uGmP z)S)9ItxC?oCz|gtlJ$Of=Oy8+cIt6?-dA<4&X`gReK&_*&EL8o2~l-$j$Mq zUih)}*Tbgo8Ht7Atedg!&poQC6Vl2i=hO@ho08!~{{^+3fvYK)CATZCLI^~`=o;wo zmsAyyB>Y7Y>?|R7oL4Gl6o?{p7~wW-t99L4qmH(CNU4 z%g#A;Av+7~f7mHOp3lM6g86J_k-k%E5fG*^yIqy1vVe8&l4`m*wykHHo z5)J3;Mt(^J_uW@t5{t-x&~FXN6xVg>|0Pp4;X9=P4)#NF^yc&{0OJgRP3VggauSnqV7Y!+sdPRc)!*=l zl+WpRaQrsKj>h}9YWcT)CXLIR|4|$ED=yQ`-8y3Z@=7ow)3+k2F9NTy2Q{ble?6#( za)4w8OfH=FXF_+tsjfL-E?^diQVZYuWL^UxF5^mvnzu2NdxFlONzY)W#pPmFjIt^x&} znPK(yUNP0<6W!W$cuj<=~~;N(RQJ|m;qgih@+Xk{=Lmpvl9|z>5-g&R|BB<^7^Q9 zgWyk(KYi9sD#Q^OF;lCs+t51e@@gkf<^S?;-BL*M=7mPPdYA~`Bcv3e+A1cL)g{$rdJcYS*EF zjycAm=W^7%_BY>^q$E!WRtv48gdTQB*Mb^gTruP1A7K_so3MH%L<~ z{vEs}7;)3(fn2Dul~xl|Fhnh(qmF=9z~sKpO&mA|LqX)cy7gpA0}19dPS71JpE2nt zxf8-51D?iEe!x||SWPJJ)Spz(fL;fh&a~?a1!jqN&^LwJtWl>OE)x`iu>k>0 zzZhz|{mbz#t}9xLcxC(gQ;y9_`~y{rMc7$37Qbj?MOTY0Z=+T*+>WoF2=-wrXmur| zl~W`Y2o!_B2+*daxtry^m=>4}l7q>HG5Uu`-&e51f7E&7uYPjI*EKaClP`k$p`QtE zzl#T{={tn??5+Jo`;qAVkTH7EjM>_g7*v>Kz|rs8V%4_)=NZD42gPEyd81#<KAq4$Ys``1f6jFN-mW*1KxvN{$H{k@j<6hs z7QY?BSZxqlS$$u~rZM=C_fXD1(I(-0df1b&&JgylwxS>jn%V3%L$Wa&9H_G4ZP)*1 zJ?nbw-C=iqG8D0!<)Ya8Ht)PEPutgONolMsgGsAtSWCW)wC1cVELx*@kwPkHjq&Z4 zx-bo4K;JZKV^hj%;MS1*6Ci2&R{L$lEvIl9gg&j}--Kz<*TXN?Y=gE!@xv5o=noge z;##hyyAv+3G*tFAA_%(xNKK=;zkNU}@ zZ6h*_@z**kt&g{!sfk5Nar=Aq0vh!#ht2{2Qx4^(oaZru!&~1Ysk3^c3){i@1Plnl z;37=D7g3s-4b$-ia_48eO5vJHqf+}8p4T|q;y<89oe(XGLSxeG+F}A=lU)pi5tvsU(l9HS&MV}js}T8 zlIQgCO>m&6P+IDj?Qj7VIfs;Y1-uEq0C}{MtkNPbC|2~K8ere@a2~Ohr_v>PfRjN* zun+az>N{e%w0T93#&i*axE;kv6>D4FBf*Rtdw0Y1Rm zbNKl={P4w~Nb&9QI@T|A+Ih5h%dwtvdm4<XzRg{k1dPAj*6h)3$F86$XnaE zKQ{g(7c1T?z3_LTP2=kw3A?Ng(=+q`2*D}dej+94EHl= zyGZb>m3_I=?c?DEh|QYXkfd9s3?$Bjd?QKHSvXIt$&)z^_9$Wbrftxc%10T$q?Jmo zB)%i}_&Lz7v;O>WU~|x$q=I(miC$)L3nuXslRPo0ynEN|N3*vFK>zM2pv*CP$9sCp z9Xy>Mc<7AlpuXBd1ebsL5$;1g1iTP5JMr=%w)$9uG|iXSNa`?{TXvsO)Uy z{X>S0!0!O~`ku?uEOB6$)14a)t+Y~TRUr#1SBS{W4+dYe9P6R56(&hP{96>&-Qw4F zrA#`zr#vHyz4}p9*wP446Dg7K8!_^u-Hv!b-(}P5jx|0q40$I}pj|$;hA|-PjE14{ zaeuwBq<$5uM;azjjtFrASs* zFPTpyZb(x!`Y+}XafvT~i=ArwnIvv)dtm2IiTs~OSZ>fPd9aw9hFP4UN@}LNzq6g> zF>1GuX}|V7aM#OOor=}ad6(w-Ut9Pxu3jhmz8mch>YWk8t0azFINqCUvCXzI|3xdMA5>-uT&t8KV1?4vhcwwfia zg^z+MBS&1(-FSSjqckvVvwRTF3WCmtGiGPdAj8sF!_^*WT_<8np ze9qLb9X=;74fiD3{-`#w9!1`(B&{8&?3>OI;~9~sK6-JA=lj_M)xS&1Ir^;<^JL)b zd4t5hol9P^Q9ZXp9>z~ zG^K_kD%JcGS5FEfzh->#?w5u;CB7`c=1iB|Gu__MG5E`;<8!H~%)E(&!Cp+*U0gGE z=uF)Ht2=OCSSYJ(65egpw)!}?yTVAQoQfk8UFQBpejX|Q36oD2JigouDKA^oC~3Ds zl9gGAoAsjkn!Yn^r_`8iM5GR0-Ll6DJ0`Avr2dBeSWh7D{gc)d9_`C)3nEs+3HW$; zjL_=44L6UfY*{AeacUs!uV|E*mkoostm)w|5BY-Fe2h=`=27+-*iQeAp&-r^UI&EG zMZ%_YSE3%S?8BKeD=6G=kWdNYn<$XrL@?we3$(>f#GB<{fGnb`<~y@%`Zk6_yfQI(%8Tcq35%Q1^QQqj0$Nb` zpI$5up7zJBN@tmS!yIETfk0}gzxhRn_DxYdI;e9crui2*k&4ejlt(3|#?!a#DgXBF z@77@<3Me(-4>~eR5H-0skeIaPYJZ&Ok0kOWt?gR)+N&d3;>v{ys#JI)SY*d9Jf$n9muX+)csH3iY zYm^1>o3zhiJ36R&@B{gC2*HvN!J5ia^TDX=X~bIR0ocalq*CZ*jr5`!SnlQIR+IS7Nc_*#EUr)J_?OE%%ikB|L~ez&XEK=I*BH zCWr4a#WDRe`(M2J^q*Vtd<;?BIxIva7_+~9oYY!(e%arc)N4WWUpx1&+x6vbZTO?~ upDY!UH)hV1*ZE{ONyU@yD09X??)2-M7PbmxzXzTu#^CAd=d#Wzp$Pyro+xGj literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..2a23873dcfe6b5c4009a676ffcc00639797308be GIT binary patch literal 22775 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 W#X!^-2SF!e$j#Zqsq$d(um1xNO;x`D literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..3c85036ff5bdc08a90c990f11f87fad8ea68af65 GIT binary patch literal 18533 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..1671d05bd995246b774f6152b8385847bf01d72e GIT binary patch literal 16847 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..26778b91345b0da485462e45b6a3ae216e1273f9 GIT binary patch literal 21549 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 U$5lZ(nEwe8H}CYU-9-KEKgVB89smFU literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..b7a91b769b0835b48e8a1188e24dc258501f28e9 GIT binary patch literal 25456 zcmdSB`9IX}_dh;kY?Za_k_-`L8M|y{jVwjTzLQz5n-WI$>|{)Z zv5#yc!uOhpS;f6m6dUz@o>=GpJz!9i_W zjk_Mmt4>yTZr)=Ef(4I{Lc0@V3r)EqFhSJU&V6kth-PHuc$$V>>Xj~yzsEs;4<7Rq zvPw`+w%)j?KsC{;d?diPXpM~V~sU1D_<|*;I29o}Eg6Y@bD#S@G z6v-+=pGncimT}K_EdPR5oJlI8!i9P}z5VphYeJ~w_y;ZPX$UIy8vHr35cvnS&Yf-> z&+h@-HpYr!j-S@VA^tePAiMD@b`0ClTK2O~N&SePhsBqgs2}}o?-ce$@aDThtn$=U zcp;8ZJ`!6vc>MjmFj5soXeXRhh#>^OkVBR^RTd3UQ#H7w9`QmvjPn4a$ks}iLMsUo ziAGytpM6t2khRv@4J7q%B%ETVBQp*+20OZA&{SA{Y2T~#~h4Y<~?X-I*_>w~|R?5#AmTGk09m9gd#PM6ss;#FedtTk~W zJq)uHDvRCVPYKq;QQ7z3&*QzU9uc<|NpPu!JrA^Dwpr_UfMkJ#SoVm2|1uqdk1>yU zub2&eEDGN_kIkbT)g=idIi>MA_${#%y6_>S;=z>LzRCKD38tzf(_nL7jifHT#=fKY z!6ZrD<`>kmsI)J%I8^0buIpTW@jj{>Wzz2Msc-ROK+ZvcMkh69@|gkFaHP(Vb7-%CnlIOwcEFV9KD6gI-6Vt3Os(q_>M zE9ko`#Y=-f;XuhE-HXs%sHd97YT?w7tlmhJN>RB5b{=yqs2R|c;fQTNW1Zoa7WBB3 zG{wK1NnQ)ReTgnf2Bi_3WVwx5D9Lb)U!8l3QjQIXQ~fgeQUVH&?gjgZ=}0q~v?YH( z%NQ;GL3#gh3{PMmeY3xi(IGA)N0tV?9as$S#cJX z3t=A?-L%zu-A2eBJ+az$##%D$YH@%-K&mW~pwvFt&PcgV~3RnjwbA zW2hnLi^b=CxdYvDq)5{F?OAP|7u)f0Q@YY6h-*~!D_6*UIXtOFafV;z)4yWHkJ<=+ zumz)dIU|+g8_Suus-d^TqtTRDlWz0^%u;VatY>`K$&TXiF$>hJFx7qyU_rMRuZS`N zZ-;N6yu;m-acc*2#?fSf*pY{OMcCxMPXaqoQaOMP>BavoCY+|n0(%{`UVK!R(Ws)b z(V<6o@LV9#CEQxmSayWNmBp3~6?Dea64ozFs!)*O=xhZ;lF%g?L3;VsEPl0zsUU_~ z#-uGNLMt--UPnLPzmAXqTO5kHD_gCC@VeG=fjR*}4L%U6V;G+kpUYxGDG5P$(n5!g z24v@hI?aiXTVakDAeM&la;hpch67>-V1dLK^rKo;Ig?Q6_ex;PQE!^Zw(kj2#K0WR4sqkiT#LZ=Zp zrWI2vxzdcryDGPUoyc4vuPmh?&tKOzKyXAGz<*=#N{t?8m$8>(1Byw&9=Ygc*rYWP zI~VaMYQR^tsTRRi_y_qyrzGumUQqZ=0q#}ZDN@& zPn{tDejV^~dB>{ho3ub&rRZah-K{!o7PYWc;Gm+w zQT_nn#;`lIkMVc_j%hKKaN3E)Usub!20sRor!WnV|g`*0R880iwmqMoucv| zD5avC#07rJUdVWpA=N3q=@^fI_~SX12+z^7s*88K<#wrDA#_R68?-s^S^j9_PlOpk zKy^Q%uGU20)##moS%N@dn_c+lY-k7ec*JtPKfdwJarojN)C-2)0SoYaOmrKGv<_m2 z4S&K-{KlVucs;9ZFpO|fL&WgLCvpgVYH@qobv1=&Tf6eMTF}=+=Q3#b1XOiY8js)4 zJO>**6q(sePZMh+%n{#Dw$y&c;TdPMr~wyM2d6X*xozy!vVOiBZ3$^425c-|4tuVs z{1XZ;%Xd4U8nS0jsdfMB+O@fZ5{X>LcqbCf@jj|3Hpzn|5qK+j+`uVoou?a}-Tt4a ziNDl^X5k`lUQFVO4Gxnt0`VZL#s=)(8}sgA8M}`;evO)lO~L~hy0KhO#SR(IjNZ_dgpBMAe*2h%=y}{s znb3i4rL)=UAbUj7N~;PkgaH-Dn&MAJ{%AcsS{Q4z~8qhxqU@ zjps`nj6J-z%En@>4thH``mSutrYPqprd}2zP7p{+Ao7|?zry3d=|8JCDZfPj=lZrx zH`*@{-&v92C_tx_LE856O!l;yzM7lA@yfV zI4%I6F0dpaVfyd^1za?(e5oq!pA%+6&vQ{hmtnZVDU#VeJe6xt`2!&chAjR8+L?kW z2F-p?jINmcfU-bAh?Mub{eH6Bah>I9&N(j1ZO7Wk0^i-?^b4AGqQygP1om(MQkJxB zs)#Bl;0tPS(e#ZbOF^k1yu|U7?9d3h+7e1THei`Gg3dzOmuF4eZZ27?<}$02zvBixYuX7p=^mGaGSD>E?jwgx z$ex?WwU+??22!JZ1RV_(S1@f8yT8sxpq4gM6kSxW&|6lJ<72ld3JG!Odkni-vDA|)EOgLi@>uE{BPy`F?Hb&MP%AJU_HoTD z93xrvFXe+De|&)}nC=lYN89)2d#5XoQFP+E8?LOFV?Ok$!^bvKf1=}n92Tjv=!sR1 zO}f`cxWTY%VnO){+DYC)s+C}t=oC+HcfzJ+8%%b{+1*yAYWLB`LLWRrra5<-RxqU$ z0Q1b$Gwi>>rA5mXOnvBqRjpY^Q2PZ_{;;o!d#r{zzJj&uM!!cj#|CHv1biL9T<|CA zNhKjP4ulhMoeNxMw;RTO%1^VhR@iA!ehS4<14hQUEzynES6#f?-S)W-ih{(T@A{Nb z;bHCu11=g|5w?A|GR!p_D$pO(y2393n|>K0Z{WA5{xlR+3b+&bmey_W(UD+$NF#X3 zIW9TH2v6E=H!Kppl+jd4#WlVAi+meXrPfHcJEyYvG&Ug80fvOqC7q1UOYZ##{w8Xa z);5$sc2Rhw(U;2J#m{!--M-xOkiP)WFVfd+<-h>D2sfsr(*HUezWJcCl}<^W^2fv4 zgLXcf&g11(9q?9JU;06GP-jZHazR=Es+V#1d_RWtmO@=2EeL)T@)wjGjYyK z=WMlKi`YJzRu$(41k&^TNFjp^Q1zV{^i^s?F*F|Q>jXnw#t-oNJSF$ z?#f<V>XH%*gVG_(jQ6QN zG7)_(^QR1HQ0Vu&%+QzMv`LuK*_tvP!9jGGlRlPLCgz=~veYRbCCV!JdhRysUZ{Jq zjQK^SX{rBCg0<;3VrK#ca@+ZCa>(#)m1UCrhw*JQL1CT14=K4W(WADPGv0D z3me;BA7jafr3oyX^TB=p?uu1IQ5KqGs&SBtkawol@@U2jT=7&~W;&DIcn;un+BjRT z2xZ%U0d9^81_3@YAN7bu5_0IM4)b_8(E25)^8uo2M6-9VMDu%2m0mNA*rA!#42^8WdYpi4cy3~`#eJTx!G>)1mBgoDseuzIL{jwEw?SS*=nHltPF6AUy&WBV3@OD@q)1f&u zJ{D})4HrpQ44%Ho74d^BVmEvV6{LA~^ouJ3r*d;0(3pHuz_8^m9~RhHRL(#E59$i1 zjj6gnaO3TZa6V8`Qq54n2D#nJ3{7}~Zq8u;h}JqT=(4Izu8MewIW1fbYAab1 zpr27k2sfI9LhjEVG?KQjE>YWsWfW84%%qH^6m55~&$%L)dxYMCPumSYBk7>tSm;e} zXY!9T)Q0_N$KUZY#qzkWOIX1mNI&TR%feOFIo@Y6TZUY=M?2#bk>Ag8X#wm^EWfmT zgM3?1DuOObvZ*}QA0Gz${(&VRHhMsBpuZ^X3M3kR*XoVUV#vyAvJ`BvLa8U!TZQt+ zd`+CcCZ+XVVyxB)D||&cB4~N?>s)Ye3DZ?DkO=E3AgD~FgLm{FKn(!;cn_U$xS(xz zer3^pjy~-yuhD$TeX8PniCw?@+7K<5R)vQ_Jvb;;!ta%A)kTmGn#XEk5c(O=2`N~T z{6{3S`WR8Op!8Qh2*2Y!5ojq1J)YP;DIAgC^eIS#&p$46o+eB^O73zCzj7F3gHeTxY9NHA zOxz(?;hQU!%ORvqI*2NM8C+mr!}{YPB8Q+}jLJ)$QUT5aaAPUwf}OTfTqj6#l~mC_bH z`_(Wc_A%v;Zd5zUDBLL|ct!!WbYk{llJ{2Eh+C}WeJNNN03Sn@w}gO^Zx><6Zy$%? zR@HioOyyHPUD7z;H#a0YxdD*$yd$6FH0kMdr8-f}_ z@Ksj}6HXg91j$b=K1t;NX5)%DV5@pyggFixF~9dTEm~!Bx1eYhIFTsnykRXZ1eA*P zwSR8w5)VJ|99Qbes|U_kKE`i6{^4vm^ldaWq4X6!@LRvryZr}Q^lf+u;kKEI3f zpWw@Se%gwVtJw;-9w9u!2_5=?+RNZhA;Da}96hNw&vT^)4K&4;M|ucWQgcxW?;g=2 zTU`d4L?iAG{vn|Tg*wZ8_KvJ7kI_qIwm0vIJIZ8ZT6y>bbaP&Ug1BMwbE1)CfC%%U zily)^+}e|^lTbT8yMx!G_k6!u3X0iY3cuidc&cW6S2Frr?YnX5N+*eZ2-5Yzz~iEF z9iN~_>?oN=`X+JWX3Q=tt$3H!cyAe@rkn?)`65K>w0iQxhbJ=-o!=r85|-Pm)Nx&! za{0Fo0?>uU9KZ#BtekGA`QweQFK3?Z^4H3b_k58|JDvNQM?LZcwVPLEE`mPo+m-`MvX3=xwLDg#^1BjuNf~XCW=ccXGTXk8O4qztDa5J_@WE zA6WaqCWhfq@;AP^4$v=pAtiJah5DO3vn&1L0HNKf=D3bwbGF#Os zB@U(wsvUABiC;2MZ7IlAQyx*!z**F56u6Y>JrpRCA=fMvQSE1WCLfYMn{8}vuyvCJ z_gxoiX{$C9nScP@kHXeNLIMgA8z7M748Z5OuvK$U65qc3jA5+vUSLp=%e;#lTWMn> z!Rc&oZ%=MBe`{;2(@Ke+Ok+i>MKQTjN)j$X>(#u;_&nk99neyr6`0oy6ih1wH zT+?mvSQuKHD4S^yA}G$^Wq1( z-1)li(OozBtsrDqj&~Nt?%q9>MDnfs@p4^3VYxOI$`XTA$mq6uCPk!~Bll9$*HrLzo@%7!=?xNjA!Dy?Wt{eJp&USCjx8@%V=jB(*g3nwXBVh7} z2&a1;gpy-5(?)lW+}vi114Jwc;)&S-XUds#nM-Cm4W{o+o}-7(p(kFb;ZJf=xv@#i zv3D!gb6!7=h2DN|I(D%et-f+bJ9Pc-n2%Xj@jY}RRmi;lS?GkoV3hTr$+@}yqy2T` z23OW>DUU>R9rOLYz3RaIwQ2i3Tu6~^SpI|enG@H5CrU(Yw{%$e49P=t^lf{}!o%JZ zV_smSC{-eBR@ZzWp%`EhCp51l!>@(Vk*0Q~Sj{P`p~UIvL9HW_FU76>=tYHT2rh<-=~_w*c+J=@yU>IVhH&OEEHGb*!#`KIwj7qezW%!;9QGrI?_+~TZ)3>u0bCB#bPM1(0yWg!T6z+2U=LEw z0&T8$Cyz^sY22<7<(M-GtCGUJ14w5FziX#zUCbE~SJ4`VXcI%TBa@#0axmj~^rBFrv{Xb(fGkq;j*KN{A^T(7ZDfaf?db`Wa zoF142%SCwJ&5Tz^x;PK`hf)5xCSg%nsYyNK90FdsYDXch(7Z9TflNW7tj2Xlw86DTtn^@fZq77;&dKpdXryA*+Diw`}HrN zm!%b4=|CLVHa=4Wmy`PK>upQjk+ zn0+S@IYcAL{oU>otSPNAEHkY5dJI_&K*fZ#ujG{V?ruE)YF_BW^eMP@UV9=-O(Fle z<-N&j?f#4Oqb4s6r6SoO5ToR$w%(<6Pl)?p7^nT zLD%?CL6$FnrQP^_`771R&WHOjq#mK@5@<*J<0W*nozFRCCEgv|v#G6T%)&`khn@ub z;xQGh#;^18u1r>%_XGdV{yC~j$Quws zbIReY=-zxWJr3Ow8z6CG08;RA)8kwqAB+1{+1p*?8cwDQH7H9o=5X9J_Tb=v$>t80 z`=#9H*?N}?$7nchOH0e6ItT3y(WBiMy3aZPpiX5|YKk_B?#R`AJ;zjcQlpCL%*5T= zmZ~VqZTJ0V(~{=Ky)$_huObmZHpxZQuP^7Ct_j(%UDY1Lkto7IeZyRr2ETbRTQ0zB z<6_q5V(skdW^cz0(XXoz_Z6H=CAss=tjMc6N_WyDIURpwS_Iwf@tLSR=2#JuZxpYk zvIyE*SqwIi`)dvFWV-icYb*7LsG6udCQcSg+a~HOuiJMFuldpSLd;qzzxQ$mQ~(`6 z{D3Q>@HlW(rZHibd6wf&rcl;Uv-x-jX0_RlqHbw05;@stwTb%qHs{j)XeeZ+V`vrh zE87W@1e53D+7!U<8rUjSZ{G}lHjzWtYe6=2_M_9sM%)_AfJ^@OmrJTE>7L4fQVgsw zK$H?#FZAcCLjKYnIwi2$GfVc|cgFhG*etq)H(p;h5H#}mI;^5XbJdix5Y$2;6+yP9 zCRxQ*T*qI|_-iqX%^w{Gv&@7qvdj64=r`0|ari$iK#2~B6Mgh?Jgo@3^!qC2b)Vz~ zs;}Z_{B9S_Amcmg2i1Dx7Z{ zks<5~tT{{LPjr!IfI(QQcs+I9!Fb7n_xRSjg_&I8$`3O3F8f%4H@A~Iy?{P|F>BH@ z#U!wbMTH#9@cFVvPR-AoM|hJcTvYP6Ab!atiP3n*?;%jW*^6Geb{upiS_m76wZeTL zETV}D@MSZ8E=3(6FZqWPyyU8>_AHK536^8AcEz3?99b7S%k{h3fon~5Z1r8&o_N-e z^^7*wPb%FoZ8CTcMvBohiu`WW*<{ApLR$MUENlzM<_CFk3#a2Zv~)~c`X%U0%7->N zsPmsy)?OdL)*tdMRg=<+kW<7wR9t<^0$>P$c9;s8M!?NT*c`ziI zVO+e88D<2&MRA<3OTPk6>`w?`7}!Pjn|Q9SugQY?=sLReEoI!7Vx8Qt*iVtN9z1?| z!8;mPf;MW1EN7NZsd*xQf#S5TC;o;qrR@<29X}~u%MpU~OaZoesQL4iSIg}Snhj}6 z{c+(0fo1APTjZc9N#BruMC?uDjX<+TT-#E&p~VmJs!YCPKNWn`=H{kWcG|jxYUBfC z3KyYdz*@3MFsS6dY4%(U>JLD$RlC+g1?7XBuo&-3*2KcU=eJF}+gwys*6CWI{S%Oa zad{_1!9K$A0?cyb8vuQI5#Y`+Md)&OWyxquYpXX1%+C?$s5lkmfD1GR5!@%MEIC`_ zPjfgxO>-gIdkb{-Tm~{Yl`;%?f8DVP!Z(i5@5$-?#~}9gL5e}xs=b*qTULYWL|r~-Ha0wErsC%2 zE{it&P3V)EO2sE(jxC{4FBXoE z`=a*p%HVX3_A!r|wyv%&eFIsCENG2&SlkQ|z~k@!IQ_rM=b$hA}7cuN=HQeM9J|E_<}juRW&WlocB8dMAGQ zNq=)1u%vPnmG!rG8a4fo&j;lHhFXxebyhLoi`!uu_dHpEa0=u~jl{Ya9CVz2{pcoq zi6&GpB@Uo!URVWVa_)G?i-v8@xg(}mxswFzELT&@3Sv}!P38CTS^OFDZUmGbdB-VS z;blscg+#|A*rJU52kY%%ap?e&%C6=JTSOYMH;*6=16~G1pxa`^V#8F@bcfPyr zJ0QIYD9%2$j!D}F+V!cbk~*{NpI zIhgmeR7DmNE5Ogl(?~A1=D`E@M%8ya?hZ7NP4-1m zvrpH%7=bj&s(Dt17UnxHsB2Utsd_SWQs%9*8{lW{k3Z9DDA4Qgj6r)d?w)FVgd(Hk z-<{d=udn){Dq;ghi=1v<*B&FJxUI{^G0jgJm9>E+Bjd$$yw&QlA$q=wl24h$12l1q zfhAw;Y|MB%h;2W?dg8thvzeP+YI^ncrBdbORVxiAUb)x|F(78jq7}iVw1e%TpIyz_%&8QdyL=C@qUb`b_BCYg_?|N5 zFW`UfrxUxFblLOG^ap(3CVlq7xwf5O*)3luZvv5{htM_8DprSukyNTyjqUYVTw}%(GdPfxkWNExScbWk(Cedb%$mKw|S=bIivu ztJn|X2MNhrv0p0$zSWb+(Z4b(NnUQ?=O+acGm$1MTv2*_q<@S1R&KfDvncw#uxB#p3&%k(`Ktfvas)AFN#f87D(zkr&EA0By1YPG_Bz#%z0T^)OhpR4Fj}>cg zOed{3zWlUurC?I8jS!|z*v|8dLjxhf9ucGx%a2pjf`E)*S;7pU6h&X&ZoZChYRQ8J zuO&^;j<8K1Eg=%D(7%V%_6!#E4#7y!b1rXG|7Aw@xU$?F4tFNl53(ZT(0Qg#D^MN& zr+&`oow7Pe4;iDag?1LB5g58tU7_6=f=j~RE_c(upg13Y4-M8(-o*@_Z5FrXKoo>PcD{ZJoP`eLHY z2p8BxA$~-xwKZO0rU7+i+~{szsT;*3 zJF)u}45&cq?lJ0iP0>O<_ zmRq~OC>(EeWgrCVVzet-TU#m3nRG!n`YLhoa{2bUT{TVZuUYmFqhgTV(e|X=pY6l@ zA}5*eoNj?bTkp&I(}wVscR;1N&A04R+rb*X*Sdx8*97c30L;D88t+B^X3nvw2sEUi z>HKO}xZ&y&q6OB(V(Zyf-63ZnKCC>$sP0=s2W^TxPVu#?WmR8iFERy*yQZZ^_&mW&;9&^Z0Gew_{MSsP6{Ss0C>I7kzdbc^rD;N!P4JV1> zgMrq&Dr`GkHCv!h-Ozb$g+rnZI*$`I&=~W1lvRu)=?^D>aVXXFLU<#zN;i3NI3y&= z+x>=TrHO$-hKMx@w_1F(x!RAXzP`1gA@03_x-w!BB4S|tSNc(Ko1vEV4z(>>!D`%` z+X0eDk@y((^w=wraC)x0EjG1bzn%J#=D{O7yT#-Il-#)->VbR7ixe8kRPQbgq$AIq zT#f5%h&KFJA`WfV(xPe7jamM|&Dn*SU$+H0>65zFLPlF?KwpTbrPHTV+0Vm46`Tsi z-;9k%eJ~(rCEqf%GEC-~;?WSrj-hhAXQ`D1B%Q~v_av%*Mn+ zB({Y?pBPq~;tE~e;L+$npm2Xrz-WXAH*CqLo2RAzBA3^^Y;&#=cWQSR`<3q5WZFjB z&vu{zs#$;lh3w4XceEMx!Vr_LEa9}XEJm#>16H6n=H`h|CZ=} z+^3FQXV?|^ZH;XPid6e~P~~bQ|4QZwD189>t{jU#X>0r-iwkqB&labeF#fQ+YZXfQ zX^7b=M-1)2AVI~Q{iMoR-T?w6R@!(U{0U8W8&qTLqjivkwz(?_sJBEklJi|iZ{a$~ z{wi^onTR>g8vYKPZhFGq>Pzk4b(cyRxei9iG*7tj3#|t3x@0=PA)EWZsAXB zeK$=&%Cbp+FeF_o*+5j~0F~dv_`mO>2P&4?%Qzs9L)pS0Ky_Aiv0+oVBM8aNqp?%N zqv0iWH#in(nWP1MIhNu%WYb0qD=f7tKnZ31zkW{*-RC#NK*zlyOnZoUI!P=%`VGL9 zhRniD`_XHi-x%E1rChh;xGih!BkY7m^5RoB^q4eGZsy1RW(n_Qt=ZY~r@Xo6|Hfblg~Pz815(?~w{e$;=C>n2nu(Olh<*wSinHOA!IGJg)IxPqRpF%bSX zxqa0$+FNMKJU&)aB@sTCSZ>X~4W>U|jc$PqADAD=jQhQ8>BZr@JGEzujpot780}?; z^>w3`r&agtx3QnJa&G~KrTqpnM?(8KFhe|^a4H0t)C-94hedV)4oGn6=c=kna<8Lc zdF`<&5|0FP^|(hA!E~Mgw*}TqG}QWw$RED3AgV0NJDV7Xm4-d+gog)U(_GE z!1ahbx@R|NSd=|pC2biH+E%CDPJ8vchIwgN=qNK~-PHZlrfmXz} zz|yRb+I;;N3P_B$6n@V%bVePEZMT5K%H!!*zN(4(=Eg+oa3#VJT_BR63?laN6)?S+&V04F{y8r zE8+xT?H6~C?R|>isw-xnRM}4`t?ZvySyqVIGLMipBv(9ZM`$E}SHLH3+_?R0ONN~> zQ^uROKi%c2q+l_xvA9f*>*Q|JbfoxuHDNDzG!<${;QV_Pi`K+o1z#xH zS7oQ#*;}eZ3-dnO#!>yfUa*n@f3Bq8`57Uyu}%ZjZ&VTp10i1onGaL7AV4=(Q9H^M zKHWrfwJohI@t8NtFM5OuF*GoR%V1A?#pIS@D8iZ?H6%|MF6`l{W{}MD2X=71KvA}D zs538@r%&pFzm|uPG|mua)LU7Y)0e_0W`{;PvSwSTt5fI~i@V)80jyMe*Nj^dXi}ch zK*VVpEpEaCa=lvUra`HebHqE7aQ2f&qoG%*_}o8DLv#lfn|So_)e1) zbXGy;QB!PBDBp|@S7y&p^f4Jonn*EHK?K$w zal!Lp3MNjf1}mgAg1Ln`vY_QbC(Y~2@imk(Cyhr#D2#VdAfjMj%II}vrBIrXxu&0= zO9I*4++3!VheeXHN#W3LJ9&o?=xZ ztQ69amSC&0c)h4RmPeyDXIWA9_(?7Q3aCJm`@Pvr!oA(OJ)JggeRcXKza7FR>H0`) z@3^XGW=LjkNgqm|sd}LB!5ZeNer@6YCHF5BcSWsiH{fgq6o*tZ zh<2&8N%@Q>1+vA4b73jIpVswLz(V6y;Z*=&knXqfJTp~+_#1(pl@5wOr$(|*^QeQB z_{-p+uAs?bZcV&(%R|ykX;--S<1jSEX9jR%dc!&i+98T^jo4j!EEgftn zh`6zh{CHEhVq02EzoC;`zVxbaI0p(2u9kqiD~3nf6TUnxNY~czC z#~9@t(mT8vL|kh$YCAD${1P(whI z0IFab8WD|<(6=56m)MWxBZ{)fw=wV5u|K=ockcEnhABsCj! z#xirNCMZmis6W774Um}xbWGG0CrR)WiFcE~HvzPAu137N8psd?y&I(v8+lcUUabTYq(M0Zb*^)UH)BCbb=4+Q`wr9?l41 z&)2S=R`F^i&rQ}kpxo66_)7R-!=p;oqZ9mQ`cSh+x6s?WnquSkN~^ovsU?#JDGe)% zp-6&75dC*8uRu+cWS%)>oy}BDIzCx&TUJpc8R!XZ*Sch;l!ut}iRre+wilqDgWx3o zjK3YlUlVt|z&0ExY;tk!VQpy_#{b^$4+^7giNg|$1!8qTjWw!?gPc;ffn9}ZCohH( z7aW%Hqn&{WH-cizV#O_-*n!VikoRRpd|%5sHV5GGPF$?w56dC#_#1x9cEkFda16Z3 zdT}ox$cf#2K-VEV{3YxKpTu@I zuUWrdQu<<9JV^JsXr<3kMVz@xQZ!o2C+OODi>v@px4dYaf^d?2G&}?MYFOjc2H+K|G90zGBIqy|f+YYCyJ^{x0|wXK zLbkWTxTot51qVZGJPYPW&M{$Hl_4hj^0~ieh#ftA$b1&nL~C7lBhqs6Pu7wd7rfij z*;UJ}gC=5HHB~>=Eqt&cvWDc$Uh3>CxQH;*peR^Yx?6Yv%5ly4=;ahQ?okowd|F)3 zqRwin3IHXd)2zQ$$XG$ARb>xGj~Z~TZ2Bc zjJRcKT&VI8Cv34j*Zp61(~2=yqq6iIT570ycqInSg4OIs2Zeh~9=}aEa*?C;e|u*N zGiPP>AaVGdh+6;5ZFhEsr4#aO0#x(4>z&4;pt>f=$gSdzbih~U9S_-d=C;}wZYKzy zzp2qC0J9kwa3g_@kO!yFG zHZcjWrei0o09aLLqbfYP(~rs4XCAWHaQv&_&U3FB^D5QjlQYI9gfBgrkNp0;lI4(L zE$e>NV{qX%sFVHCNa?!bbH|k3)0H0aEN^#aW6s#`Y*Ox*3)K>0!D|6oe#3Zt0?S#> zS&#qcHV;;YBrVt4Ef-4Tr1rwo0Sm_tsX1&ck*w40e@!2j z(6yXh5MRmPsOlUF-$f0NJTCV)zCHQDu<-plIO2l8At1DdI(3sT9dzci4sbw#3VYvNY= zv7er2-g^4M*#X%17~iSry*$u2af?*-rS;(0hbOEeYW!svr0ePDP7jJjiRt4GA-XS} z1P?AZ@%rU>XOGM$n%2Zz;uCe9u8zWetglA|?AR?2X7?Wc{>7e`Zp$o-lLTmgR|Q3kb2PK zt4m?fZy+eZVBnA1TCk@D>xbvQ_0Zd^)0mX{9i-Dr-l!|lO6E_hDk+_rU`KsLE+bRSQPgkC42;jr(_JLPWplk1LB|oG$iI7*iUmQ5iCd6@ zMJ=mVy~+F7Tj1X3&c?&5joOxAs$#es`xo@BEkM=_}2eWJ{Js25DxK^2OCN)BQhlorRA*A=p@7JJL*;gt2WSGrv7h=X?wQ1@ba=uvW)j~_1Wq1 z*yt$zo7&ID*qr{&06LldJ6MtKHZgF+nPE4NcbN+G`xFagl}?(XFMs$Ss6m5e0S*IC z0|U(3#*)ND8X9RV#9=sZ{zLD9;V%lH9J3`RYfgX#8MwfH<^ESOUgmG&99{#6-kXq)IiSCM*3i&k zCiRt7!kGa$K?CmZ>sE|R`Z{Y~8-3CY%4p>HesCP&)LYXy0jznQAh_GL4`)1W2GdwD-E21;9Zdn=3IJtcXF=A{(!F zyTKg5Rq^Xftc7425Q5z8BcHTS9~jP(hq!8@m(Oti!kfWC^n;ss`t-k~Cjc?IU0;1l7WZ~9Sy+HxhD!PYVh7x`{q80|JP&}Q4CTSFTgjC4*F9R& zf%ZQ|paXW;t|V>3o^0WNqgi<&`@M25Q@k~K={TkPxxhHAR8sDjOxfnkAScuxzt-jA zI~Gmg=hGgWu^XnB-`)tcXl-mv2a?Q*-Q?omLr;cO*RNUL?i|D0mh& zqlHW(gX>L6J+xiBraguL(>>jT_h;G(UuSCVFX+nsRw)$nSX-A`=}>3Ni;lnQ5>>Pm z3RKMjjD2&GsPB@9#i%6YeecF-=I?;lBLH-|e98_hk;Zw| zmzG0@AP4#b+4%P1&9&JTM`&sth=d9d30q_qWehTo2OBeI?clFEZ!EEnm56J7Umu3Z8YmIk%IlxSZ*=mL_Y)wQ))dbguS4(S`jK%f>e<9FfnEY>re@KOY^ zfL9?r?*DgnEFKE<4%T3jtK3k5Z$}Uxv;hM<#66J^)0LdMi7ZCCB<^rOaBF7|g9~+W z*rI@3Q-o|84zz-t3$*G?hO?6dLo5E7x>K`(c8}SQF+jifMW{<|UegNnwm@c{?Q1x3 zGFH74susxt(2g&cULFx*0??Z&QF=u!9hd;g&*X|`@PB~G)1tPWpvHX&OFyYYGWjvF#z3fSIx{{VyMmy{y`3aT?p*i1V3lv+yRx+N!8-dQFK;#Uxxj-H8q0(z;CU4;XYwpYcp?agoXEBsDOZFucBa$p3 zdx#XFBKu?+Z@Vl_lVxV=U6zU_vTu=n&5~s>^L|srXt9f7Dv>ZWSz;`o$NTereg1~; z{qf%Cp67Yae$P42xt#H!L&PW0A>uCRXE8ToeltJhQ7`7*XSRTGD0O4H_FA6?BCW(v zV)Zm-odUg8SzZf_iyDY$jQcb`i^GGeq}*3rN}QmJ#14N(wa}I$-4-;8C~j|4n&PHN z+i*0#SC!&*txomfRIwg1CG2xVrz-#*RpsjEh4S$oA0T_?1GMqg6~t@fkgMDfe7tKC zBRoAFlrvqYY9NRu3p#XxV=o}etbkutz6Os#;W~&D9^IMJ-2~ZO@KJDCZt-P#Q^>k1 zsN64l*mjc14e`$J(~`M7jg&Lr4?+Jd1KaoV9>Obrn$^bjzD8`UtpUVOtfMCPDsJh5 z@`QsUs#On>YXj7SrVSjb?f9z7)H5&rwmIn-U^{*h`|S=&jknhyxU5{$Y}T1)fE*}r zbZ$~Cz=6f@9XR7NwG2)J2y%;kRryW3@+DgF8|I` zg@v2zKSuzq9n$sYtA5-2Bq5TnQT0H#krq(lFLalmo@X{IyjC)=_wIE}6)>0l&p`(B zexS=tEV2LHbC6s(<3C(B?xUGPhQ{g42j2Ao?KbwCG4r2d(?>dbW@L|KnhE{$0-^j| zvOw;sa!oBf{<+JEh^VIOg4h1#u5qPR-Nye49sApa`>JJkhzr-q`G4-Er3If5I%RIc zA{BB!?>`b;4O2&O{41oM%j)^vWG&mJt4N;N<0XFxs8%6m-7(bbPnp5w&$IMd6KPYa zE8;>}z10htsfB;|f44}}L9@vePDgbQkBV6+Q2HFsol zHBNtpyv~RF@sSgP&m555ke*v$bRJi`axc}VRd7pcMsdNRXbMAGel2kthYhGX`&T_q zHSz1ywnu*nf?)a15N^0-qjG9DzMzV*_?ctUlPTbw`e0;OX(7H|rCFt)dF7pNf*_5h zJ&55mSUVCh8u68*@LaX*?QX22$}z?l^1bn^zVNYfK5YyxX*@hrewH;=4DYLt<_J{= zdZIq7-|q=1g81DlpFHe#VKU|gGEr|)wa}WnWK4$>h+I>6JXudHJca5 z|GYt0VWyTG(tBEZSqvUtDWj@a9Igym%(#*_Ds)qzt}N@q2_A>E@3E98l$pXf+>8M@ z*zh>%HX}UjY0@V^H5*yR7b8yUkFQ-a=cXXnu4bJN;bc1(uKzLspbhJkhO^0|;cRK^ z5hK9QJ4g`Gd>Y?Gn%lX$VEBVP(em9ku^lh_l}Wilkdx^U3>&Cwwl0455ZNDAS+C;z zG@aYXWRpA}unA7DG~A6G7S0*<-?a|YYK4reBLj4$WLVP+d|m#|LysxF9;IIMW|LJa z{ieNMdLmH0QP=k$51bf2qn%KikEWZG&>LE$$R`E#S*Z+ZQEVFtv;DdM#U0-uw4(q9 z+s}#mP?IYXlv1zaaVc+dzq2hs%ppa=+Vg|KrgwHwA-b`#?*rZ-vG9*M?LnitR_O9f zZ-b+p3S#5drcJU>lCxF$eIaSGyv1h znOrH%@ML=Ajt6R{5UHt$eOzG@uu1f-F}v}VL97+`A6hVgd-PXmB71e4TOot%XYS@B#se1iXckzrd?8vK5 zr&;TB7H7{n$i#r3sxPalfzqMQsM$%{?j!;G_88(>|g>nyg?$e4`jibQm*R|5+y*Aml%7QZOomUXr9qudY^yKrEiNBAB3fI*k z6W`~{$_N4dHDz|en%LGyiwg_xuj7U}=Rd+H3u?Q5g=vtcY581z0mbzyd=O!{IjyNT zqZ-i($;q<+JZx>;lcJ~@ti4+C^)bu?W^*#}D={lz;U1#fV8`)D)VV6Vhqnqj9?)oZooBN9oq?GWge#?~Z=)9+YRB}%bi7bU-)yU@H6RmL#@sAL zGA0Pdv-UgD7^72awRR8p?vGMC*(HUAHjk@~(AKYlW;gvxvngVwu+ufIl42y86bn_4 zTzm1c=`{D+X01@|Rf*lx%GCRmUqI&VFtToTJT@f6xDk?Rf_j<7uaWxjrzo9W5?-|# z^BvNjv2(Qs;^QRExpPEE#9(Lgs~__%wZq;Dr#&5KWNarC zL%H`lC8rR{s3kqEIn$8-^a^@eQV6RN`mrvbDn4h8Upy!H=TO}x+i6dYsQ$Xx-++)u zw_M4MU7Pq!rDFGdGNY8>;sx{U7on}=18WF-0_E;WONE=h>33b)BmQlmt=7Kxc73dO?+Ih zSW~-rZMdK-vG7g~+KmDX*1EYJ6@b9f-bCbPT62_eT*zZE+VbiPt??hc(CLEB`umb~ z&7^%Kvev=JiD5pyZq&+fo<+o38h*f;MugUmO(sUa8_N4MvDlKW-@KpiUv;xAV?v0z z&#>znPlK^qNF|yMjj+}8J#aeo5EwQI~zexQZXky_FbHgN;IFF~= z@|`Ap`=J(s2THOyZXZ{A6u++4a5dg7ExMF6#{42teD1f5Zlv8=hU3_9UYh;3IqeN( z?QTZ^!Sw#u(WgBk`SwC@KHO6;w(oincxlRjdEHd`hbVnJmm`|{jCUvhu_}ocj(2F0&_Oe(Utsvl^2vKKkaP!aJqXd+TQ!b_qo=d82v8 zCfi)oLvcJ0OOI);X!l;NAssRI0GK(&h^5-#{&z(foO1D6Q^MmF9!;6?;V1XLtqWn_ z_qYS8q0QHx`i4TM^ft#_=!kW{&|s6PnFt`2m6#12_lOjKQ91uDa;es^Xx2pU>Q$@x z;#6@mv;vu^_+v(Q*^X|H^0yQ66)}HN*|LbyS$$1uZ)Hq3>Iy!2VU5p5p--%q7qnuvz@i@=(#c4q(f>HQW(9?P%r4$lPDmIjm>6Jj$wBw|y` z1Y2En6mPBfyyc=#$~y@dX)wbLFTM9k8e#~M9DWOARtpcdK$i$DED39kmdg+FPT}d-}d# zmkN=2(yrIl`O&W>wo3Pd?#b0gZ1S=BG^Hg$lLqpk`H&6XJ`#mm6TW%W1bc9r^h+nV zWWN2~0>=66z_Y6)%4qhVaE}unL#%Hgg)A7!dy8-XDCL%kQ+0_W#EZ_T>>c{IustnW zp{?5RLDH^y%TXP*;r!>tp^@oJf#=Gh{XX_Uk(+O z$LzC*TBBxNHCRoQ_Rg}T-CPHZl+-2?YdZo}D$ZV1MNqNKbFQ0M!64@c6s_vqGUrIto)F{70QWU z7u;(xuu!qB6&$}B`J5_p0CT(!hnkketaM9f9Qw?rV}sssVm>gjn_gmcP}&bG_lJcr zm)$>a&EWPAQEilHju_cbcRQw!Gp|I7I1a&z5!65nCQq~;hxs(OdigD4J4Le^`_=l}&LYcD zQAG?+Ku1=M31PO*mFNi>?64L@DBLI`gPr0aYT(~HyAt0840d|1@KCy4+0A&xwgQ`& zA1%*qj5!9p8(U)#?YCX6{I1gw3708|?&UKsyi}gPpF&y?LEBS3S0d~u%-Wx!arb3- z9}l~kaZDNSCG7u5ea0wDcTjIwpsZ1Pw-|djQ0~5E(2N3quW26wv4Of2Nx*))C53(G ztT1OmVc|{ft=dW)ocpOIPp?#%IrX7xpE5MHAldV5$NZOm2rHY=Q?7fPOS-T zMg6ssI%uT1ljTl)r6Zmpo}t}Olfd=+TwM^Npw$}$2_M)lbb?#WN>!gZLiKKadp60q zM7gL`9Ji=y_ELr#C_*~&>YnaPA zUb{FoX}HVd+}!1_(tDK&dY{*bd*BX53qMIc5IeV4UutQI6@30lnaMu@#bcP?2i zq_sWbUr^M@W|urF+B!~KIZRzVt_9h>#j`p<`&45I4}}x*;gkDj#~+cC_VMk^ku`QqsdeoOO;?3&a26 zx8|JW9T(KJD`wvZptl_QLl8q2{L?_`P0pU^JixDl8g)Kw1;T5iB$a2seT()co;xu! zh$}}z6f3dnNWkWnuosZ$phkRspJ6wyog!;lMP8*PM2^|r?*ZA+efi>-0scOsROoFU zYW4!dPPsa_l)WH%YImNZL?DK6#2TrE=y$U16forORVgGJ(&UTR(v?oc7+;*voCxlo zetfr>G$RDOQLvpAF5_9v=HJx7Y)bo?i%RCnmumW(D2rFEdP-8)-0dH}(={IU{lH0Y zDX4n!g1z9wRE*ZE`7a)()7TjurQ#KcM-3?n+A5->QXFx^>LO;i-}X!coFEUA7inSb z60oKwJQPR^i-$5oASz}#%|^BS(viONnjyFS<1jNJImX^=o4e(NXnhl?4t#}#Q^%l%58unYe^*TT{OQ<(Bl z@GUGHWFB!L_9Ip9!`Gyq)>+lg>Q2PvOG+{7#f?=APqUw%Xgz_b&36No>);i09ttYg zS{f?hW7niVhEyu{?d^H+c}|hSB4A#o2G^2AiR?Ov_^k8itPD3w7c$|6>?r)yuAgV} zyUXxB9e~~%In3{%RqjxAY~qZgR;MEyHIOZXUlf}_ngMJdF%01bP4UR=b1`qJ?gAOw z3L~Zw3aynP^<-#CN$PtmrAWWug}rla|NC{Ori~2v3+To=|E-ht0n-o0Z<=|f*|!~S zdC53;mM+Y+j*+oZGVgrN@29Vk&3l>ean1^(OW#KSZ*e??u@^&sL?CKZes5DaCw8Zv zAvgN#(5hX5rILBgaD3yvOlznx`vp~mDkoLK*&p&X$873;$R%c&+qajg(#2xhVl~G@ z27!Ov)|lmrlsg@vsivhCl18fK2E(6_EKB;SfxNHar+C9R-||!{^@nkWD%M$8pV%=_ z(<@#p%3_*3|3keEWx#JAN_7426YF7?1H|)`+`d<0eVub>Kx*os6+J_|aD|QcqS9`j z^q!nom;$gUBeP1`0>}L=N+FESL243I9~pKMyH%4=3-9FJfnpQCy!<&Pytp&-ZMUg> zpV37*s#H88i_;^fX*Bpz>B-mo zv2t<}KIqA3D=YC~|H-1g9o#m5i}KCHDFy##q&_dc_pKy{nfqzOA=((*+(mO9W9y3V zHNAG}A&8_yR516M23mXvdDaR;R#Lk%3S`DT9YSoT<_4fA5pxNaq#SslRBF?`_Rq(( z0O%tBi2yWewzdctDpxO!zJ2Uf<=-GYvPaR!N^3#1!Z$-wYWtpq=a|*Rl|5zM#qubwTThwLefmvK8O! zbO}V3fZn`|nfP1k1-ux6C_-Wd5wP|AVCL^9k1;PVIiWWODf;|=X%D*HIGZJPbdi_d z8uJ+6zt-=m@vh&S|2Aw6;geu_KVu?-{_+2roV>t$;`oQ?~~4XSV-d;STRA ziGY=}2T`pD&g@&;Y9!b4=}@QwqaDHmRbPUy)R+wZ-KUF~h-T-L#V`?FU%pe&6{r)Z9x=7p6hS?EzKd2Vu z)E7oTLPyi$NO#S-c+33E2?B-xznB|fstIm?h;uiM%;gGnRaNHl0b9g)qEiTO2t>Xk zq))bIQ^fHby3%(Pkc;LNtcjD{pa%}NB$A=sLn|`Oe#r-x{+MKBx#YCFnK_{c|83^2 zkvWkxOC;L`!)iFGDTV+wHuG>JUMlG$^))x3>_9jt2%3!|x5j7wvdvpd5XTFLZTb=> z2*g?rLw+#7pf@^7fOPYZCx(1V?aHy2aPFEqRO|wSrt9j$d?3Nn=tGh|SwxY6*l3F28c1 z1yc;K!av5A!tAtzgG{E6h(_j?#XqKfq6|~oofw_!U ze6X%?{O8n$6QNC+Qo^Z8dEdpWhi|L-2De|Ops#zFP;O{tIXj5pEU$y1rR*rn78Z?Saj+usWls8UaQBSDm>a&^sV!o` zO>Qf!ks_V>lmLHztdMV8@JyODI#|(LhR#_ieT;? zM|$(m8gn!MX3jftBwQ)u{P8pg$it^3qr*F=jU{5tjxT&M0xF1G3A2wu8PW3>4+j4V zryDS0Zvo&2jfX?yPr0?Nl@p1?e!@KdzMUyjT_qT{S4zT>8RuUbeuf`|WK6gaPQyu~ zU}4-;Wazn2pgd|wqUGu0kgY4|4RyKzy(4l=PA+Ei$Z8xT)(ZgE_;D1!Qwo}&80x93 zhaC;&SUpMC(n>!rm%VbBE=TWT#5&U6Scd!1*$>dgSm{jCdEJgV2|PtEhiaW z$DA3|_lJ0=P?w%lN-I3{#=z6Ssr6Zo{YXaVYli2`)?r9*M&`sQC7aNZx4AeHIeJ39 zJC<{bXX@(i{1fN7^jG%qW7z{#ci3adWC%SuaEVnlLl()@`ZN<@Ds{i2ILM!}-i?f+p$HO5hh7kc*&UbPeb#{y*FEn6dx> literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..f204150151dcfd91de3830cfbf28dd17cae8a82c GIT binary patch literal 21193 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 H;H!TF5XEZR literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..c58952f83d5f152b3de07c40f4fc51eef7d9f990 GIT binary patch literal 22756 zcmdRW_ghol67EhyQL2dara>w4Q8e_6yY1bQn5qVq||kbB+<#H8oF{lRn0>@6=C;e^0|WpYJ<)_ioZpX{-om?holx zDS~G9CZq(m>09G#9|sQ~J35uw*jcrvwHDs$w)3MoEJjFUZLWHC&{vNWZ6Qx0{{Q@q zyMi@6z);kW+`v^Kqat|jlH{nimwrlmUth$wb8DH7^N!2lQgzKM4>$Kj+6H@2Tv~hP z$Ig6JcP;3YgdM$_P)ewl%i3|K_=@`5TjcfS5Qf}-x4Nef@ZYaF&V5fDdJ&GMvl8a< zzbHCl0ag~?BTROE_Q+lu)4$rK?dCq+{yAe?u7F(5+QQ-Y^_mOX;z%|u32voU z<&_?A*2jtmGCP5Z5?VYq_QbOG80ir6BgH(pmh$rn0&i=9F<{Fax~7A+xZfakJtJ+q zX8O*H>spZTZTpa`W3rStdr$P#x!^6^=cFt(j=p!Um}Yx%j@)p3QL%E_T3%ol5^^wY z?UH-6JC+wuR)R%}oqIW5^MdF+!JvG#)Ih!$?Y@RUb75SeI_OVr{?H?|4@vRkeX+F+ z5fpB?_nI;KUM5UfZX%(ZWzF8K2^Sc ztLz1t#qqaw7?66>!r+78q1_2C%0oN)eZ2eQ-ClW8$F|_PkF;z*pt6`?Hf%hGb2mRZ zD+X-CE?oQzxIbnB!q&}m`Hk#-AYytS#E2no)8wg+dvGIJdj} zoPqzqPfY&O+g=jFp3fH6JTK_e04vfbYu~AySMG|UoS@vstzHfawaBxmt`3vN!e3fX zC?MiskKri09H58;w)AFvC%c*3{<%xIHC9|c5yCKFbqB7S+KkćxHxlKJ@^=#Nv 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 W + + + + + + + + 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 0000000000000000000000000000000000000000..99ede52bcb21df8d366e40f27d825d2a4281e6a1 GIT binary patch literal 19187 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..1364447079153d90323f220960237ab3f7f08ab4 GIT binary patch literal 22470 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 literal 0 HcmV?d00001 From d2f9fbe7febfaeb6002fb7dd7805e0949d760cde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Devernay?= Date: Sun, 4 Oct 2015 11:27:41 +0200 Subject: [PATCH 108/178] update openfx --- Engine/Project.cpp | 5 ++--- HostSupport/HostSupport.pro | 5 +++++ libs/OpenFX | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Engine/Project.cpp b/Engine/Project.cpp index 9f07eb2261..2064328f97 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" @@ -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/HostSupport/HostSupport.pro b/HostSupport/HostSupport.pro index 803642c7b3..7470e352ec 100644 --- a/HostSupport/HostSupport.pro +++ b/HostSupport/HostSupport.pro @@ -71,11 +71,16 @@ 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 diff --git a/libs/OpenFX b/libs/OpenFX index 002cf0b8a0..afde4332e9 160000 --- a/libs/OpenFX +++ b/libs/OpenFX @@ -1 +1 @@ -Subproject commit 002cf0b8a0c6022e3abc13e00fa5f5197b8e1099 +Subproject commit afde4332e95e9763a8c58597cd39c5b740acc0e0 From a73093daf2b2a1efbe14de02e8b2d5711ff3760a Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Sun, 4 Oct 2015 11:55:46 +0200 Subject: [PATCH 109/178] Fix slider focus handling --- Gui/ScaleSliderQWidget.cpp | 82 ++++++++++++++++++++++++++------------ Gui/ScaleSliderQWidget.h | 7 +++- 2 files changed, 63 insertions(+), 26 deletions(-) diff --git a/Gui/ScaleSliderQWidget.cpp b/Gui/ScaleSliderQWidget.cpp index fc4e4e1a9f..ff30a465fa 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,64 @@ 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) +{ + 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 +298,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); } 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; }; From a6ce2db628127935ac9ebdc14751b941202f4026 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Sun, 4 Oct 2015 12:13:21 +0200 Subject: [PATCH 110/178] Remove unused space on the right of the vertical color bar --- Gui/DockablePanel.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Gui/DockablePanel.cpp b/Gui/DockablePanel.cpp index e62e53ca79..412c427b08 100644 --- a/Gui/DockablePanel.cpp +++ b/Gui/DockablePanel.cpp @@ -417,6 +417,7 @@ 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->setSpacing(0); if (iseffect) { _imp->_verticalColorBar = new VerticalColorBar(_imp->_horizContainer); _imp->_verticalColorBar->setColor(currentColor); From d1477be8c36e922c3d3e24cd07c35584df39b3b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Devernay?= Date: Sun, 4 Oct 2015 15:46:33 +0200 Subject: [PATCH 111/178] VerticalColorBar: save more space --- Gui/DockablePanel.cpp | 4 +--- Gui/VerticalColorBar.cpp | 8 ++++---- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/Gui/DockablePanel.cpp b/Gui/DockablePanel.cpp index 412c427b08..aadcc5bfa7 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,7 @@ 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(0); if (iseffect) { _imp->_verticalColorBar = new VerticalColorBar(_imp->_horizContainer); 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); } From 1a7af6168dda09604ece5452421a6e5b3d332e24 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Sun, 4 Oct 2015 12:35:01 +0200 Subject: [PATCH 112/178] Fix include --- Engine/Project.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Engine/Project.cpp b/Engine/Project.cpp index 2064328f97..f9a906d81f 100644 --- a/Engine/Project.cpp +++ b/Engine/Project.cpp @@ -56,7 +56,7 @@ #ifdef __NATRON_WIN32__ #include // for wideStringToString #endif -#include // OFX::XML::escape +#include // OFX::XML::escape #include "Engine/AppInstance.h" #include "Engine/AppManager.h" From 8815ccb23eb3f4e3b7289d70a94274c7e50fb291 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Sun, 4 Oct 2015 15:48:27 +0200 Subject: [PATCH 113/178] renderRoi: getRestToRender + markForRendering is now made in a single operation --- Engine/EffectInstance.cpp | 12 +++++++++--- Engine/EffectInstance.h | 1 + Engine/EffectInstanceRenderRoI.cpp | 9 +++++++++ Engine/Image.cpp | 26 ++++++++++++++++++++++++++ Engine/Image.h | 22 +++++++++++++++++++++- 5 files changed, 66 insertions(+), 4 deletions(-) diff --git a/Engine/EffectInstance.cpp b/Engine/EffectInstance.cpp index 9b2a020a8a..c34bb438c4 100644 --- a/Engine/EffectInstance.cpp +++ b/Engine/EffectInstance.cpp @@ -1748,6 +1748,8 @@ 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 (renderFullScaleThenDownscale) { //The renderMappedImage is cached , read bitmap from it @@ -1758,7 +1760,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); } @@ -1780,7 +1783,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); } @@ -1930,6 +1934,7 @@ EffectInstance::tiledRenderingFunctor(const QThread* callingThread, renderMappedRectToRender, downscaledRectToRender, byPassCache, + bitmapMarkedForRendering, outputClipPrefDepth, outputClipPrefsComps, processChannels, @@ -1961,6 +1966,7 @@ EffectInstance::renderHandler(RenderArgs & args, const RectI & renderMappedRectToRender, const RectI & downscaledRectToRender, bool byPassCache, + bool bitmapMarkedForRendering, Natron::ImageBitDepthEnum outputClipPrefDepth, const std::list & outputClipPrefsComps, bool* processChannels, @@ -2150,7 +2156,7 @@ 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 (renderFullScaleThenDownscale) { it->second.fullscaleImage->markForRendering(downscaledRectToRender); diff --git a/Engine/EffectInstance.h b/Engine/EffectInstance.h index fc9c838aa2..938ebfc4e2 100644 --- a/Engine/EffectInstance.h +++ b/Engine/EffectInstance.h @@ -1813,6 +1813,7 @@ class EffectInstance const RectI & renderMappedRectToRender, const RectI & downscaledRectToRender, bool byPassCache, + bool bitmapMarkedForRendering, Natron::ImageBitDepthEnum outputClipPrefDepth, const std::list & outputClipPrefsComps, bool* processChannels, diff --git a/Engine/EffectInstanceRenderRoI.cpp b/Engine/EffectInstanceRenderRoI.cpp index 8315ff0cf3..4ea9898534 100644 --- a/Engine/EffectInstanceRenderRoI.cpp +++ b/Engine/EffectInstanceRenderRoI.cpp @@ -1463,6 +1463,15 @@ EffectInstance::renderRoI(const RenderRoIArgs & args, } 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() ); } } diff --git a/Engine/Image.cpp b/Engine/Image.cpp index 79fe6d747c..637f9fb9a2 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; + } + QMutexLocker 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, diff --git a/Engine/Image.h b/Engine/Image.h index e56222b8bc..69cff19dd7 100644 --- a/Engine/Image.h +++ b/Engine/Image.h @@ -578,7 +578,23 @@ namespace Natron { QMutexLocker locker(&_entryLock); return _bitmap.minimalNonMarkedBbox(regionOfInterest); } - + +#if NATRON_ENABLE_TRIMAP + RectI getMinimalRectAndMarkForRendering_trimap(const RectI & regionOfInterest,bool* isBeingRenderedElsewhere) + { + if (!_useBitmap) { + return regionOfInterest; + } + RectI ret; + { + QMutexLocker locker(&_entryLock); + ret = _bitmap.minimalNonMarkedBbox_trimap(regionOfInterest,isBeingRenderedElsewhere); + } + markForRendering(ret); + return ret; + } +#endif + void markForRendered(const RectI & roi) { if (!_useBitmap) { @@ -615,6 +631,10 @@ namespace Natron { _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. From 3f07a712c259c92eebdb080bd0d8db4eadef321a Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Sun, 4 Oct 2015 15:53:50 +0200 Subject: [PATCH 114/178] slider: take focus only if possible --- Gui/ScaleSliderQWidget.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Gui/ScaleSliderQWidget.cpp b/Gui/ScaleSliderQWidget.cpp index ff30a465fa..eec0829484 100644 --- a/Gui/ScaleSliderQWidget.cpp +++ b/Gui/ScaleSliderQWidget.cpp @@ -256,7 +256,9 @@ ScaleSliderQWidget::zoomRange() void ScaleSliderQWidget::enterEvent(QEvent* e) { - setFocus(); + if (Gui::isFocusStealingPossible()) { + setFocus(); + } QWidget::enterEvent(e); } From ca8df45b5c5dc70c1efd0eec07c07ba7f1177240 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Sun, 4 Oct 2015 16:34:04 +0200 Subject: [PATCH 115/178] Fix copy/paste of keyframes in dopesheet, fix #937 --- Engine/Knob.h | 3 +++ Engine/KnobImpl.h | 26 +++++++++---------- Gui/DopeSheetEditorUndoRedo.cpp | 46 +++++++++++++++++++++++++-------- Gui/DopeSheetEditorUndoRedo.h | 5 ++++ 4 files changed, 56 insertions(+), 24 deletions(-) diff --git a/Engine/Knob.h b/Engine/Knob.h index c602aa454f..b9ec829556 100644 --- a/Engine/Knob.h +++ b/Engine/Knob.h @@ -629,6 +629,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. @@ -1463,6 +1464,8 @@ class Knob Natron::ValueChangedReasonEnum reason, KeyFrame* newKey); + 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 diff --git a/Engine/KnobImpl.h b/Engine/KnobImpl.h index e49d989a0a..f16afec125 100644 --- a/Engine/KnobImpl.h +++ b/Engine/KnobImpl.h @@ -1738,7 +1738,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(); @@ -1753,21 +1753,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, time, 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(); @@ -1787,12 +1780,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, key.getTime(), 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*/) 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; }; From 855d2a0e7deded2ab7131eb6c1bedcddd76b40db Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Sun, 4 Oct 2015 17:12:03 +0200 Subject: [PATCH 116/178] Windows: build-sdk: patch SeExpr for compat with MingW --- .../include/patches/SeExpr/mingw-compat.patch | 17 +++++++++++++++++ tools/Windows/include/scripts/build-sdk.sh | 5 ++++- 2 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 tools/Windows/include/patches/SeExpr/mingw-compat.patch 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 From 6c8a2f9a64f766e054831fef4c253d5b8cb33608 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Sun, 4 Oct 2015 18:06:12 +0200 Subject: [PATCH 117/178] call closeProject in destructor of AppInstance. More explicit error message when startWritersRendering fails. --- Engine/AppInstance.cpp | 16 +++++++--------- Engine/Project.cpp | 31 ++++++++++++++++--------------- Gui/Gui.cpp | 2 +- 3 files changed, 24 insertions(+), 25 deletions(-) diff --git a/Engine/AppInstance.cpp b/Engine/AppInstance.cpp index cb622762e6..e937ad2117 100644 --- a/Engine/AppInstance.cpp +++ b/Engine/AppInstance.cpp @@ -130,12 +130,13 @@ 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->closeProject(true); _imp->_currentProject->discardAppPointer(); + + appPTR->removeInstance(_imp->_appID); } void @@ -464,12 +465,9 @@ AppInstance::load(const CLArgs& cl) } } - try { - startWritersRendering(cl.areRenderStatsEnabled(),writersWork); - } catch (const std::exception& e) { - getProject()->removeLockFile(); - throw e; - } + + startWritersRendering(cl.areRenderStatsEnabled(),writersWork); + @@ -1122,7 +1120,7 @@ AppInstance::startWritersRendering(bool enableRenderStats,const std::listisOutputNode() ) { diff --git a/Engine/Project.cpp b/Engine/Project.cpp index f9a906d81f..173b3e7810 100644 --- a/Engine/Project.cpp +++ b/Engine/Project.cpp @@ -1457,23 +1457,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(); diff --git a/Gui/Gui.cpp b/Gui/Gui.cpp index 4df32d9e64..352da184b8 100644 --- a/Gui/Gui.cpp +++ b/Gui/Gui.cpp @@ -178,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 { From e8eeea2f72f564f523f9e182a7478a6fb794ce36 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Sun, 4 Oct 2015 18:26:53 +0200 Subject: [PATCH 118/178] Comment shouldCacheOutput() --- Engine/Node.cpp | 70 ++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 58 insertions(+), 12 deletions(-) diff --git a/Engine/Node.cpp b/Engine/Node.cpp index 9fd4342514..bb3ac7ed9a 100644 --- a/Engine/Node.cpp +++ b/Engine/Node.cpp @@ -6663,7 +6663,6 @@ Node::shouldCacheOutput(bool isFrameVaryingOrAnimated) const ///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(); @@ -6674,28 +6673,75 @@ 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 From 11a334c3a58392b0f25df917c1f696cc15ac9c25 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Sun, 4 Oct 2015 18:38:38 +0200 Subject: [PATCH 119/178] Set vertical bar spacing to 2 --- Gui/DockablePanel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gui/DockablePanel.cpp b/Gui/DockablePanel.cpp index aadcc5bfa7..f69aa21057 100644 --- a/Gui/DockablePanel.cpp +++ b/Gui/DockablePanel.cpp @@ -415,7 +415,7 @@ DockablePanel::DockablePanel(Gui* gui , _imp->_horizContainer = new QWidget(this); _imp->_horizLayout = new QHBoxLayout(_imp->_horizContainer); _imp->_horizLayout->setContentsMargins(0, 3, 3, 0); - _imp->_horizLayout->setSpacing(0); + _imp->_horizLayout->setSpacing(2); if (iseffect) { _imp->_verticalColorBar = new VerticalColorBar(_imp->_horizContainer); _imp->_verticalColorBar->setColor(currentColor); From 89525e9d65185632649888a41415c1af4e2a3616 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Sun, 4 Oct 2015 19:21:45 +0200 Subject: [PATCH 120/178] Nodes: Display in the node graph the layer onto which the processing occurs --- Engine/Node.cpp | 32 +++++++++++++++++--------------- Engine/Node.h | 4 ++++ Gui/NodeGui.cpp | 49 +++++++++++++++++++++++++++++++++---------------- Gui/NodeGui.h | 1 + 4 files changed, 55 insertions(+), 31 deletions(-) diff --git a/Engine/Node.cpp b/Engine/Node.cpp index bb3ac7ed9a..aacbdc4cd2 100644 --- a/Engine/Node.cpp +++ b/Engine/Node.cpp @@ -6023,25 +6023,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(inputNb); + 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(), @@ -6072,6 +6067,10 @@ Node::Implementation::onLayerChanged(int inputNb,const ChannelSelector& selector enabled->setValue(true, 0); } } + + if (inputNb == -1) { + _publicInterface->s_outputLayerChanged(); + } } void @@ -7749,6 +7748,9 @@ Node::refreshChannelSelectors(bool setValues) } layerKnob->populateChoices(choices); + if (hasChanged) { + s_outputLayerChanged(); + } if (setValues) { diff --git a/Engine/Node.h b/Engine/Node.h index 8b3916b1e9..705b0f2174 100644 --- a/Engine/Node.h +++ b/Engine/Node.h @@ -1068,6 +1068,8 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON bool refreshDraftFlagInternal(const std::vector >& inputs); void setNameInternal(const std::string& name); + + void s_outputLayerChanged() { Q_EMIT outputLayerChanged(); } public Q_SLOTS: @@ -1110,6 +1112,8 @@ public Q_SLOTS: Q_SIGNALS: + void outputLayerChanged(); + void mustComputeHashOnMainThread(); void settingsPanelClosed(bool); diff --git a/Gui/NodeGui.cpp b/Gui/NodeGui.cpp index 847a2c39f7..6b33f04287 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(); @@ -2672,6 +2661,27 @@ NodeGui::setNameItemHtml(const QString & name, QString textLabel; textLabel.append("
"); bool hasFontData = true; + QString extraLayerStr; + + double time = getDagGui()->getGui()->getApp()->getTimeLine()->currentFrame(); + EffectInstance::ComponentsNeededMap neededComps; + bool processAll; + SequenceTime ptTime; + int ptView; + bool processChannels[4]; + NodePtr ptInput; + getNode()->getLiveInstance()->getComponentsNeededAndProduced_public(time, 0, &neededComps, &processAll, &ptTime, &ptView, processChannels, &ptInput); + EffectInstance::ComponentsNeededMap::iterator foundOutput = neededComps.find(-1); + if (foundOutput != neededComps.end() && !foundOutput->second.empty()) { + const Natron::ImageComponents& comp = foundOutput->second.front(); + if (!comp.isColorPlane()) { + extraLayerStr.append("
"); + extraLayerStr.push_back('('); + extraLayerStr.append(comp.getLayerName().c_str()); + extraLayerStr.push_back(')'); + } + } + if ( !label.isEmpty() ) { QString labelCopy = label; @@ -2695,9 +2705,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); @@ -2709,6 +2719,7 @@ NodeGui::setNameItemHtml(const QString & name, .arg(QApplication::font().family())); textLabel.append(fontTag); textLabel.append(name); + textLabel.append(extraLayerStr); textLabel.append(""); } textLabel.append("
"); @@ -2730,6 +2741,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 d669d260ae..fd8d5e907c 100644 --- a/Gui/NodeGui.h +++ b/Gui/NodeGui.h @@ -429,6 +429,7 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON public Q_SLOTS: + void onOutputLayerChanged(); void onSettingsPanelClosed(bool closed); From 122f9934ac5f0fc5a58057027cf58550db1d994c Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Sun, 4 Oct 2015 23:08:18 +0200 Subject: [PATCH 121/178] Don't show None components --- Gui/NodeGui.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gui/NodeGui.cpp b/Gui/NodeGui.cpp index 6b33f04287..cedacdd5d4 100644 --- a/Gui/NodeGui.cpp +++ b/Gui/NodeGui.cpp @@ -2674,7 +2674,7 @@ NodeGui::setNameItemHtml(const QString & name, EffectInstance::ComponentsNeededMap::iterator foundOutput = neededComps.find(-1); if (foundOutput != neededComps.end() && !foundOutput->second.empty()) { const Natron::ImageComponents& comp = foundOutput->second.front(); - if (!comp.isColorPlane()) { + if (!comp.isColorPlane() && comp.getNumComponents() > 0) { extraLayerStr.append("
"); extraLayerStr.push_back('('); extraLayerStr.append(comp.getLayerName().c_str()); From 5af6a1e17630d2d08a178e49c325b65ee2bf1450 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Mon, 5 Oct 2015 11:22:07 +0200 Subject: [PATCH 122/178] Fix images read/write lock. Also if an image is requested directly from getImage() and it needs to be resized, do not take the original cached image write lock. --- Engine/Cache.h | 38 ++++++ Engine/CacheEntry.h | 30 +++-- Engine/EffectInstance.cpp | 2 + Engine/EffectInstance.h | 80 +++++++------ Engine/EffectInstanceRenderRoI.cpp | 44 ++++++- Engine/FrameEntry.h | 4 +- Engine/Image.cpp | 185 +++++++++++++++++------------ Engine/Image.h | 100 +++++++++++----- Engine/ImageConvert.cpp | 4 +- Engine/ImageCopyChannels.cpp | 2 +- Engine/ImageMaskMix.cpp | 10 +- Engine/Node.cpp | 2 +- Engine/OfxClipInstance.cpp | 7 +- Engine/OutputSchedulerThread.cpp | 2 + Engine/ParallelRenderArgs.cpp | 1 + Engine/RotoPaint.cpp | 1 + Engine/ViewerInstance.cpp | 2 +- 17 files changed, 344 insertions(+), 170 deletions(-) diff --git a/Engine/Cache.h b/Engine/Cache.h index 95b79cd9f2..9da589ee2a 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::const_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::const_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/EffectInstance.cpp b/Engine/EffectInstance.cpp index c34bb438c4..4e4704d7a1 100644 --- a/Engine/EffectInstance.cpp +++ b/Engine/EffectInstance.cpp @@ -794,6 +794,7 @@ EffectInstance::getImage(int inputNb, RectD(), requestedComps, depth, + true, this, inputImagesThreadLocal), &inputImages); @@ -2027,6 +2028,7 @@ EffectInstance::renderHandler(RenderArgs & args, RectD(), comps, outputClipPrefDepth, + false, this); if (!identityInput) { for (std::map::iterator it = planes.planes.begin(); it != planes.planes.end(); ++it) { diff --git a/Engine/EffectInstance.h b/Engine/EffectInstance.h index 938ebfc4e2..ee9cd4695d 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) { } }; @@ -1218,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; /** @@ -1230,6 +1241,7 @@ class EffectInstance , downscaleImage() , renderMappedImage() , tmpImage() + , cacheSwapImage() , originalCachedImage(0) , isAllocatedOnTheFly(false) { diff --git a/Engine/EffectInstanceRenderRoI.cpp b/Engine/EffectInstanceRenderRoI.cpp index 4ea9898534..bfe74b8f1e 100644 --- a/Engine/EffectInstanceRenderRoI.cpp +++ b/Engine/EffectInstanceRenderRoI.cpp @@ -1276,10 +1276,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(renderFullScaleThenDownscale ? 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. @@ -1289,11 +1310,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() ); } } @@ -1483,6 +1503,18 @@ 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 && 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 637f9fb9a2..129af29a6b 100644 --- a/Engine/Image.cpp +++ b/Engine/Image.cpp @@ -598,7 +598,7 @@ Image::printUnrenderedPixels(const RectI& roi) const if (!_useBitmap) { return; } - QMutexLocker k(&_entryLock); + QReadLocker k(&_entryLock); const char* bm = _bitmap.getBitmapAt(roi.x1, roi.y1); int roiw = roi.x2 - roi.x1; @@ -711,7 +711,7 @@ Image::onMemoryAllocated(bool diskRestoration) void Image::setBitmapDirtyZone(const RectI& zone) { - QMutexLocker k(&_entryLock); + QWriteLocker k(&_entryLock); _bitmap.setDirtyZone(zone); } @@ -797,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; @@ -850,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; @@ -955,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) { @@ -965,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; @@ -973,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) { @@ -989,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); @@ -1123,7 +1160,7 @@ Image::fill(const RectI & roi, float a) { - QMutexLocker k(&_entryLock); + QWriteLocker k(&_entryLock); switch ( getBitDepth() ) { case eImageBitDepthByte: @@ -1146,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; @@ -1182,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() ) { @@ -1361,7 +1398,7 @@ Image::getPixelAspectRatio() const unsigned int Image::getRowElements() const { - QMutexLocker k(&_entryLock); + QReadLocker k(&_entryLock); return getComponentsCount() * _bounds.width(); } @@ -1385,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; @@ -1568,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; @@ -1685,7 +1722,7 @@ Image::checkForNaNs(const RectI& roi) return false; } - QMutexLocker k(&_entryLock); + QWriteLocker k(&_entryLock); unsigned int compsCount = getComponentsCount(); @@ -1742,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; @@ -1827,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 69cff19dd7..3594747409 100644 --- a/Engine/Image.h +++ b/Engine/Image.h @@ -223,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; @@ -249,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(); @@ -366,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); + } }; /** @@ -405,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 @@ -446,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 @@ -547,7 +589,7 @@ namespace Natron { if (!_useBitmap) { return; } - QMutexLocker locker(&_entryLock); + QReadLocker locker(&_entryLock); _bitmap.minimalNonMarkedRects_trimap(regionOfInterest, ret, isBeingRenderedElsewhere); } #endif @@ -556,7 +598,7 @@ namespace Natron { if (!_useBitmap) { return ; } - QMutexLocker locker(&_entryLock); + QReadLocker locker(&_entryLock); _bitmap.minimalNonMarkedRects(regionOfInterest,ret); } @@ -566,7 +608,7 @@ namespace Natron { if (!_useBitmap) { return regionOfInterest; } - QMutexLocker locker(&_entryLock); + QReadLocker locker(&_entryLock); return _bitmap.minimalNonMarkedBbox_trimap(regionOfInterest,isBeingRenderedElsewhere); } #endif @@ -575,7 +617,7 @@ namespace Natron { if (!_useBitmap) { return regionOfInterest; } - QMutexLocker locker(&_entryLock); + QReadLocker locker(&_entryLock); return _bitmap.minimalNonMarkedBbox(regionOfInterest); } @@ -587,7 +629,7 @@ namespace Natron { } RectI ret; { - QMutexLocker locker(&_entryLock); + QReadLocker locker(&_entryLock); ret = _bitmap.minimalNonMarkedBbox_trimap(regionOfInterest,isBeingRenderedElsewhere); } markForRendering(ret); @@ -600,7 +642,7 @@ namespace Natron { if (!_useBitmap) { return; } - QMutexLocker locker(&_entryLock); + QWriteLocker locker(&_entryLock); RectI intersection; _bounds.intersect(roi, &intersection); _bitmap.markForRendered(intersection); @@ -613,7 +655,7 @@ namespace Natron { if (!_useBitmap) { return; } - QMutexLocker locker(&_entryLock); + QWriteLocker locker(&_entryLock); RectI intersection; _bounds.intersect(roi, &intersection); _bitmap.markForRendering(intersection); @@ -625,7 +667,7 @@ namespace Natron { if (!_useBitmap) { return; } - QMutexLocker locker(&_entryLock); + QWriteLocker locker(&_entryLock); RectI intersection; _bounds.intersect(roi, &intersection); _bitmap.clear(intersection); 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/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/Node.cpp b/Engine/Node.cpp index aacbdc4cd2..4d8d0f2a75 100644 --- a/Engine/Node.cpp +++ b/Engine/Node.cpp @@ -4628,7 +4628,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; } diff --git a/Engine/OfxClipInstance.cpp b/Engine/OfxClipInstance.cpp index 69b93098d3..ed263bc123 100644 --- a/Engine/OfxClipInstance.cpp +++ b/Engine/OfxClipInstance.cpp @@ -987,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()); @@ -1278,10 +1278,11 @@ OfxImage::OfxImage(boost::shared_ptr internalImage, const RectD & rod = internalImage->getRoD(); // Not the OFX RoD!!! Natron::Image::getRoD() is in *CANONICAL* coordinates if (isSrcImage) { - Natron::Image::ReadAccess access(internalImage.get()); - const unsigned char* ptr = access.pixelAt( pluginsSeenBounds.left(), pluginsSeenBounds.bottom() ); + boost::shared_ptr access(new Natron::Image::ReadAccess(internalImage.get())); + const unsigned char* ptr = access->pixelAt( pluginsSeenBounds.left(), pluginsSeenBounds.bottom() ); assert(ptr); setPointerProperty( kOfxImagePropData, const_cast(ptr)); + _imgAccess = access; } else { boost::shared_ptr access(new Natron::Image::WriteAccess(internalImage.get())); unsigned char* ptr = access->pixelAt( pluginsSeenBounds.left(), pluginsSeenBounds.bottom() ); diff --git a/Engine/OutputSchedulerThread.cpp b/Engine/OutputSchedulerThread.cpp index e252c43901..f1adbca9be 100644 --- a/Engine/OutputSchedulerThread.cpp +++ b/Engine/OutputSchedulerThread.cpp @@ -2159,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"); @@ -2259,6 +2260,7 @@ DefaultScheduler::processFrame(const BufferedFrames& frames) rod, components, imageDepth, + false, _effect, inputImages); try { diff --git a/Engine/ParallelRenderArgs.cpp b/Engine/ParallelRenderArgs.cpp index c3ac857abb..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); diff --git a/Engine/RotoPaint.cpp b/Engine/RotoPaint.cpp index f04814c66f..2175f03ff9 100644 --- a/Engine/RotoPaint.cpp +++ b/Engine/RotoPaint.cpp @@ -346,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/ViewerInstance.cpp b/Engine/ViewerInstance.cpp index 6706fdd4b5..dfd7326312 100644 --- a/Engine/ViewerInstance.cpp +++ b/Engine/ViewerInstance.cpp @@ -1400,7 +1400,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(); From ddb7aad06be4084d3eeb14c1735d0b691c0ab091 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Mon, 5 Oct 2015 11:42:36 +0200 Subject: [PATCH 123/178] fix iterators --- Engine/Cache.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Engine/Cache.h b/Engine/Cache.h index 9da589ee2a..aa964d6315 100644 --- a/Engine/Cache.h +++ b/Engine/Cache.h @@ -669,7 +669,7 @@ class Cache CacheIterator memoryCached = _memoryCache(hash); if (memoryCached != _memoryCache.end()) { std::list & ret = getValueFromIterator(memoryCached); - for (typename std::list::const_iterator it = ret.begin(); it != ret.end(); ++it) { + for (typename std::list::iterator it = ret.begin(); it != ret.end(); ++it) { if ( (*it)->getKey() == key && (*it)->getParams() == entryToBeEvicted->getParams()) { ret.erase(it); break; @@ -683,7 +683,7 @@ class Cache if (diskCached != _diskCache.end()) { ///Remove the old entry std::list & ret = getValueFromIterator(diskCached); - for (typename std::list::const_iterator it = ret.begin(); it != ret.end(); ++it) { + for (typename std::list::iterator it = ret.begin(); it != ret.end(); ++it) { if ( (*it)->getKey() == key && (*it)->getParams() == entryToBeEvicted->getParams()) { ret.erase(it); break; From 9de09cead462683f4290408cd513e9d2bc4f7d8d Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Mon, 5 Oct 2015 11:53:10 +0200 Subject: [PATCH 124/178] Viewer : fix Return key bind for roto --- Gui/ViewerTab10.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Gui/ViewerTab10.cpp b/Gui/ViewerTab10.cpp index 216ab7deba..12e2cced8c 100644 --- a/Gui/ViewerTab10.cpp +++ b/Gui/ViewerTab10.cpp @@ -648,8 +648,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) ) { @@ -710,6 +708,9 @@ ViewerTab::keyPressEvent(QKeyEvent* 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 { accept = false; QWidget::keyPressEvent(e); From b47472a7445d8953318c38bc50ccd51684ed4c0f Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Mon, 5 Oct 2015 12:21:11 +0200 Subject: [PATCH 125/178] Fix unslave of knobs for roto --- Engine/Knob.cpp | 2 +- Engine/Knob.h | 5 +++- Engine/KnobImpl.h | 70 ++--------------------------------------------- Gui/RotoGui.cpp | 1 - 4 files changed, 8 insertions(+), 70 deletions(-) diff --git a/Engine/Knob.cpp b/Engine/Knob.cpp index c589d5c64a..16ab60b9a9 100644 --- a/Engine/Knob.cpp +++ b/Engine/Knob.cpp @@ -2962,7 +2962,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); diff --git a/Engine/Knob.h b/Engine/Knob.h index b9ec829556..bf8ac393bf 100644 --- a/Engine/Knob.h +++ b/Engine/Knob.h @@ -951,7 +951,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; } @@ -1449,7 +1452,7 @@ class Knob bool copyState) OVERRIDE FINAL; - + public: diff --git a/Engine/KnobImpl.h b/Engine/KnobImpl.h index f16afec125..ba2250e6c4 100644 --- a/Engine/KnobImpl.h +++ b/Engine/KnobImpl.h @@ -1415,78 +1415,14 @@ Knob::unSlave(int dimension, if (getHolder() && _signalSlotHandler) { getHolder()->onKnobSlaved( this, master.second.get(),dimension,false ); } + if (helper) { + helper->removeListener(this); + } if (hasChanged) { evaluateValueChange(dimension, getCurrentTime(), 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); - } -} template KnobHelper::ValueChangedReturnCodeEnum diff --git a/Gui/RotoGui.cpp b/Gui/RotoGui.cpp index 8fb6320639..4dfb4e1f3e 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; } From 8efae24f3e47f99da329abd9771d1c0b9766b13d Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Mon, 5 Oct 2015 12:35:47 +0200 Subject: [PATCH 126/178] Fix roto animation curve editing --- Engine/Bezier.cpp | 4 ++++ 1 file changed, 4 insertions(+) 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(); From 78903d0db759f64adaabb49c1e223639dfb3ba1b Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Mon, 5 Oct 2015 14:21:08 +0200 Subject: [PATCH 127/178] Fix a bug where clip preferences would not load properly --- Engine/Node.cpp | 21 +++++++++++++-------- Engine/Node.h | 2 ++ Engine/NodeGroup.cpp | 4 ++++ 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/Engine/Node.cpp b/Engine/Node.cpp index 4d8d0f2a75..4096310482 100644 --- a/Engine/Node.cpp +++ b/Engine/Node.cpp @@ -6930,27 +6930,32 @@ Node::forceRefreshAllInputRelatedData() } void -Node::markInputRelatedDataDirtyRecursiveInternal(std::list& markedNodes,bool recurse) { - std::list::iterator found = std::find(markedNodes.begin(), markedNodes.end(), this); - if (found != markedNodes.end()) { - return; - } +Node::markAllInputRelatedDataDirty() +{ { QMutexLocker k(&_imp->pluginsPropMutex); _imp->mustComputeInputRelatedData = true; } - markedNodes.push_back(this); - 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)->markInputRelatedDataDirtyRecursiveInternal(markedNodes,false); + (*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); diff --git a/Engine/Node.h b/Engine/Node.h index 705b0f2174..40a51deef9 100644 --- a/Engine/Node.h +++ b/Engine/Node.h @@ -1045,6 +1045,8 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON void forceRefreshAllInputRelatedData(); + void markAllInputRelatedDataDirty(); + private: void refreshInputRelatedDataRecursiveInternal(std::list& markedNodes); diff --git a/Engine/NodeGroup.cpp b/Engine/NodeGroup.cpp index c41aa5823c..ee6b476253 100644 --- a/Engine/NodeGroup.cpp +++ b/Engine/NodeGroup.cpp @@ -920,6 +920,10 @@ NodeCollection::forceComputeInputDependentDataOnAllTrees() 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->forceRefreshAllInputRelatedData(); From 0bf602cca408aba8ccca251b732b776bd8987556 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Mon, 5 Oct 2015 14:25:24 +0200 Subject: [PATCH 128/178] Fix dereference --- Engine/RotoPaint.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Engine/RotoPaint.cpp b/Engine/RotoPaint.cpp index 2175f03ff9..4a6799243c 100644 --- a/Engine/RotoPaint.cpp +++ b/Engine/RotoPaint.cpp @@ -222,8 +222,6 @@ RotoPaint::getRegionOfDefinition(U64 hash,double time, const RenderScale & scale FramesNeededMap RotoPaint::getFramesNeeded(double time, int view) { - boost::shared_ptr roto = getNode()->getRotoContext(); - boost::shared_ptr bottomMerge = roto->getRotoPaintBottomMergeNode(); FramesNeededMap ret; std::map > views; @@ -244,7 +242,9 @@ RotoPaint::getRegionsOfInterest(double /*time*/, { boost::shared_ptr roto = getNode()->getRotoContext(); boost::shared_ptr bottomMerge = roto->getRotoPaintBottomMergeNode(); - ret->insert(std::make_pair(bottomMerge->getLiveInstance(), renderWindow)); + if (bottomMerge) { + ret->insert(std::make_pair(bottomMerge->getLiveInstance(), renderWindow)); + } } bool From d12aa3eaf122f1965fb3a0ba02f9cdf94519ccb8 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Mon, 5 Oct 2015 14:39:16 +0200 Subject: [PATCH 129/178] Add trigger() function to ButtonParam Python API --- .../NatronEngine/ButtonParam.rst | 3 + Engine/EffectInstanceRenderRoI.cpp | 2 +- Engine/KnobTypes.cpp | 6 + Engine/KnobTypes.h | 2 + Engine/NatronEngine/buttonparam_wrapper.cpp | 24 ++ Engine/NatronEngine/natron_wrapper.cpp | 6 + .../natronengine_module_wrapper.cpp | 294 +++++++++--------- Engine/NatronEngine/natronengine_python.h | 84 ++--- Engine/ParameterWrapper.cpp | 6 + Engine/ParameterWrapper.h | 2 + Gui/KnobGuiButton.cpp | 2 +- Gui/NatronGui/natrongui_module_wrapper.cpp | 90 +++--- Gui/NatronGui/natrongui_python.h | 12 +- 13 files changed, 291 insertions(+), 242 deletions(-) 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/Engine/EffectInstanceRenderRoI.cpp b/Engine/EffectInstanceRenderRoI.cpp index bfe74b8f1e..19281f9991 100644 --- a/Engine/EffectInstanceRenderRoI.cpp +++ b/Engine/EffectInstanceRenderRoI.cpp @@ -560,7 +560,7 @@ EffectInstance::renderRoI(const RenderRoIArgs & args, if (ret == eRenderRoIRetCodeOk) { ImageList convertedPlanes; AppInstance* app = getApp(); - assert(inputArgs.components.size() == outputPlanes->size()); + 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(); diff --git a/Engine/KnobTypes.cpp b/Engine/KnobTypes.cpp index 79d582ca5f..a4d0c8f9bb 100644 --- a/Engine/KnobTypes.cpp +++ b/Engine/KnobTypes.cpp @@ -567,6 +567,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/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/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 a974769fa4..0f21370339 100644 --- a/Engine/NatronEngine/natronengine_module_wrapper.cpp +++ b/Engine/NatronEngine/natronengine_module_wrapper.cpp @@ -32,10 +32,24 @@ static PyMethodDef NatronEngine_methods[] = { }; // Classes initialization functions ------------------------------------------------------------ +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_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_BooleanParam(PyObject* module); +void init_StringParamBase(PyObject* module); +void init_StringParam(PyObject* module); +void init_FileParam(PyObject* module); +void init_OutputFileParam(PyObject* module); +void init_PathParam(PyObject* module); void init_IntParam(PyObject* module); void init_Int2DParam(PyObject* module); void init_Int3DParam(PyObject* module); @@ -44,29 +58,15 @@ void init_Double2DParam(PyObject* module); void init_Double3DParam(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); -void init_FileParam(PyObject* module); -void init_OutputFileParam(PyObject* module); -void init_PathParam(PyObject* module); -void init_PageParam(PyObject* module); void init_ButtonParam(PyObject* module); -void init_ParametricParam(PyObject* module); void init_GroupParam(PyObject* module); +void init_PageParam(PyObject* module); +void init_ParametricParam(PyObject* module); void init_Int2DTuple(PyObject* module); void init_Int3DTuple(PyObject* module); void init_Double2DTuple(PyObject* module); void init_Double3DTuple(PyObject* module); void init_ColorTuple(PyObject* module); -void init_PyCoreApplication(PyObject* module); -void init_Group(PyObject* module); -void init_App(PyObject* module); -void init_Effect(PyObject* module); -void init_AppSettings(PyObject* module); -void init_ItemBase(PyObject* module); -void init_Layer(PyObject* module); -void init_BezierCurve(PyObject* module); void init_RectI(PyObject* module); void init_RectD(PyObject* module); void init_Natron(PyObject* module); @@ -137,6 +137,99 @@ static PythonToCppFunc is_std_vector_RectI__PythonToCpp_std_vector_RectI__Conver return 0; } +// C++ to Python conversion for type 'std::vector'. +static PyObject* std_vector_std_string__CppToPython_std_vector_std_string_(const void* cppIn) { + ::std::vector& cppInRef = *((::std::vector*)cppIn); + + // TEMPLATE - stdVectorToPyList - START + ::std::vector::size_type vectorSize = cppInRef.size(); + PyObject* pyOut = PyList_New((int) vectorSize); + for (::std::vector::size_type idx = 0; idx < vectorSize; ++idx) { + ::std::string cppItem(cppInRef[idx]); + PyList_SET_ITEM(pyOut, idx, Shiboken::Conversions::copyToPython(Shiboken::Conversions::PrimitiveTypeConverter(), &cppItem)); + } + return pyOut; + // TEMPLATE - stdVectorToPyList - END + +} +static void std_vector_std_string__PythonToCpp_std_vector_std_string_(PyObject* pyIn, void* cppOut) { + ::std::vector& cppOutRef = *((::std::vector*)cppOut); + + // TEMPLATE - pySeqToStdVector - START + int vectorSize = PySequence_Size(pyIn); + cppOutRef.reserve(vectorSize); + for (int idx = 0; idx < vectorSize; ++idx) { + Shiboken::AutoDecRef pyItem(PySequence_GetItem(pyIn, idx)); + ::std::string cppItem; + Shiboken::Conversions::pythonToCppCopy(Shiboken::Conversions::PrimitiveTypeConverter(), pyItem, &(cppItem)); + cppOutRef.push_back(cppItem); + } + // TEMPLATE - pySeqToStdVector - END + +} +static PythonToCppFunc is_std_vector_std_string__PythonToCpp_std_vector_std_string__Convertible(PyObject* pyIn) { + if (Shiboken::Conversions::convertibleSequenceTypes(Shiboken::Conversions::PrimitiveTypeConverter(), pyIn)) + return std_vector_std_string__PythonToCpp_std_vector_std_string_; + return 0; +} + +// C++ to Python conversion for type 'std::pair'. +static PyObject* std_pair_std_string_std_string__CppToPython_std_pair_std_string_std_string_(const void* cppIn) { + ::std::pair& cppInRef = *((::std::pair*)cppIn); + + PyObject* pyOut = PyTuple_New(2); + PyTuple_SET_ITEM(pyOut, 0, Shiboken::Conversions::copyToPython(Shiboken::Conversions::PrimitiveTypeConverter(), &cppInRef.first)); + PyTuple_SET_ITEM(pyOut, 1, Shiboken::Conversions::copyToPython(Shiboken::Conversions::PrimitiveTypeConverter(), &cppInRef.second)); + return pyOut; + +} +static void std_pair_std_string_std_string__PythonToCpp_std_pair_std_string_std_string_(PyObject* pyIn, void* cppOut) { + ::std::pair& cppOutRef = *((::std::pair*)cppOut); + + Shiboken::Conversions::pythonToCppCopy(Shiboken::Conversions::PrimitiveTypeConverter(), PySequence_Fast_GET_ITEM(pyIn, 0), &(cppOutRef.first)); + Shiboken::Conversions::pythonToCppCopy(Shiboken::Conversions::PrimitiveTypeConverter(), PySequence_Fast_GET_ITEM(pyIn, 1), &(cppOutRef.second)); + +} +static PythonToCppFunc is_std_pair_std_string_std_string__PythonToCpp_std_pair_std_string_std_string__Convertible(PyObject* pyIn) { + if (Shiboken::Conversions::convertiblePairTypes(Shiboken::Conversions::PrimitiveTypeConverter(), false, Shiboken::Conversions::PrimitiveTypeConverter(), false, pyIn)) + return std_pair_std_string_std_string__PythonToCpp_std_pair_std_string_std_string_; + return 0; +} + +// C++ to Python conversion for type 'const std::list > &'. +static PyObject* conststd_list_std_pair_std_string_std_string__REF_CppToPython_conststd_list_std_pair_std_string_std_string__REF(const void* cppIn) { + ::std::list >& cppInRef = *((::std::list >*)cppIn); + + // TEMPLATE - stdListToPyList - START + PyObject* pyOut = PyList_New((int) cppInRef.size()); + ::std::list >::const_iterator it = cppInRef.begin(); + for (int idx = 0; it != cppInRef.end(); ++it, ++idx) { + ::std::pair cppItem(*it); + PyList_SET_ITEM(pyOut, idx, Shiboken::Conversions::copyToPython(SbkNatronEngineTypeConverters[SBK_NATRONENGINE_STD_PAIR_STD_STRING_STD_STRING_IDX], &cppItem)); + } + return pyOut; + // TEMPLATE - stdListToPyList - END + +} +static void conststd_list_std_pair_std_string_std_string__REF_PythonToCpp_conststd_list_std_pair_std_string_std_string__REF(PyObject* pyIn, void* cppOut) { + ::std::list >& cppOutRef = *((::std::list >*)cppOut); + + // TEMPLATE - pyListToStdList - START + for (int i = 0; i < PySequence_Size(pyIn); i++) { + Shiboken::AutoDecRef pyItem(PySequence_GetItem(pyIn, i)); + ::std::pair cppItem = ::std::pair(); + Shiboken::Conversions::pythonToCppCopy(SbkNatronEngineTypeConverters[SBK_NATRONENGINE_STD_PAIR_STD_STRING_STD_STRING_IDX], pyItem, &(cppItem)); + cppOutRef.push_back(cppItem); + } + // TEMPLATE - pyListToStdList - END + +} +static PythonToCppFunc is_conststd_list_std_pair_std_string_std_string__REF_PythonToCpp_conststd_list_std_pair_std_string_std_string__REF_Convertible(PyObject* pyIn) { + if (Shiboken::Conversions::convertibleSequenceTypes(SbkNatronEngineTypeConverters[SBK_NATRONENGINE_STD_PAIR_STD_STRING_STD_STRING_IDX], pyIn)) + return conststd_list_std_pair_std_string_std_string__REF_PythonToCpp_conststd_list_std_pair_std_string_std_string__REF; + return 0; +} + // C++ to Python conversion for type 'std::list'. static PyObject* std_list_ItemBasePTR__CppToPython_std_list_ItemBasePTR_(const void* cppIn) { ::std::list& cppInRef = *((::std::list*)cppIn); @@ -307,99 +400,6 @@ static PythonToCppFunc is_std_list_std_string__PythonToCpp_std_list_std_string__ return 0; } -// C++ to Python conversion for type 'std::vector'. -static PyObject* std_vector_std_string__CppToPython_std_vector_std_string_(const void* cppIn) { - ::std::vector& cppInRef = *((::std::vector*)cppIn); - - // TEMPLATE - stdVectorToPyList - START - ::std::vector::size_type vectorSize = cppInRef.size(); - PyObject* pyOut = PyList_New((int) vectorSize); - for (::std::vector::size_type idx = 0; idx < vectorSize; ++idx) { - ::std::string cppItem(cppInRef[idx]); - PyList_SET_ITEM(pyOut, idx, Shiboken::Conversions::copyToPython(Shiboken::Conversions::PrimitiveTypeConverter(), &cppItem)); - } - return pyOut; - // TEMPLATE - stdVectorToPyList - END - -} -static void std_vector_std_string__PythonToCpp_std_vector_std_string_(PyObject* pyIn, void* cppOut) { - ::std::vector& cppOutRef = *((::std::vector*)cppOut); - - // TEMPLATE - pySeqToStdVector - START - int vectorSize = PySequence_Size(pyIn); - cppOutRef.reserve(vectorSize); - for (int idx = 0; idx < vectorSize; ++idx) { - Shiboken::AutoDecRef pyItem(PySequence_GetItem(pyIn, idx)); - ::std::string cppItem; - Shiboken::Conversions::pythonToCppCopy(Shiboken::Conversions::PrimitiveTypeConverter(), pyItem, &(cppItem)); - cppOutRef.push_back(cppItem); - } - // TEMPLATE - pySeqToStdVector - END - -} -static PythonToCppFunc is_std_vector_std_string__PythonToCpp_std_vector_std_string__Convertible(PyObject* pyIn) { - if (Shiboken::Conversions::convertibleSequenceTypes(Shiboken::Conversions::PrimitiveTypeConverter(), pyIn)) - return std_vector_std_string__PythonToCpp_std_vector_std_string_; - return 0; -} - -// C++ to Python conversion for type 'std::pair'. -static PyObject* std_pair_std_string_std_string__CppToPython_std_pair_std_string_std_string_(const void* cppIn) { - ::std::pair& cppInRef = *((::std::pair*)cppIn); - - PyObject* pyOut = PyTuple_New(2); - PyTuple_SET_ITEM(pyOut, 0, Shiboken::Conversions::copyToPython(Shiboken::Conversions::PrimitiveTypeConverter(), &cppInRef.first)); - PyTuple_SET_ITEM(pyOut, 1, Shiboken::Conversions::copyToPython(Shiboken::Conversions::PrimitiveTypeConverter(), &cppInRef.second)); - return pyOut; - -} -static void std_pair_std_string_std_string__PythonToCpp_std_pair_std_string_std_string_(PyObject* pyIn, void* cppOut) { - ::std::pair& cppOutRef = *((::std::pair*)cppOut); - - Shiboken::Conversions::pythonToCppCopy(Shiboken::Conversions::PrimitiveTypeConverter(), PySequence_Fast_GET_ITEM(pyIn, 0), &(cppOutRef.first)); - Shiboken::Conversions::pythonToCppCopy(Shiboken::Conversions::PrimitiveTypeConverter(), PySequence_Fast_GET_ITEM(pyIn, 1), &(cppOutRef.second)); - -} -static PythonToCppFunc is_std_pair_std_string_std_string__PythonToCpp_std_pair_std_string_std_string__Convertible(PyObject* pyIn) { - if (Shiboken::Conversions::convertiblePairTypes(Shiboken::Conversions::PrimitiveTypeConverter(), false, Shiboken::Conversions::PrimitiveTypeConverter(), false, pyIn)) - return std_pair_std_string_std_string__PythonToCpp_std_pair_std_string_std_string_; - return 0; -} - -// C++ to Python conversion for type 'const std::list > &'. -static PyObject* conststd_list_std_pair_std_string_std_string__REF_CppToPython_conststd_list_std_pair_std_string_std_string__REF(const void* cppIn) { - ::std::list >& cppInRef = *((::std::list >*)cppIn); - - // TEMPLATE - stdListToPyList - START - PyObject* pyOut = PyList_New((int) cppInRef.size()); - ::std::list >::const_iterator it = cppInRef.begin(); - for (int idx = 0; it != cppInRef.end(); ++it, ++idx) { - ::std::pair cppItem(*it); - PyList_SET_ITEM(pyOut, idx, Shiboken::Conversions::copyToPython(SbkNatronEngineTypeConverters[SBK_NATRONENGINE_STD_PAIR_STD_STRING_STD_STRING_IDX], &cppItem)); - } - return pyOut; - // TEMPLATE - stdListToPyList - END - -} -static void conststd_list_std_pair_std_string_std_string__REF_PythonToCpp_conststd_list_std_pair_std_string_std_string__REF(PyObject* pyIn, void* cppOut) { - ::std::list >& cppOutRef = *((::std::list >*)cppOut); - - // TEMPLATE - pyListToStdList - START - for (int i = 0; i < PySequence_Size(pyIn); i++) { - Shiboken::AutoDecRef pyItem(PySequence_GetItem(pyIn, i)); - ::std::pair cppItem = ::std::pair(); - Shiboken::Conversions::pythonToCppCopy(SbkNatronEngineTypeConverters[SBK_NATRONENGINE_STD_PAIR_STD_STRING_STD_STRING_IDX], pyItem, &(cppItem)); - cppOutRef.push_back(cppItem); - } - // TEMPLATE - pyListToStdList - END - -} -static PythonToCppFunc is_conststd_list_std_pair_std_string_std_string__REF_PythonToCpp_conststd_list_std_pair_std_string_std_string__REF_Convertible(PyObject* pyIn) { - if (Shiboken::Conversions::convertibleSequenceTypes(SbkNatronEngineTypeConverters[SBK_NATRONENGINE_STD_PAIR_STD_STRING_STD_STRING_IDX], pyIn)) - return conststd_list_std_pair_std_string_std_string__REF_PythonToCpp_conststd_list_std_pair_std_string_std_string__REF; - return 0; -} - // C++ to Python conversion for type 'QList'. static PyObject* _QList_QVariant__CppToPython__QList_QVariant_(const void* cppIn) { ::QList& cppInRef = *((::QList*)cppIn); @@ -558,10 +558,24 @@ SBK_MODULE_INIT_FUNCTION_BEGIN(NatronEngine) #endif // Initialize classes in the type system + init_PyCoreApplication(module); + init_Group(module); + init_App(module); + init_AppSettings(module); + init_ItemBase(module); + init_BezierCurve(module); + init_Layer(module); init_Roto(module); init_UserParamHolder(module); + init_Effect(module); init_Param(module); init_AnimatedParam(module); + init_BooleanParam(module); + init_StringParamBase(module); + init_StringParam(module); + init_FileParam(module); + init_OutputFileParam(module); + init_PathParam(module); init_IntParam(module); init_Int2DParam(module); init_Int3DParam(module); @@ -570,29 +584,15 @@ SBK_MODULE_INIT_FUNCTION_BEGIN(NatronEngine) init_Double3DParam(module); init_ColorParam(module); init_ChoiceParam(module); - init_BooleanParam(module); - init_StringParamBase(module); - init_StringParam(module); - init_FileParam(module); - init_OutputFileParam(module); - init_PathParam(module); - init_PageParam(module); init_ButtonParam(module); - init_ParametricParam(module); init_GroupParam(module); + init_PageParam(module); + init_ParametricParam(module); init_Int2DTuple(module); init_Int3DTuple(module); init_Double2DTuple(module); init_Double3DTuple(module); init_ColorTuple(module); - init_PyCoreApplication(module); - init_Group(module); - init_App(module); - init_Effect(module); - init_AppSettings(module); - init_ItemBase(module); - init_Layer(module); - init_BezierCurve(module); init_RectI(module); init_RectD(module); init_Natron(module); @@ -613,6 +613,28 @@ SBK_MODULE_INIT_FUNCTION_BEGIN(NatronEngine) std_vector_RectI__PythonToCpp_std_vector_RectI_, is_std_vector_RectI__PythonToCpp_std_vector_RectI__Convertible); + // Register converter for type 'std::vector'. + SbkNatronEngineTypeConverters[SBK_NATRONENGINE_STD_VECTOR_STD_STRING_IDX] = Shiboken::Conversions::createConverter(&PyList_Type, std_vector_std_string__CppToPython_std_vector_std_string_); + Shiboken::Conversions::registerConverterName(SbkNatronEngineTypeConverters[SBK_NATRONENGINE_STD_VECTOR_STD_STRING_IDX], "std::vector"); + Shiboken::Conversions::addPythonToCppValueConversion(SbkNatronEngineTypeConverters[SBK_NATRONENGINE_STD_VECTOR_STD_STRING_IDX], + std_vector_std_string__PythonToCpp_std_vector_std_string_, + is_std_vector_std_string__PythonToCpp_std_vector_std_string__Convertible); + + // Register converter for type 'std::pair'. + SbkNatronEngineTypeConverters[SBK_NATRONENGINE_STD_PAIR_STD_STRING_STD_STRING_IDX] = Shiboken::Conversions::createConverter(&PyList_Type, std_pair_std_string_std_string__CppToPython_std_pair_std_string_std_string_); + Shiboken::Conversions::registerConverterName(SbkNatronEngineTypeConverters[SBK_NATRONENGINE_STD_PAIR_STD_STRING_STD_STRING_IDX], "std::pair"); + Shiboken::Conversions::addPythonToCppValueConversion(SbkNatronEngineTypeConverters[SBK_NATRONENGINE_STD_PAIR_STD_STRING_STD_STRING_IDX], + std_pair_std_string_std_string__PythonToCpp_std_pair_std_string_std_string_, + is_std_pair_std_string_std_string__PythonToCpp_std_pair_std_string_std_string__Convertible); + + // Register converter for type 'const std::list>&'. + SbkNatronEngineTypeConverters[SBK_NATRONENGINE_STD_LIST_STD_PAIR_STD_STRING_STD_STRING_IDX] = Shiboken::Conversions::createConverter(&PyList_Type, conststd_list_std_pair_std_string_std_string__REF_CppToPython_conststd_list_std_pair_std_string_std_string__REF); + Shiboken::Conversions::registerConverterName(SbkNatronEngineTypeConverters[SBK_NATRONENGINE_STD_LIST_STD_PAIR_STD_STRING_STD_STRING_IDX], "const std::list>&"); + Shiboken::Conversions::registerConverterName(SbkNatronEngineTypeConverters[SBK_NATRONENGINE_STD_LIST_STD_PAIR_STD_STRING_STD_STRING_IDX], "std::list>"); + Shiboken::Conversions::addPythonToCppValueConversion(SbkNatronEngineTypeConverters[SBK_NATRONENGINE_STD_LIST_STD_PAIR_STD_STRING_STD_STRING_IDX], + conststd_list_std_pair_std_string_std_string__REF_PythonToCpp_conststd_list_std_pair_std_string_std_string__REF, + is_conststd_list_std_pair_std_string_std_string__REF_PythonToCpp_conststd_list_std_pair_std_string_std_string__REF_Convertible); + // Register converter for type 'std::list'. SbkNatronEngineTypeConverters[SBK_NATRONENGINE_STD_LIST_ITEMBASEPTR_IDX] = Shiboken::Conversions::createConverter(&PyList_Type, std_list_ItemBasePTR__CppToPython_std_list_ItemBasePTR_); Shiboken::Conversions::registerConverterName(SbkNatronEngineTypeConverters[SBK_NATRONENGINE_STD_LIST_ITEMBASEPTR_IDX], "std::list"); @@ -649,28 +671,6 @@ SBK_MODULE_INIT_FUNCTION_BEGIN(NatronEngine) std_list_std_string__PythonToCpp_std_list_std_string_, is_std_list_std_string__PythonToCpp_std_list_std_string__Convertible); - // Register converter for type 'std::vector'. - SbkNatronEngineTypeConverters[SBK_NATRONENGINE_STD_VECTOR_STD_STRING_IDX] = Shiboken::Conversions::createConverter(&PyList_Type, std_vector_std_string__CppToPython_std_vector_std_string_); - Shiboken::Conversions::registerConverterName(SbkNatronEngineTypeConverters[SBK_NATRONENGINE_STD_VECTOR_STD_STRING_IDX], "std::vector"); - Shiboken::Conversions::addPythonToCppValueConversion(SbkNatronEngineTypeConverters[SBK_NATRONENGINE_STD_VECTOR_STD_STRING_IDX], - std_vector_std_string__PythonToCpp_std_vector_std_string_, - is_std_vector_std_string__PythonToCpp_std_vector_std_string__Convertible); - - // Register converter for type 'std::pair'. - SbkNatronEngineTypeConverters[SBK_NATRONENGINE_STD_PAIR_STD_STRING_STD_STRING_IDX] = Shiboken::Conversions::createConverter(&PyList_Type, std_pair_std_string_std_string__CppToPython_std_pair_std_string_std_string_); - Shiboken::Conversions::registerConverterName(SbkNatronEngineTypeConverters[SBK_NATRONENGINE_STD_PAIR_STD_STRING_STD_STRING_IDX], "std::pair"); - Shiboken::Conversions::addPythonToCppValueConversion(SbkNatronEngineTypeConverters[SBK_NATRONENGINE_STD_PAIR_STD_STRING_STD_STRING_IDX], - std_pair_std_string_std_string__PythonToCpp_std_pair_std_string_std_string_, - is_std_pair_std_string_std_string__PythonToCpp_std_pair_std_string_std_string__Convertible); - - // Register converter for type 'const std::list>&'. - SbkNatronEngineTypeConverters[SBK_NATRONENGINE_STD_LIST_STD_PAIR_STD_STRING_STD_STRING_IDX] = Shiboken::Conversions::createConverter(&PyList_Type, conststd_list_std_pair_std_string_std_string__REF_CppToPython_conststd_list_std_pair_std_string_std_string__REF); - Shiboken::Conversions::registerConverterName(SbkNatronEngineTypeConverters[SBK_NATRONENGINE_STD_LIST_STD_PAIR_STD_STRING_STD_STRING_IDX], "const std::list>&"); - Shiboken::Conversions::registerConverterName(SbkNatronEngineTypeConverters[SBK_NATRONENGINE_STD_LIST_STD_PAIR_STD_STRING_STD_STRING_IDX], "std::list>"); - Shiboken::Conversions::addPythonToCppValueConversion(SbkNatronEngineTypeConverters[SBK_NATRONENGINE_STD_LIST_STD_PAIR_STD_STRING_STD_STRING_IDX], - conststd_list_std_pair_std_string_std_string__REF_PythonToCpp_conststd_list_std_pair_std_string_std_string__REF, - is_conststd_list_std_pair_std_string_std_string__REF_PythonToCpp_conststd_list_std_pair_std_string_std_string__REF_Convertible); - // Register converter for type 'QList'. SbkNatronEngineTypeConverters[SBK_NATRONENGINE_QLIST_QVARIANT_IDX] = Shiboken::Conversions::createConverter(&PyList_Type, _QList_QVariant__CppToPython__QList_QVariant_); Shiboken::Conversions::registerConverterName(SbkNatronEngineTypeConverters[SBK_NATRONENGINE_QLIST_QVARIANT_IDX], "QList"); diff --git a/Engine/NatronEngine/natronengine_python.h b/Engine/NatronEngine/natronengine_python.h index 5a746fec4a..97df07ca3d 100644 --- a/Engine/NatronEngine/natronengine_python.h +++ b/Engine/NatronEngine/natronengine_python.h @@ -74,29 +74,17 @@ CLANG_DIAG_ON(uninitialized) #define SBK_NATRON_VIEWERCOLORSPACEENUM_IDX 39 #define SBK_RECTD_IDX 48 #define SBK_RECTI_IDX 49 -#define SBK_ITEMBASE_IDX 24 -#define SBK_BEZIERCURVE_IDX 3 -#define SBK_BEZIERCURVE_CAIROOPERATORENUM_IDX 4 -#define SBK_LAYER_IDX 25 -#define SBK_APPSETTINGS_IDX 2 -#define SBK_GROUP_IDX 17 -#define SBK_APP_IDX 1 -#define SBK_PYCOREAPPLICATION_IDX 46 #define SBK_COLORTUPLE_IDX 9 #define SBK_DOUBLE3DTUPLE_IDX 13 #define SBK_DOUBLE2DTUPLE_IDX 11 #define SBK_INT3DTUPLE_IDX 22 #define SBK_INT2DTUPLE_IDX 20 #define SBK_PARAM_IDX 43 +#define SBK_PARAMETRICPARAM_IDX 44 +#define SBK_PAGEPARAM_IDX 42 #define SBK_GROUPPARAM_IDX 18 #define SBK_BUTTONPARAM_IDX 6 #define SBK_ANIMATEDPARAM_IDX 0 -#define SBK_STRINGPARAMBASE_IDX 53 -#define SBK_PATHPARAM_IDX 45 -#define SBK_OUTPUTFILEPARAM_IDX 41 -#define SBK_FILEPARAM_IDX 16 -#define SBK_STRINGPARAM_IDX 51 -#define SBK_STRINGPARAM_TYPEENUM_IDX 52 #define SBK_BOOLEANPARAM_IDX 5 #define SBK_CHOICEPARAM_IDX 7 #define SBK_COLORPARAM_IDX 8 @@ -106,11 +94,23 @@ CLANG_DIAG_ON(uninitialized) #define SBK_INTPARAM_IDX 23 #define SBK_INT2DPARAM_IDX 19 #define SBK_INT3DPARAM_IDX 21 -#define SBK_PARAMETRICPARAM_IDX 44 -#define SBK_PAGEPARAM_IDX 42 +#define SBK_STRINGPARAMBASE_IDX 53 +#define SBK_PATHPARAM_IDX 45 +#define SBK_OUTPUTFILEPARAM_IDX 41 +#define SBK_FILEPARAM_IDX 16 +#define SBK_STRINGPARAM_IDX 51 +#define SBK_STRINGPARAM_TYPEENUM_IDX 52 #define SBK_USERPARAMHOLDER_IDX 54 -#define SBK_EFFECT_IDX 15 #define SBK_ROTO_IDX 50 +#define SBK_ITEMBASE_IDX 24 +#define SBK_BEZIERCURVE_IDX 3 +#define SBK_BEZIERCURVE_CAIROOPERATORENUM_IDX 4 +#define SBK_LAYER_IDX 25 +#define SBK_APPSETTINGS_IDX 2 +#define SBK_GROUP_IDX 17 +#define SBK_APP_IDX 1 +#define SBK_EFFECT_IDX 15 +#define SBK_PYCOREAPPLICATION_IDX 46 #define SBK_NatronEngine_IDX_COUNT 55 // This variable stores all Python types exported by this module. @@ -122,14 +122,14 @@ extern SbkConverter** SbkNatronEngineTypeConverters; // Converter indices #define SBK_STD_SIZE_T_IDX 0 #define SBK_NATRONENGINE_STD_VECTOR_RECTI_IDX 1 // std::vector -#define SBK_NATRONENGINE_STD_LIST_ITEMBASEPTR_IDX 2 // std::list -#define SBK_NATRONENGINE_STD_LIST_PARAMPTR_IDX 3 // std::list -#define SBK_NATRONENGINE_STD_LIST_EFFECTPTR_IDX 4 // std::list -#define SBK_NATRONENGINE_STD_LIST_INT_IDX 5 // const std::list & -#define SBK_NATRONENGINE_STD_LIST_STD_STRING_IDX 6 // std::list -#define SBK_NATRONENGINE_STD_VECTOR_STD_STRING_IDX 7 // std::vector -#define SBK_NATRONENGINE_STD_PAIR_STD_STRING_STD_STRING_IDX 8 // std::pair -#define SBK_NATRONENGINE_STD_LIST_STD_PAIR_STD_STRING_STD_STRING_IDX 9 // const std::list > & +#define SBK_NATRONENGINE_STD_VECTOR_STD_STRING_IDX 2 // std::vector +#define SBK_NATRONENGINE_STD_PAIR_STD_STRING_STD_STRING_IDX 3 // std::pair +#define SBK_NATRONENGINE_STD_LIST_STD_PAIR_STD_STRING_STD_STRING_IDX 4 // const std::list > & +#define SBK_NATRONENGINE_STD_LIST_ITEMBASEPTR_IDX 5 // std::list +#define SBK_NATRONENGINE_STD_LIST_PARAMPTR_IDX 6 // std::list +#define SBK_NATRONENGINE_STD_LIST_EFFECTPTR_IDX 7 // std::list +#define SBK_NATRONENGINE_STD_LIST_INT_IDX 8 // const std::list & +#define SBK_NATRONENGINE_STD_LIST_STD_STRING_IDX 9 // std::list #define SBK_NATRONENGINE_QLIST_QVARIANT_IDX 10 // QList #define SBK_NATRONENGINE_QLIST_QSTRING_IDX 11 // QList #define SBK_NATRONENGINE_QMAP_QSTRING_QVARIANT_IDX 12 // QMap @@ -158,29 +158,17 @@ template<> inline PyTypeObject* SbkType< ::Natron::PixmapEnum >() { return SbkNa 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< ::ItemBase >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_ITEMBASE_IDX]); } -template<> inline PyTypeObject* SbkType< ::BezierCurve::CairoOperatorEnum >() { return SbkNatronEngineTypes[SBK_BEZIERCURVE_CAIROOPERATORENUM_IDX]; } -template<> inline PyTypeObject* SbkType< ::BezierCurve >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_BEZIERCURVE_IDX]); } -template<> inline PyTypeObject* SbkType< ::Layer >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_LAYER_IDX]); } -template<> inline PyTypeObject* SbkType< ::AppSettings >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_APPSETTINGS_IDX]); } -template<> inline PyTypeObject* SbkType< ::Group >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_GROUP_IDX]); } -template<> inline PyTypeObject* SbkType< ::App >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_APP_IDX]); } -template<> inline PyTypeObject* SbkType< ::PyCoreApplication >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_PYCOREAPPLICATION_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]); } template<> inline PyTypeObject* SbkType< ::Double2DTuple >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_DOUBLE2DTUPLE_IDX]); } template<> inline PyTypeObject* SbkType< ::Int3DTuple >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_INT3DTUPLE_IDX]); } template<> inline PyTypeObject* SbkType< ::Int2DTuple >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_INT2DTUPLE_IDX]); } template<> inline PyTypeObject* SbkType< ::Param >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_PARAM_IDX]); } +template<> inline PyTypeObject* SbkType< ::ParametricParam >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_PARAMETRICPARAM_IDX]); } +template<> inline PyTypeObject* SbkType< ::PageParam >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_PAGEPARAM_IDX]); } 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< ::StringParamBase >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_STRINGPARAMBASE_IDX]); } -template<> inline PyTypeObject* SbkType< ::PathParam >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_PATHPARAM_IDX]); } -template<> inline PyTypeObject* SbkType< ::OutputFileParam >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_OUTPUTFILEPARAM_IDX]); } -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< ::ChoiceParam >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_CHOICEPARAM_IDX]); } template<> inline PyTypeObject* SbkType< ::ColorParam >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_COLORPARAM_IDX]); } @@ -190,11 +178,23 @@ template<> inline PyTypeObject* SbkType< ::Double3DParam >() { return reinterpre template<> inline PyTypeObject* SbkType< ::IntParam >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_INTPARAM_IDX]); } template<> inline PyTypeObject* SbkType< ::Int2DParam >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_INT2DPARAM_IDX]); } template<> inline PyTypeObject* SbkType< ::Int3DParam >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_INT3DPARAM_IDX]); } -template<> inline PyTypeObject* SbkType< ::ParametricParam >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_PARAMETRICPARAM_IDX]); } -template<> inline PyTypeObject* SbkType< ::PageParam >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_PAGEPARAM_IDX]); } +template<> inline PyTypeObject* SbkType< ::StringParamBase >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_STRINGPARAMBASE_IDX]); } +template<> inline PyTypeObject* SbkType< ::PathParam >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_PATHPARAM_IDX]); } +template<> inline PyTypeObject* SbkType< ::OutputFileParam >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_OUTPUTFILEPARAM_IDX]); } +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< ::UserParamHolder >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_USERPARAMHOLDER_IDX]); } -template<> inline PyTypeObject* SbkType< ::Effect >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_EFFECT_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]); } +template<> inline PyTypeObject* SbkType< ::BezierCurve::CairoOperatorEnum >() { return SbkNatronEngineTypes[SBK_BEZIERCURVE_CAIROOPERATORENUM_IDX]; } +template<> inline PyTypeObject* SbkType< ::BezierCurve >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_BEZIERCURVE_IDX]); } +template<> inline PyTypeObject* SbkType< ::Layer >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_LAYER_IDX]); } +template<> inline PyTypeObject* SbkType< ::AppSettings >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_APPSETTINGS_IDX]); } +template<> inline PyTypeObject* SbkType< ::Group >() { return reinterpret_cast(SbkNatronEngineTypes[SBK_GROUP_IDX]); } +template<> 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]); } } // namespace Shiboken diff --git a/Engine/ParameterWrapper.cpp b/Engine/ParameterWrapper.cpp index 303a663bcf..b7f7858539 100644 --- a/Engine/ParameterWrapper.cpp +++ b/Engine/ParameterWrapper.cpp @@ -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 c6379b49a6..243528458d 100644 --- a/Engine/ParameterWrapper.h +++ b/Engine/ParameterWrapper.h @@ -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/Gui/KnobGuiButton.cpp b/Gui/KnobGuiButton.cpp index 5b2b330490..567dc5cb1c 100644 --- a/Gui/KnobGuiButton.cpp +++ b/Gui/KnobGuiButton.cpp @@ -142,7 +142,7 @@ KnobGuiButton::emitValueChanged() { boost::shared_ptr k = _knob.lock(); assert(k); - k->evaluateValueChange(0, k->getCurrentTime(), Natron::eValueChangedReasonUserEdited); + k->trigger(); } void diff --git a/Gui/NatronGui/natrongui_module_wrapper.cpp b/Gui/NatronGui/natrongui_module_wrapper.cpp index 61a193684d..844ab26bb6 100644 --- a/Gui/NatronGui/natrongui_module_wrapper.cpp +++ b/Gui/NatronGui/natrongui_module_wrapper.cpp @@ -32,10 +32,10 @@ static PyMethodDef NatronGui_methods[] = { }; // Classes initialization functions ------------------------------------------------------------ -void init_PyViewer(PyObject* module); -void init_PyTabWidget(PyObject* module); void init_GuiApp(PyObject* module); void init_PyGuiApplication(PyObject* module); +void init_PyViewer(PyObject* module); +void init_PyTabWidget(PyObject* module); void init_PyModalDialog(PyObject* module); void init_PyPanel(PyObject* module); @@ -46,6 +46,40 @@ SbkConverter** SbkPySide_QtGuiTypeConverters; // Module initialization ------------------------------------------------------------ // Container Type converters. +// C++ to Python conversion for type 'std::list'. +static PyObject* _std_list_std_string__CppToPython__std_list_std_string_(const void* cppIn) { + ::std::list& cppInRef = *((::std::list*)cppIn); + + // TEMPLATE - stdListToPyList - START + PyObject* pyOut = PyList_New((int) cppInRef.size()); + ::std::list::const_iterator it = cppInRef.begin(); + for (int idx = 0; it != cppInRef.end(); ++it, ++idx) { + ::std::string cppItem(*it); + PyList_SET_ITEM(pyOut, idx, Shiboken::Conversions::copyToPython(Shiboken::Conversions::PrimitiveTypeConverter(), &cppItem)); + } + return pyOut; + // TEMPLATE - stdListToPyList - END + +} +static void _std_list_std_string__PythonToCpp__std_list_std_string_(PyObject* pyIn, void* cppOut) { + ::std::list& cppOutRef = *((::std::list*)cppOut); + + // TEMPLATE - pyListToStdList - START + for (int i = 0; i < PySequence_Size(pyIn); i++) { + Shiboken::AutoDecRef pyItem(PySequence_GetItem(pyIn, i)); + ::std::string cppItem; + Shiboken::Conversions::pythonToCppCopy(Shiboken::Conversions::PrimitiveTypeConverter(), pyItem, &(cppItem)); + cppOutRef.push_back(cppItem); + } + // TEMPLATE - pyListToStdList - END + +} +static PythonToCppFunc is__std_list_std_string__PythonToCpp__std_list_std_string__Convertible(PyObject* pyIn) { + if (Shiboken::Conversions::convertibleSequenceTypes(Shiboken::Conversions::PrimitiveTypeConverter(), pyIn)) + return _std_list_std_string__PythonToCpp__std_list_std_string_; + return 0; +} + // C++ to Python conversion for type 'std::list'. static PyObject* _std_list_EffectPTR__CppToPython__std_list_EffectPTR_(const void* cppIn) { ::std::list& cppInRef = *((::std::list*)cppIn); @@ -150,40 +184,6 @@ static PythonToCppFunc is__conststd_list_int_REF_PythonToCpp__conststd_list_int_ return 0; } -// C++ to Python conversion for type 'std::list'. -static PyObject* _std_list_std_string__CppToPython__std_list_std_string_(const void* cppIn) { - ::std::list& cppInRef = *((::std::list*)cppIn); - - // TEMPLATE - stdListToPyList - START - PyObject* pyOut = PyList_New((int) cppInRef.size()); - ::std::list::const_iterator it = cppInRef.begin(); - for (int idx = 0; it != cppInRef.end(); ++it, ++idx) { - ::std::string cppItem(*it); - PyList_SET_ITEM(pyOut, idx, Shiboken::Conversions::copyToPython(Shiboken::Conversions::PrimitiveTypeConverter(), &cppItem)); - } - return pyOut; - // TEMPLATE - stdListToPyList - END - -} -static void _std_list_std_string__PythonToCpp__std_list_std_string_(PyObject* pyIn, void* cppOut) { - ::std::list& cppOutRef = *((::std::list*)cppOut); - - // TEMPLATE - pyListToStdList - START - for (int i = 0; i < PySequence_Size(pyIn); i++) { - Shiboken::AutoDecRef pyItem(PySequence_GetItem(pyIn, i)); - ::std::string cppItem; - Shiboken::Conversions::pythonToCppCopy(Shiboken::Conversions::PrimitiveTypeConverter(), pyItem, &(cppItem)); - cppOutRef.push_back(cppItem); - } - // TEMPLATE - pyListToStdList - END - -} -static PythonToCppFunc is__std_list_std_string__PythonToCpp__std_list_std_string__Convertible(PyObject* pyIn) { - if (Shiboken::Conversions::convertibleSequenceTypes(Shiboken::Conversions::PrimitiveTypeConverter(), pyIn)) - return _std_list_std_string__PythonToCpp__std_list_std_string_; - return 0; -} - // C++ to Python conversion for type 'QList'. static PyObject* _QList_QActionPTR__CppToPython__QList_QActionPTR_(const void* cppIn) { ::QList& cppInRef = *((::QList*)cppIn); @@ -494,13 +494,20 @@ SBK_MODULE_INIT_FUNCTION_BEGIN(NatronGui) #endif // Initialize classes in the type system - init_PyViewer(module); - init_PyTabWidget(module); init_GuiApp(module); init_PyGuiApplication(module); + init_PyViewer(module); + init_PyTabWidget(module); init_PyModalDialog(module); init_PyPanel(module); + // Register converter for type 'std::list'. + SbkNatronGuiTypeConverters[SBK_NATRONGUI_STD_LIST_STD_STRING_IDX] = Shiboken::Conversions::createConverter(&PyList_Type, _std_list_std_string__CppToPython__std_list_std_string_); + Shiboken::Conversions::registerConverterName(SbkNatronGuiTypeConverters[SBK_NATRONGUI_STD_LIST_STD_STRING_IDX], "std::list"); + Shiboken::Conversions::addPythonToCppValueConversion(SbkNatronGuiTypeConverters[SBK_NATRONGUI_STD_LIST_STD_STRING_IDX], + _std_list_std_string__PythonToCpp__std_list_std_string_, + is__std_list_std_string__PythonToCpp__std_list_std_string__Convertible); + // Register converter for type 'std::list'. SbkNatronGuiTypeConverters[SBK_NATRONGUI_STD_LIST_EFFECTPTR_IDX] = Shiboken::Conversions::createConverter(&PyList_Type, _std_list_EffectPTR__CppToPython__std_list_EffectPTR_); Shiboken::Conversions::registerConverterName(SbkNatronGuiTypeConverters[SBK_NATRONGUI_STD_LIST_EFFECTPTR_IDX], "std::list"); @@ -524,13 +531,6 @@ SBK_MODULE_INIT_FUNCTION_BEGIN(NatronGui) _conststd_list_int_REF_PythonToCpp__conststd_list_int_REF, is__conststd_list_int_REF_PythonToCpp__conststd_list_int_REF_Convertible); - // Register converter for type 'std::list'. - SbkNatronGuiTypeConverters[SBK_NATRONGUI_STD_LIST_STD_STRING_IDX] = Shiboken::Conversions::createConverter(&PyList_Type, _std_list_std_string__CppToPython__std_list_std_string_); - Shiboken::Conversions::registerConverterName(SbkNatronGuiTypeConverters[SBK_NATRONGUI_STD_LIST_STD_STRING_IDX], "std::list"); - Shiboken::Conversions::addPythonToCppValueConversion(SbkNatronGuiTypeConverters[SBK_NATRONGUI_STD_LIST_STD_STRING_IDX], - _std_list_std_string__PythonToCpp__std_list_std_string_, - is__std_list_std_string__PythonToCpp__std_list_std_string__Convertible); - // Register converter for type 'QList'. SbkNatronGuiTypeConverters[SBK_NATRONGUI_QLIST_QACTIONPTR_IDX] = Shiboken::Conversions::createConverter(&PyList_Type, _QList_QActionPTR__CppToPython__QList_QActionPTR_); Shiboken::Conversions::registerConverterName(SbkNatronGuiTypeConverters[SBK_NATRONGUI_QLIST_QACTIONPTR_IDX], "QList"); diff --git a/Gui/NatronGui/natrongui_python.h b/Gui/NatronGui/natrongui_python.h index 573ab7974b..5af481fe4c 100644 --- a/Gui/NatronGui/natrongui_python.h +++ b/Gui/NatronGui/natrongui_python.h @@ -56,9 +56,9 @@ CLANG_DIAG_ON(uninitialized) // Type indices #define SBK_PYTABWIDGET_IDX 4 -#define SBK_GUIAPP_IDX 0 #define SBK_PYVIEWER_IDX 5 #define SBK_PYGUIAPPLICATION_IDX 1 +#define SBK_GUIAPP_IDX 0 #define SBK_PYPANEL_IDX 3 #define SBK_PYMODALDIALOG_IDX 2 #define SBK_NatronGui_IDX_COUNT 6 @@ -70,10 +70,10 @@ extern PyTypeObject** SbkNatronGuiTypes; extern SbkConverter** SbkNatronGuiTypeConverters; // Converter indices -#define SBK_NATRONGUI_STD_LIST_EFFECTPTR_IDX 0 // std::list -#define SBK_NATRONGUI_STD_VECTOR_STD_STRING_IDX 1 // const std::vector & -#define SBK_NATRONGUI_STD_LIST_INT_IDX 2 // const std::list & -#define SBK_NATRONGUI_STD_LIST_STD_STRING_IDX 3 // std::list +#define SBK_NATRONGUI_STD_LIST_STD_STRING_IDX 0 // std::list +#define SBK_NATRONGUI_STD_LIST_EFFECTPTR_IDX 1 // std::list +#define SBK_NATRONGUI_STD_VECTOR_STD_STRING_IDX 2 // const std::vector & +#define SBK_NATRONGUI_STD_LIST_INT_IDX 3 // const std::list & #define SBK_NATRONGUI_QLIST_QACTIONPTR_IDX 4 // QList #define SBK_NATRONGUI_QLIST_QOBJECTPTR_IDX 5 // const QList & #define SBK_NATRONGUI_QLIST_QBYTEARRAY_IDX 6 // QList @@ -90,9 +90,9 @@ namespace Shiboken // PyType functions, to get the PyObjectType for a type T template<> inline PyTypeObject* SbkType< ::PyTabWidget >() { return reinterpret_cast(SbkNatronGuiTypes[SBK_PYTABWIDGET_IDX]); } -template<> inline PyTypeObject* SbkType< ::GuiApp >() { return reinterpret_cast(SbkNatronGuiTypes[SBK_GUIAPP_IDX]); } template<> inline PyTypeObject* SbkType< ::PyViewer >() { return reinterpret_cast(SbkNatronGuiTypes[SBK_PYVIEWER_IDX]); } template<> inline PyTypeObject* SbkType< ::PyGuiApplication >() { return reinterpret_cast(SbkNatronGuiTypes[SBK_PYGUIAPPLICATION_IDX]); } +template<> inline PyTypeObject* SbkType< ::GuiApp >() { return reinterpret_cast(SbkNatronGuiTypes[SBK_GUIAPP_IDX]); } template<> inline PyTypeObject* SbkType< ::PyPanel >() { return reinterpret_cast(SbkNatronGuiTypes[SBK_PYPANEL_IDX]); } template<> inline PyTypeObject* SbkType< ::PyModalDialog >() { return reinterpret_cast(SbkNatronGuiTypes[SBK_PYMODALDIALOG_IDX]); } From fb59e56fb7710a152adcac76ae80056794ac8e4b Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Mon, 5 Oct 2015 14:51:09 +0200 Subject: [PATCH 130/178] processEvents() in progressUpdate --- Gui/Gui50.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gui/Gui50.cpp b/Gui/Gui50.cpp index 6effc93ab6..2d7152f476 100644 --- a/Gui/Gui50.cpp +++ b/Gui/Gui50.cpp @@ -262,7 +262,7 @@ Gui::progressUpdate(KnobHolder* effect, } found->second->setValue(t * 100); } - //QCoreApplication::processEvents(); + QCoreApplication::processEvents(); return true; } From dc77003a642ff7994452222094dca69d056af21e Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Mon, 5 Oct 2015 15:18:17 +0200 Subject: [PATCH 131/178] Add Script Editor shortcuts to Shortcut Editor --- Gui/ActionShortcuts.h | 20 +++++++++++++++++ Gui/GuiApplicationManager10.cpp | 10 +++++++++ Gui/ScriptEditor.cpp | 38 +++++++++++++++++++++------------ 3 files changed, 54 insertions(+), 14 deletions(-) diff --git a/Gui/ActionShortcuts.h b/Gui/ActionShortcuts.h index 623b742aa3..04b166646a 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" @@ -573,6 +574,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/GuiApplicationManager10.cpp b/Gui/GuiApplicationManager10.cpp index 4675f9c732..c08e1e36eb 100644 --- a/Gui/GuiApplicationManager10.cpp +++ b/Gui/GuiApplicationManager10.cpp @@ -907,6 +907,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/ScriptEditor.cpp b/Gui/ScriptEditor.cpp index 8f94287c39..d06fa78d49 100644 --- a/Gui/ScriptEditor.cpp +++ b/Gui/ScriptEditor.cpp @@ -45,6 +45,7 @@ #include "Gui/SequenceFileDialog.h" #include "Gui/ScriptTextEdit.h" #include "Gui/Utils.h" +#include "Gui/ActionShortcuts.h" #include "Engine/Settings.h" @@ -127,8 +128,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())); @@ -137,16 +138,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); @@ -175,8 +178,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())); @@ -184,7 +187,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); @@ -198,8 +202,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); @@ -461,14 +465,20 @@ 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); From e7f99aac67454cd8e522e19d878bf4df733b2feb Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Mon, 5 Oct 2015 15:43:27 +0200 Subject: [PATCH 132/178] ScriptEditor: implement auto-scrolling --- Gui/ScriptEditor.cpp | 21 +++++++++++++++++++++ Gui/ScriptEditor.h | 2 ++ Gui/ScriptTextEdit.cpp | 18 ++++++++++++++++++ Gui/ScriptTextEdit.h | 9 +++++++++ 4 files changed, 50 insertions(+) diff --git a/Gui/ScriptEditor.cpp b/Gui/ScriptEditor.cpp index d06fa78d49..8b3778190d 100644 --- a/Gui/ScriptEditor.cpp +++ b/Gui/ScriptEditor.cpp @@ -35,6 +35,7 @@ #include #include #include +#include #include #include "Gui/GuiApplicationManager.h" @@ -76,6 +77,9 @@ struct ScriptEditorPrivate QMutex autoSavedScriptMutex; QString autoSavedScript; + ///Indicate whether we should auto-scroll as results are printed or not + bool outputAtBottom; + ScriptEditorPrivate() : mainLayout(0) , buttonsContainer(0) @@ -94,6 +98,7 @@ struct ScriptEditorPrivate , autoSaveTimer() , autoSavedScriptMutex() , autoSavedScript() + , outputAtBottom(true) { } @@ -224,6 +229,7 @@ ScriptEditor::ScriptEditor(Gui* gui) QSplitter* splitter = new QSplitter(Qt::Vertical,this); _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); @@ -431,6 +437,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(); } @@ -515,6 +524,9 @@ void ScriptEditor::appendToScriptEditor(const QString& str) { _imp->outputEdit->append(str + "\n"); + if (_imp->outputAtBottom) { + _imp->outputEdit->verticalScrollBar()->setValue(_imp->outputEdit->verticalScrollBar()->maximum()); + } } void @@ -526,6 +538,9 @@ 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 @@ -541,3 +556,9 @@ 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 270ddfe12b..37708b8176 100644 --- a/Gui/ScriptEditor.h +++ b/Gui/ScriptEditor.h @@ -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); 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; }; From 484b186d58096b4d6b02553eef4cf04517d3f3ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Devernay?= Date: Mon, 5 Oct 2015 15:50:28 +0200 Subject: [PATCH 133/178] update openfx --- libs/OpenFX | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/OpenFX b/libs/OpenFX index afde4332e9..25b890140d 160000 --- a/libs/OpenFX +++ b/libs/OpenFX @@ -1 +1 @@ -Subproject commit afde4332e95e9763a8c58597cd39c5b740acc0e0 +Subproject commit 25b890140daeb67ccdb05ecd45ea9bd919f2939a From 224f7ea1364fb192589b78bae468e851ed22f978 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Mon, 5 Oct 2015 17:23:00 +0200 Subject: [PATCH 134/178] Better compat with The Foundry Furnace multi-plane plug-ins --- Engine/EffectInstance.cpp | 54 +++++++++++--------- Engine/Node.cpp | 100 ++++++++++++++++++++++++------------- Engine/Node.h | 2 + Engine/OfxClipInstance.cpp | 2 +- Gui/NodeGui.cpp | 21 +++----- 5 files changed, 107 insertions(+), 72 deletions(-) diff --git a/Engine/EffectInstance.cpp b/Engine/EffectInstance.cpp index 4e4704d7a1..468e2856a6 100644 --- a/Engine/EffectInstance.cpp +++ b/Engine/EffectInstance.cpp @@ -2374,6 +2374,10 @@ 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; @@ -3663,32 +3667,36 @@ EffectInstance::getComponentsNeededAndProduced_public(double time, { ImageComponents layer; std::vector compVec; + + //Use regular clip preferences + ImageBitDepthEnum depth; + std::list clipPrefsComps; + getPreferredDepthAndComponents(-1, &clipPrefsComps, &depth); + for (std::list::iterator it = clipPrefsComps.begin(); it != clipPrefsComps.end(); ++it) { + //if (it->isColorPlane()) { + compVec.push_back(*it); + //} + } + + bool ok = getNode()->getUserComponents(-1, processChannels, processAllRequested, &layer); - if (ok) { - if ( !layer.isColorPlane() && (layer.getNumComponents() != 0) ) { - compVec.push_back(layer); - } 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()) { - compVec.push_back(*it); - //} + 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; + } } - } - } 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()) { - compVec.push_back(*it); - //} - } + if (!found) { + compVec.push_back(layer); + } + } } + + comps->insert( std::make_pair(-1, compVec) ); } diff --git a/Engine/Node.cpp b/Engine/Node.cpp index 4096310482..7d19824fed 100644 --- a/Engine/Node.cpp +++ b/Engine/Node.cpp @@ -5947,6 +5947,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 { @@ -7668,15 +7680,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); @@ -7692,7 +7710,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); @@ -7703,44 +7721,44 @@ 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 { @@ -7759,13 +7777,25 @@ Node::refreshChannelSelectors(bool setValues) 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()) { @@ -7781,7 +7811,7 @@ Node::refreshChannelSelectors(bool setValues) _imp->liveInstance->endChanges(true); layerKnob->unblockValueChanges(); if (isColor && it->first == -1 && _imp->enabledChan[0].lock()) { - assert(colorIndex != -1); + 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) { diff --git a/Engine/Node.h b/Engine/Node.h index 40a51deef9..4bd8a1c97e 100644 --- a/Engine/Node.h +++ b/Engine/Node.h @@ -1047,6 +1047,8 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON void markAllInputRelatedDataDirty(); + bool getSelectedLayer(int inputNb,std::string& layer) const; + private: void refreshInputRelatedDataRecursiveInternal(std::list& markedNodes); diff --git a/Engine/OfxClipInstance.cpp b/Engine/OfxClipInstance.cpp index ed263bc123..79ab463124 100644 --- a/Engine/OfxClipInstance.cpp +++ b/Engine/OfxClipInstance.cpp @@ -1083,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) { diff --git a/Gui/NodeGui.cpp b/Gui/NodeGui.cpp index cedacdd5d4..03dec2753a 100644 --- a/Gui/NodeGui.cpp +++ b/Gui/NodeGui.cpp @@ -2663,21 +2663,16 @@ NodeGui::setNameItemHtml(const QString & name, bool hasFontData = true; QString extraLayerStr; - double time = getDagGui()->getGui()->getApp()->getTimeLine()->currentFrame(); - EffectInstance::ComponentsNeededMap neededComps; - bool processAll; - SequenceTime ptTime; - int ptView; - bool processChannels[4]; - NodePtr ptInput; - getNode()->getLiveInstance()->getComponentsNeededAndProduced_public(time, 0, &neededComps, &processAll, &ptTime, &ptView, processChannels, &ptInput); - EffectInstance::ComponentsNeededMap::iterator foundOutput = neededComps.find(-1); - if (foundOutput != neededComps.end() && !foundOutput->second.empty()) { - const Natron::ImageComponents& comp = foundOutput->second.front(); - if (!comp.isColorPlane() && comp.getNumComponents() > 0) { + 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") { extraLayerStr.append("
"); extraLayerStr.push_back('('); - extraLayerStr.append(comp.getLayerName().c_str()); + extraLayerStr.append(selectedLayer.c_str()); extraLayerStr.push_back(')'); } } From c3afc43e1a7db7047cddbe212937d237eb42bdbe Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Mon, 5 Oct 2015 17:27:06 +0200 Subject: [PATCH 135/178] Fix deadlock with allocateImagePlaneAndSetInThreadLocalStorage --- Engine/EffectInstance.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Engine/EffectInstance.cpp b/Engine/EffectInstance.cpp index 468e2856a6..f26da8e45f 100644 --- a/Engine/EffectInstance.cpp +++ b/Engine/EffectInstance.cpp @@ -2381,7 +2381,7 @@ EffectInstance::allocateImagePlaneAndSetInThreadLocalStorage(const Natron::Image 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, 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 { From 0d513e18aa481df5c2e7d6e0c9c3597f5615b203 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Mon, 5 Oct 2015 18:03:24 +0200 Subject: [PATCH 136/178] Bug fix with components --- Engine/EffectInstance.cpp | 52 +++++++++++++++++++----------- Engine/EffectInstance.h | 2 +- Engine/EffectInstanceRenderRoI.cpp | 2 +- Engine/Node.cpp | 4 +-- 4 files changed, 37 insertions(+), 23 deletions(-) diff --git a/Engine/EffectInstance.cpp b/Engine/EffectInstance.cpp index f26da8e45f..7015e5f4c5 100644 --- a/Engine/EffectInstance.cpp +++ b/Engine/EffectInstance.cpp @@ -545,7 +545,7 @@ 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, @@ -748,9 +748,9 @@ EffectInstance::getImage(int inputNb, assert(attachedStroke); if (attachedStroke) { if (duringPaintStroke) { - inputImg = getNode()->getOrRenderLastStrokeImage(mipMapLevel, pixelRoI, par, comp, depth); + inputImg = getNode()->getOrRenderLastStrokeImage(mipMapLevel, pixelRoI, par, requestComps, depth); } else { - inputImg = roto->renderMaskFromStroke(attachedStroke, pixelRoI, comp, + inputImg = roto->renderMaskFromStroke(attachedStroke, pixelRoI, requestComps, time, view, depth, mipMapLevel); if ( roto->isDoingNeatRender() ) { getNode()->updateStrokeImage(inputImg); @@ -783,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, @@ -822,12 +822,12 @@ EffectInstance::getImage(int inputNb, #ifdef DEBUG ///Check that the rendered image contains what we requested. - if ((!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 = comp; + 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(); @@ -868,15 +868,19 @@ EffectInstance::getImage(int inputNb, //Remap if needed ImagePremultiplicationEnum outputPremult; - if (comp.isColorPlane()) { + if (requestComps.isColorPlane()) { outputPremult = n->getOutputPremultiplication(); } else { outputPremult = eImagePremultiplicationOpaque; } - inputImg = convertPlanesFormatsIfNeeded(getApp(), inputImg, pixelRoI, comp, depth, getNode()->usesAlpha0ToConvertFromRGBToRGBA(), outputPremult); + std::list prefComps; + ImageBitDepthEnum prefDepth; + getPreferredDepthAndComponents(inputNb, &prefComps, &prefDepth); + assert(!prefComps.empty()); + inputImg = convertPlanesFormatsIfNeeded(getApp(), inputImg, pixelRoI, prefComps.front(), prefDepth, getNode()->usesAlpha0ToConvertFromRGBToRGBA(), outputPremult); if (inputImagesThreadLocal.empty()) { ///If the effect is analysis (e.g: Tracker) there's no input images in the tread local storage, hence add it @@ -3667,19 +3671,11 @@ EffectInstance::getComponentsNeededAndProduced_public(double time, { ImageComponents layer; std::vector compVec; - - //Use regular clip preferences + bool ok = getNode()->getUserComponents(-1, processChannels, processAllRequested, &layer); ImageBitDepthEnum depth; std::list clipPrefsComps; getPreferredDepthAndComponents(-1, &clipPrefsComps, &depth); - for (std::list::iterator it = clipPrefsComps.begin(); it != clipPrefsComps.end(); ++it) { - //if (it->isColorPlane()) { - compVec.push_back(*it); - //} - } - - - bool ok = getNode()->getUserComponents(-1, processChannels, processAllRequested, &layer); + if (ok && layer.getNumComponents() != 0) { if (!layer.isColorPlane()) { @@ -3693,8 +3689,26 @@ EffectInstance::getComponentsNeededAndProduced_public(double time, 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 { + for (std::list::iterator it = clipPrefsComps.begin(); it != clipPrefsComps.end(); ++it) { + compVec.push_back(*it); + } + } + + } else { + for (std::list::iterator it = clipPrefsComps.begin(); it != clipPrefsComps.end(); ++it) { + compVec.push_back(*it); + } } + + + + comps->insert( std::make_pair(-1, compVec) ); diff --git a/Engine/EffectInstance.h b/Engine/EffectInstance.h index ee9cd4695d..89f6563f1f 100644 --- a/Engine/EffectInstance.h +++ b/Engine/EffectInstance.h @@ -903,7 +903,7 @@ 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, diff --git a/Engine/EffectInstanceRenderRoI.cpp b/Engine/EffectInstanceRenderRoI.cpp index 19281f9991..816a066c6d 100644 --- a/Engine/EffectInstanceRenderRoI.cpp +++ b/Engine/EffectInstanceRenderRoI.cpp @@ -194,7 +194,7 @@ EffectInstance::convertPlanesFormatsIfNeeded(const AppInstance* app, bool useAlpha0ForRGBToRGBAConversion, ImagePremultiplicationEnum outputPremult) { - bool imageConversionNeeded = targetComponents != inputImage->getComponents() || targetDepth != inputImage->getBitDepth(); + bool imageConversionNeeded = targetComponents.getNumComponents() != inputImage->getComponents().getNumComponents() || targetDepth != inputImage->getBitDepth(); if (!imageConversionNeeded) { return inputImage; } else { diff --git a/Engine/Node.cpp b/Engine/Node.cpp index 7d19824fed..0d87c2b625 100644 --- a/Engine/Node.cpp +++ b/Engine/Node.cpp @@ -5304,7 +5304,7 @@ Node::findClosestInList(const Natron::ImageComponents& comp, } else { int diff = it->getNumComponents() - comp.getNumComponents(); int diffSoFar = closestComp->getNumComponents() - comp.getNumComponents(); - if (diff > diffSoFar && diffSoFar != 0) { + if (diff > 0 && diff < diffSoFar) { closestComp = it; } } @@ -6040,7 +6040,7 @@ Node::Implementation::onLayerChanged(int inputNb,const ChannelSelector& selector ///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(inputNb); + boost::shared_ptr inp = _publicInterface->getInput(it->first); bool mustBeSecret = !inp.get() || outputIsAll; it->second.layer.lock()->setSecret(mustBeSecret); } From 69648fe352e176f0eec98de78ea8d6518bb04e32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole-Andr=C3=A9=20Rodlie?= Date: Tue, 6 Oct 2015 06:37:53 +0200 Subject: [PATCH 137/178] Linux: check for libstdc++ on startup --- tools/linux/README.md | 22 ------------ tools/linux/common.sh | 2 +- tools/linux/include/scripts/Natron.sh | 36 +++++++++++++++++++ .../linux/include/scripts/build-installer.sh | 8 +++++ 4 files changed, 45 insertions(+), 23 deletions(-) diff --git a/tools/linux/README.md b/tools/linux/README.md index f1e28e359e..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. diff --git a/tools/linux/common.sh b/tools/linux/common.sh index dc0dc516b2..7635f43a78 100644 --- a/tools/linux/common.sh +++ b/tools/linux/common.sh @@ -50,7 +50,7 @@ GIT_OCIO_CONFIG_TAR=https://github.com/MrKepzie/OpenColorIO-Configs/archive/Natr COLOR_PROFILES_VERSION=2.0.0 # bump timestamp on SDK changes, important! -CORELIBS_VERSION=20150928 +CORELIBS_VERSION=20151006 # SDK # diff --git a/tools/linux/include/scripts/Natron.sh b/tools/linux/include/scripts/Natron.sh index 9cffaf739c..9fc26224aa 100644 --- a/tools/linux/include/scripts/Natron.sh +++ b/tools/linux/include/scripts/Natron.sh @@ -23,6 +23,42 @@ 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 +fi +COMPAT_GCC=`$DIR/bin/strings $STDC_LIB | grep GLIBCXX_${COMPAT_VERSION}` +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 diff --git a/tools/linux/include/scripts/build-installer.sh b/tools/linux/include/scripts/build-installer.sh index 53b236eb00..d8a34e71db 100644 --- a/tools/linux/include/scripts/build-installer.sh +++ b/tools/linux/include/scripts/build-installer.sh @@ -247,6 +247,14 @@ 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 From 2a358fb60c9005d0d6eee5b6ea0360c8e78e7a74 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Tue, 6 Oct 2015 10:14:35 +0200 Subject: [PATCH 138/178] Look for plugins in NATRON_PLUGIN_PATH recursively --- Engine/AppInstance.cpp | 15 ++-- Engine/AppInstance.h | 6 +- Engine/AppManager.cpp | 159 +++++++++++++++++++++++++---------- Engine/AppManagerPrivate.cpp | 1 - Engine/Node.cpp | 2 +- Gui/GuiAppInstance.cpp | 20 ++--- 6 files changed, 130 insertions(+), 73 deletions(-) diff --git a/Engine/AppInstance.cpp b/Engine/AppInstance.cpp index e937ad2117..e6f10c266d 100644 --- a/Engine/AppInstance.cpp +++ b/Engine/AppInstance.cpp @@ -131,11 +131,6 @@ AppInstance::AppInstance(int appID) AppInstance::~AppInstance() { - ///Clear nodes now, not in the destructor of the project as - ///deleting nodes might reference the project. - _imp->_currentProject->closeProject(true); - _imp->_currentProject->discardAppPointer(); - appPTR->removeInstance(_imp->_appID); } @@ -1239,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() diff --git a/Engine/AppInstance.h b/Engine/AppInstance.h index c73a188a75..3b9bab0d68 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; diff --git a/Engine/AppManager.cpp b/Engine/AppManager.cpp index 15c3691af1..068e39144a 100644 --- a/Engine/AppManager.cpp +++ b/Engine/AppManager.cpp @@ -239,6 +239,8 @@ AppManager::~AppManager() delete qApp; } + + void AppManager::quit(AppInstance* instance) { @@ -945,7 +947,7 @@ AppManager::loadBuiltinNodePlugins(std::mapwriteToOfxLog_mt_safe(message); std::cerr << message.toStdString() << std::endl; + return false; } - + return true; } break; } } + return false; } QString @@ -1055,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() { @@ -1065,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 @@ -1142,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); } } 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/Node.cpp b/Engine/Node.cpp index 0d87c2b625..66e011a44c 100644 --- a/Engine/Node.cpp +++ b/Engine/Node.cpp @@ -5505,7 +5505,7 @@ Node::onInputChanged(int inputNb) isViewer->refreshActiveInputs(inputNb); } - bool shouldDoInputChanged = (!getApp()->getProject()->isLoadingProject() && !getApp()->isCreatingPythonGroup()) || + bool shouldDoInputChanged = (!getApp()->getProject()->isProjectClosing() && !getApp()->getProject()->isLoadingProject() && !getApp()->isCreatingPythonGroup()) || _imp->liveInstance->isRotoPaintNode(); if (shouldDoInputChanged) { diff --git a/Gui/GuiAppInstance.cpp b/Gui/GuiAppInstance.cpp index 9d0fe3b6e1..87781b864a 100644 --- a/Gui/GuiAppInstance.cpp +++ b/Gui/GuiAppInstance.cpp @@ -162,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()->notifyGuiClosingPublic(); - _imp->_gui->deleteLater(); - _imp->_gui = 0; - _imp.reset(); -//#endif - QCoreApplication::processEvents(); } bool From 7dc9e968eb3cc20a5205f189c6142e302084f0b1 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Tue, 6 Oct 2015 10:15:52 +0200 Subject: [PATCH 139/178] Remove bracket --- Engine/AppManager.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Engine/AppManager.cpp b/Engine/AppManager.cpp index 068e39144a..79c31c262c 100644 --- a/Engine/AppManager.cpp +++ b/Engine/AppManager.cpp @@ -1179,11 +1179,11 @@ AppManager::loadPythonGroups() findAllScriptsRecursive(d,allPlugins,&foundInit, &foundInitGui); } if (!foundInit) { - QString message = QObject::tr("> init.py script not loaded"); + 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"); + QString message = QObject::tr("init.py script loaded"); appPTR->setLoadingStatus(message); std::cout << message.toStdString() << std::endl; } @@ -1191,12 +1191,12 @@ AppManager::loadPythonGroups() if (!appPTR->isBackground()) { if (!foundInitGui) { - QString message = QObject::tr("> initGui.py script not loaded"); + QString message = QObject::tr("initGui.py script not loaded"); appPTR->setLoadingStatus(message); std::cout << message.toStdString() << std::endl; } else { - QString message = QObject::tr("> initGui.py script loaded"); + QString message = QObject::tr("initGui.py script loaded"); appPTR->setLoadingStatus(message); std::cout << message.toStdString() << std::endl; } From 21a45078901cdf28774667d62bb9991eeb3105ac Mon Sep 17 00:00:00 2001 From: MrKepzie Date: Tue, 6 Oct 2015 10:32:56 +0200 Subject: [PATCH 140/178] Fix include --- Engine/EffectInstanceRenderRoI.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Engine/EffectInstanceRenderRoI.cpp b/Engine/EffectInstanceRenderRoI.cpp index 816a066c6d..a325b2352d 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" From e6bc60e9ee8b6dc043b9dd3c867ff23c1e679e2d Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Tue, 6 Oct 2015 11:15:13 +0200 Subject: [PATCH 141/178] Timer: make sure time since last frame is not negatve --- Engine/Timer.cpp | 3 +++ 1 file changed, 3 insertions(+) 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 From fe760fa5a1aeba4615daed0d60f8b7ad784acf0e Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Tue, 6 Oct 2015 11:37:38 +0200 Subject: [PATCH 142/178] Make sure buttons are checked --- Gui/RotoGui.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Gui/RotoGui.cpp b/Gui/RotoGui.cpp index 4dfb4e1f3e..121742bddd 100644 --- a/Gui/RotoGui.cpp +++ b/Gui/RotoGui.cpp @@ -4806,6 +4806,7 @@ void RotoGui::onPressureOpacityClicked(bool isDown) { _imp->pressureOpacityButton->setDown(isDown); + _imp->pressureOpacityButton->setChecked(isDown); onBreakMultiStrokeTriggered(); } @@ -4813,6 +4814,7 @@ void RotoGui::onPressureSizeClicked(bool isDown) { _imp->pressureSizeButton->setDown(isDown); + _imp->pressureSizeButton->setChecked(isDown); onBreakMultiStrokeTriggered(); } @@ -4820,6 +4822,7 @@ void RotoGui::onPressureHardnessClicked(bool isDown) { _imp->pressureHardnessButton->setDown(isDown); + _imp->pressureHardnessButton->setChecked(isDown); onBreakMultiStrokeTriggered(); } From 32125f7ce1e52b2d6670b20ebe1766ed58ccc429 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Tue, 6 Oct 2015 11:56:06 +0200 Subject: [PATCH 143/178] Send instanceChange action when pages are made active by the user and group checked/unchecked --- Engine/EffectInstance.cpp | 11 +++++++++-- Gui/DockablePanel.cpp | 10 ++++++++-- Gui/KnobGuiGroup.cpp | 25 +++++++++++++++++++------ Gui/KnobGuiGroup.h | 3 +++ 4 files changed, 39 insertions(+), 10 deletions(-) diff --git a/Engine/EffectInstance.cpp b/Engine/EffectInstance.cpp index 7015e5f4c5..640d9061c9 100644 --- a/Engine/EffectInstance.cpp +++ b/Engine/EffectInstance.cpp @@ -2453,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. @@ -2465,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)*/ diff --git a/Gui/DockablePanel.cpp b/Gui/DockablePanel.cpp index f69aa21057..5541478ac5 100644 --- a/Gui/DockablePanel.cpp +++ b/Gui/DockablePanel.cpp @@ -479,9 +479,15 @@ DockablePanel::onPageIndexChanged(int index) continue; } if (isPage->getDescription() == stdName) { - isPage->setSecret(false); + if (isPage->getIsSecret()) { + isPage->evaluateValueChange(0, isPage->getCurrentTime(), Natron::eValueChangedReasonUserEdited); + isPage->setSecret(false); + } } else { - isPage->setSecret(true); + if (!isPage->getIsSecret()) { + isPage->evaluateValueChange(0, isPage->getCurrentTime(), Natron::eValueChangedReasonUserEdited); + isPage->setSecret(true); + } } } Natron::EffectInstance* isEffect = dynamic_cast(_imp->_holder); 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; From 9dc017525498f5556afc2cd632d9bc59a204b61b Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Tue, 6 Oct 2015 12:17:02 +0200 Subject: [PATCH 144/178] Evaluate after setSecret --- Gui/DockablePanel.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gui/DockablePanel.cpp b/Gui/DockablePanel.cpp index 5541478ac5..80f955bd5b 100644 --- a/Gui/DockablePanel.cpp +++ b/Gui/DockablePanel.cpp @@ -480,13 +480,13 @@ DockablePanel::onPageIndexChanged(int index) } if (isPage->getDescription() == stdName) { if (isPage->getIsSecret()) { - isPage->evaluateValueChange(0, isPage->getCurrentTime(), Natron::eValueChangedReasonUserEdited); isPage->setSecret(false); + isPage->evaluateValueChange(0, isPage->getCurrentTime(), Natron::eValueChangedReasonUserEdited); } } else { if (!isPage->getIsSecret()) { - isPage->evaluateValueChange(0, isPage->getCurrentTime(), Natron::eValueChangedReasonUserEdited); isPage->setSecret(true); + isPage->evaluateValueChange(0, isPage->getCurrentTime(), Natron::eValueChangedReasonUserEdited); } } } From a1ec77ad0514ce24a7cda6898fa49a363b7dce62 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Tue, 6 Oct 2015 12:21:44 +0200 Subject: [PATCH 145/178] Fix bbox computation --- Engine/RotoStrokeItem.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Engine/RotoStrokeItem.cpp b/Engine/RotoStrokeItem.cpp index 8d771ba10e..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; } } From 7c207279f6379e31cb0fe44706cc3e47e3b4d2f6 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Tue, 6 Oct 2015 12:43:43 +0200 Subject: [PATCH 146/178] While dragging interacts, use draft mode --- Gui/NodeGui.cpp | 3 ++- Gui/ViewerTab20.cpp | 39 +++++++++++++++++++++++++++++++++++---- Gui/ViewerTabPrivate.cpp | 2 ++ Gui/ViewerTabPrivate.h | 2 ++ 4 files changed, 41 insertions(+), 5 deletions(-) diff --git a/Gui/NodeGui.cpp b/Gui/NodeGui.cpp index 03dec2753a..7052130ca9 100644 --- a/Gui/NodeGui.cpp +++ b/Gui/NodeGui.cpp @@ -2669,7 +2669,8 @@ NodeGui::setNameItemHtml(const QString & name, if (selectedLayer != ImageComponents::getRGBAComponents().getComponentsGlobalName() && selectedLayer != ImageComponents::getRGBComponents().getComponentsGlobalName() && selectedLayer != ImageComponents::getAlphaComponents().getComponentsGlobalName() && - selectedLayer != "None") { + selectedLayer != "None" && + selectedLayer != "All") { extraLayerStr.append("
"); extraLayerStr.push_back('('); extraLayerStr.append(selectedLayer.c_str()); diff --git a/Gui/ViewerTab20.cpp b/Gui/ViewerTab20.cpp index c85c30cae7..79e4032bae 100644 --- a/Gui/ViewerTab20.cpp +++ b/Gui/ViewerTab20.cpp @@ -264,11 +264,14 @@ ViewerTab::notifyOverlaysPenDown(double scaleX, double timestamp, QMouseEvent* e) { - + if ( !getGui()->getApp() || getGui()->getApp()->isClosing() ) { return false; } - + + _imp->hasPenDown = true; + _imp->hasCaughtPenMotionWhileDragging = false; + std::list > nodes; getGui()->getNodesEntitledForOverlays(nodes); @@ -463,11 +466,21 @@ ViewerTab::notifyOverlaysPenMotion_internal(const boost::shared_ptrisDraftRenderEnabled()) { + 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 @@ -544,6 +557,17 @@ ViewerTab::notifyOverlaysPenUp(double scaleX, 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; @@ -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(); + } + + + return didSomething; } diff --git a/Gui/ViewerTabPrivate.cpp b/Gui/ViewerTabPrivate.cpp index c807ea7755..6200ce16d9 100644 --- a/Gui/ViewerTabPrivate.cpp +++ b/Gui/ViewerTabPrivate.cpp @@ -144,6 +144,8 @@ ViewerTabPrivate::ViewerTabPrivate(ViewerTab* publicInterface,ViewerInstance* no , fpsMutex() , fps(24.) , lastOverlayNode() +, hasPenDown(false) +, hasCaughtPenMotionWhileDragging(false) { infoWidget[0] = infoWidget[1] = NULL; currentRoto.first = NULL; diff --git a/Gui/ViewerTabPrivate.h b/Gui/ViewerTabPrivate.h index d7e6187a18..7181af1c54 100644 --- a/Gui/ViewerTabPrivate.h +++ b/Gui/ViewerTabPrivate.h @@ -205,6 +205,8 @@ struct ViewerTabPrivate //The last node that took the penDown/motion/keyDown/keyRelease etc... boost::weak_ptr lastOverlayNode; + bool hasPenDown; + bool hasCaughtPenMotionWhileDragging; ViewerTabPrivate(ViewerTab* publicInterface,ViewerInstance* node); From 9c6cca4805d395c82884e9514242cf216d921f66 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Tue, 6 Oct 2015 14:38:53 +0200 Subject: [PATCH 147/178] Fix draft mode above 100% zoom factor --- Engine/AppInstance.h | 2 +- Engine/AppManager.cpp | 2 +- Engine/ViewerInstance.cpp | 11 ++++++++++- Gui/CurveWidget.cpp | 2 +- Gui/DopeSheetView.cpp | 2 +- Gui/Gui.h | 2 +- Gui/Gui50.cpp | 4 ++-- Gui/GuiAppInstance.cpp | 4 ++-- Gui/GuiAppInstance.h | 2 +- Gui/KnobGuiColor.cpp | 2 +- Gui/KnobGuiDouble.cpp | 2 +- Gui/KnobGuiInt.cpp | 2 +- Gui/TimeLineGui.cpp | 2 +- Gui/ViewerTab20.cpp | 4 ++-- Gui/ViewerTab40.cpp | 4 ++-- 15 files changed, 28 insertions(+), 19 deletions(-) diff --git a/Engine/AppInstance.h b/Engine/AppInstance.h index 3b9bab0d68..380d6c1305 100644 --- a/Engine/AppInstance.h +++ b/Engine/AppInstance.h @@ -371,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(); diff --git a/Engine/AppManager.cpp b/Engine/AppManager.cpp index 79c31c262c..9c6a966c57 100644 --- a/Engine/AppManager.cpp +++ b/Engine/AppManager.cpp @@ -584,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(); diff --git a/Engine/ViewerInstance.cpp b/Engine/ViewerInstance.cpp index dfd7326312..7fb9cbc1b6 100644 --- a/Engine/ViewerInstance.cpp +++ b/Engine/ViewerInstance.cpp @@ -798,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); } diff --git a/Gui/CurveWidget.cpp b/Gui/CurveWidget.cpp index dc9b7a46a0..d838ccb4fc 100644 --- a/Gui/CurveWidget.cpp +++ b/Gui/CurveWidget.cpp @@ -936,7 +936,7 @@ CurveWidget::mouseReleaseEvent(QMouseEvent*) _imp->_gui->setDraftRenderEnabled(false); bool autoProxyEnabled = appPTR->getCurrentSettings()->isAutoProxyEnabled(); if (autoProxyEnabled) { - _imp->_gui->renderAllViewers(); + _imp->_gui->renderAllViewers(true); } } } diff --git a/Gui/DopeSheetView.cpp b/Gui/DopeSheetView.cpp index 2cf66dd34e..11105b3791 100644 --- a/Gui/DopeSheetView.cpp +++ b/Gui/DopeSheetView.cpp @@ -3328,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); } } } diff --git a/Gui/Gui.h b/Gui/Gui.h index 6d6551021a..a28708ac71 100644 --- a/Gui/Gui.h +++ b/Gui/Gui.h @@ -531,7 +531,7 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON void redrawAllViewers(); - void renderAllViewers(); + void renderAllViewers(bool canAbort); void toggleAutoHideGraphInputs(); diff --git a/Gui/Gui50.cpp b/Gui/Gui50.cpp index 2d7152f476..268fe230e2 100644 --- a/Gui/Gui50.cpp +++ b/Gui/Gui50.cpp @@ -664,12 +664,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); } } } diff --git a/Gui/GuiAppInstance.cpp b/Gui/GuiAppInstance.cpp index 87781b864a..05731c957d 100644 --- a/Gui/GuiAppInstance.cpp +++ b/Gui/GuiAppInstance.cpp @@ -1029,9 +1029,9 @@ GuiAppInstance::closeLoadPRojectSplashScreen() } void -GuiAppInstance::renderAllViewers() +GuiAppInstance::renderAllViewers(bool canAbort) { - _imp->_gui->renderAllViewers(); + _imp->_gui->renderAllViewers(canAbort); } void diff --git a/Gui/GuiAppInstance.h b/Gui/GuiAppInstance.h index 9696f962a4..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; diff --git a/Gui/KnobGuiColor.cpp b/Gui/KnobGuiColor.cpp index eb504ff148..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); } } 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/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/TimeLineGui.cpp b/Gui/TimeLineGui.cpp index 52b80815b2..689e37f44c 100644 --- a/Gui/TimeLineGui.cpp +++ b/Gui/TimeLineGui.cpp @@ -863,7 +863,7 @@ TimeLineGui::mouseReleaseEvent(QMouseEvent* e) } } else if (autoProxyEnabled && wasScrubbing) { - _imp->gui->getApp()->renderAllViewers(); + _imp->gui->getApp()->renderAllViewers(true); } } diff --git a/Gui/ViewerTab20.cpp b/Gui/ViewerTab20.cpp index 79e4032bae..ab1eb14502 100644 --- a/Gui/ViewerTab20.cpp +++ b/Gui/ViewerTab20.cpp @@ -467,7 +467,7 @@ ViewerTab::notifyOverlaysPenMotion_internal(const boost::shared_ptrisDraftRenderEnabled()) { + if (_imp->hasPenDown && !getGui()->isDraftRenderEnabled()) { getGui()->setDraftRenderEnabled(true); } @@ -660,7 +660,7 @@ ViewerTab::notifyOverlaysPenUp(double scaleX, if (mustTriggerRender) { //We had draft enabled but penRelease didn't trigger any render, trigger one to refresh the viewer - getGui()->getApp()->renderAllViewers(); + getGui()->getApp()->renderAllViewers(true); } diff --git a/Gui/ViewerTab40.cpp b/Gui/ViewerTab40.cpp index da0c56049f..48fc7c83fa 100644 --- a/Gui/ViewerTab40.cpp +++ b/Gui/ViewerTab40.cpp @@ -1104,7 +1104,7 @@ ViewerTab::onGammaSliderEditingFinished(bool hasMovedOnce) { bool autoProxyEnabled = appPTR->getCurrentSettings()->isAutoProxyEnabled(); if (autoProxyEnabled && hasMovedOnce) { - getGui()->renderAllViewers(); + getGui()->renderAllViewers(true); } } @@ -1113,7 +1113,7 @@ ViewerTab::onGainSliderEditingFinished(bool hasMovedOnce) { bool autoProxyEnabled = appPTR->getCurrentSettings()->isAutoProxyEnabled(); if (autoProxyEnabled && hasMovedOnce) { - getGui()->renderAllViewers(); + getGui()->renderAllViewers(true); } } From 4cb9b0db025f5fdb00b1701ccd0594c411921f07 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Tue, 6 Oct 2015 15:06:15 +0200 Subject: [PATCH 148/178] Small fix with track link serialisation --- Engine/KnobTypes.cpp | 9 +-------- Engine/NodeGroupSerialization.cpp | 10 +++++++++- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/Engine/KnobTypes.cpp b/Engine/KnobTypes.cpp index a4d0c8f9bb..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; diff --git a/Engine/NodeGroupSerialization.cpp b/Engine/NodeGroupSerialization.cpp index 20d028791f..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(); @@ -269,13 +271,19 @@ 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); + } + group->getApplication()->updateProjectLoadStatus(QObject::tr("Restoring graph links in group: ") + groupName); From d443841d5a1c8df972cd96bb7e8c5b89b6188c39 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Tue, 6 Oct 2015 15:25:19 +0200 Subject: [PATCH 149/178] Fix dialog --- Engine/EffectInstance.cpp | 4 ++-- Engine/Project.cpp | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Engine/EffectInstance.cpp b/Engine/EffectInstance.cpp index 640d9061c9..1cac7dff2d 100644 --- a/Engine/EffectInstance.cpp +++ b/Engine/EffectInstance.cpp @@ -2488,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) { diff --git a/Engine/Project.cpp b/Engine/Project.cpp index 173b3e7810..6049e32af8 100644 --- a/Engine/Project.cpp +++ b/Engine/Project.cpp @@ -778,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); From bd0b69ab8e70e8aa827b8caf4a6fdb537d9383c7 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Tue, 6 Oct 2015 15:57:47 +0200 Subject: [PATCH 150/178] Viewer view switch shortcuts are now in shortcut editor. Also Modifiers keyDown events are now sent to all active interacts so they can properly figure out which modifiers are active --- Gui/ActionShortcuts.h | 6 +++++ Gui/GuiApplicationManager10.cpp | 5 ++++ Gui/ViewerGL.cpp | 16 ------------ Gui/ViewerGL.h | 1 - Gui/ViewerTab.h | 1 + Gui/ViewerTab10.cpp | 45 ++++++++++++++++++++++++++++----- Gui/ViewerTab20.cpp | 21 +++++++++++++-- 7 files changed, 69 insertions(+), 26 deletions(-) diff --git a/Gui/ActionShortcuts.h b/Gui/ActionShortcuts.h index 04b166646a..7949cbe158 100644 --- a/Gui/ActionShortcuts.h +++ b/Gui/ActionShortcuts.h @@ -301,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" diff --git a/Gui/GuiApplicationManager10.cpp b/Gui/GuiApplicationManager10.cpp index c08e1e36eb..472fec0f0b 100644 --- a/Gui/GuiApplicationManager10.cpp +++ b/Gui/GuiApplicationManager10.cpp @@ -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); diff --git a/Gui/ViewerGL.cpp b/Gui/ViewerGL.cpp index 3355e61e82..f30ab40bf4 100644 --- a/Gui/ViewerGL.cpp +++ b/Gui/ViewerGL.cpp @@ -2879,22 +2879,6 @@ ViewerGL::resizeEvent(QResizeEvent* 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 { diff --git a/Gui/ViewerGL.h b/Gui/ViewerGL.h index bdd221b2fc..6fa32b6353 100644 --- a/Gui/ViewerGL.h +++ b/Gui/ViewerGL.h @@ -424,7 +424,6 @@ public Q_SLOTS: virtual void focusInEvent(QFocusEvent* e) OVERRIDE FINAL; virtual void focusOutEvent(QFocusEvent* e) OVERRIDE FINAL; virtual void leaveEvent(QEvent* e) OVERRIDE FINAL; - virtual void keyReleaseEvent(QKeyEvent* e) OVERRIDE FINAL; virtual void tabletEvent(QTabletEvent* e) OVERRIDE FINAL; /** diff --git a/Gui/ViewerTab.h b/Gui/ViewerTab.h index 90d1a7e4be..cbeb2fbce1 100644 --- a/Gui/ViewerTab.h +++ b/Gui/ViewerTab.h @@ -448,6 +448,7 @@ 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; diff --git a/Gui/ViewerTab10.cpp b/Gui/ViewerTab10.cpp index 12e2cced8c..7c0ae1284d 100644 --- a/Gui/ViewerTab10.cpp +++ b/Gui/ViewerTab10.cpp @@ -107,16 +107,25 @@ 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); @@ -711,6 +720,10 @@ ViewerTab::keyPressEvent(QKeyEvent* e) } 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); @@ -721,6 +734,24 @@ ViewerTab::keyPressEvent(QKeyEvent* e) } } // 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) { diff --git a/Gui/ViewerTab20.cpp b/Gui/ViewerTab20.cpp index ab1eb14502..7ddaba02ff 100644 --- a/Gui/ViewerTab20.cpp +++ b/Gui/ViewerTab20.cpp @@ -737,7 +737,17 @@ ViewerTab::notifyOverlaysKeyDown(double scaleX, 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; + + Natron::KeyboardModifiers natronMod = QtEnumConvert::fromQtModifiers( e->modifiers() ); std::list > nodes; @@ -748,6 +758,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); @@ -758,10 +772,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; } } @@ -786,8 +804,7 @@ ViewerTab::notifyOverlaysKeyUp(double scaleX, } _imp->lastOverlayNode.reset(); - - + double time = getGui()->getApp()->getTimeLine()->currentFrame(); std::list > nodes; From 5a869b4118f03016f634fcf58adcfaa401c13db6 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Tue, 6 Oct 2015 16:29:22 +0200 Subject: [PATCH 151/178] Few fixed with events --- Gui/CurveEditor.cpp | 2 ++ Gui/CurveWidget.cpp | 22 ++++++++++++++-------- Gui/DopeSheetEditor.cpp | 2 ++ Gui/Gui.h | 2 ++ Gui/Gui50.cpp | 8 +++++++- Gui/GuiPrivate.cpp | 1 + Gui/GuiPrivate.h | 1 + Gui/Histogram.cpp | 2 ++ Gui/NodeGraph25.cpp | 2 ++ Gui/PanelWidget.cpp | 19 +++++++++++++++++++ Gui/PanelWidget.h | 9 +++++++++ Gui/PropertiesBinWrapper.cpp | 7 +++++++ Gui/PropertiesBinWrapper.h | 1 + Gui/ScriptEditor.cpp | 2 ++ Gui/ViewerTab10.cpp | 2 ++ Gui/ViewerTab20.cpp | 8 ++++++++ 16 files changed, 81 insertions(+), 9 deletions(-) diff --git a/Gui/CurveEditor.cpp b/Gui/CurveEditor.cpp index c2d996090d..b3eed471ee 100644 --- a/Gui/CurveEditor.cpp +++ b/Gui/CurveEditor.cpp @@ -1547,6 +1547,8 @@ CurveEditor::keyPressEvent(QKeyEvent* e) if (accept) { takeClickFocus(); e->accept(); + } else { + handleUnCaughtKeyPressEvent(); } } diff --git a/Gui/CurveWidget.cpp b/Gui/CurveWidget.cpp index d838ccb4fc..afce3ead43 100644 --- a/Gui/CurveWidget.cpp +++ b/Gui/CurveWidget.cpp @@ -1339,21 +1339,27 @@ CurveWidget::keyPressEvent(QKeyEvent* e) accept = false; QGLWidget::keyPressEvent(e); } - if (accept) { - CurveEditor* ce = 0; - if ( parentWidget() ) { - QWidget* parent = parentWidget()->parentWidget(); - if (parent) { - if (parent->objectName() == "CurveEditor") { - ce = dynamic_cast(parent); - } + + 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 diff --git a/Gui/DopeSheetEditor.cpp b/Gui/DopeSheetEditor.cpp index 68fa069486..044b241ce0 100644 --- a/Gui/DopeSheetEditor.cpp +++ b/Gui/DopeSheetEditor.cpp @@ -235,6 +235,8 @@ DopeSheetEditor::keyPressEvent(QKeyEvent* e) if (accept) { takeClickFocus(); e->accept(); + } else { + handleUnCaughtKeyPressEvent(); } } diff --git a/Gui/Gui.h b/Gui/Gui.h index a28708ac71..e5c8f0f825 100644 --- a/Gui/Gui.h +++ b/Gui/Gui.h @@ -597,6 +597,8 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON static bool isFocusStealingPossible(); PanelWidget* getCurrentPanelFocus() const; + + void setLastKeyPressVisitedClickFocus(bool visited); Q_SIGNALS: diff --git a/Gui/Gui50.cpp b/Gui/Gui50.cpp index 268fe230e2..625df26fff 100644 --- a/Gui/Gui50.cpp +++ b/Gui/Gui50.cpp @@ -455,6 +455,12 @@ static RightClickableWidget* isParentSettingsPanelRecursive(QWidget* w) } } +void +Gui::setLastKeyPressVisitedClickFocus(bool visited) +{ + _imp->keyPressEventHasVisitedFocusWidget = visited; +} + void Gui::keyPressEvent(QKeyEvent* e) { @@ -539,7 +545,7 @@ Gui::keyPressEvent(QKeyEvent* e) } else if (isKeybind(kShortcutGroupGlobal, kShortcutIDActionConnectViewerToInput10, modifiers, key) ) { connectInput(9); } else { - if (_imp->currentPanelFocus) { + if (_imp->currentPanelFocus && !_imp->keyPressEventHasVisitedFocusWidget) { ++_imp->currentPanelFocusEventRecursion; //If a panel as the click focus, try to send the event to it diff --git a/Gui/GuiPrivate.cpp b/Gui/GuiPrivate.cpp index 2b5e48639e..4407dc2214 100644 --- a/Gui/GuiPrivate.cpp +++ b/Gui/GuiPrivate.cpp @@ -250,6 +250,7 @@ GuiPrivate::GuiPrivate(GuiAppInstance* app, , statsDialog(0) , currentPanelFocus(0) , currentPanelFocusEventRecursion(0) +, keyPressEventHasVisitedFocusWidget(false) , wasLaskUserSeekDuringPlayback(false) { } diff --git a/Gui/GuiPrivate.h b/Gui/GuiPrivate.h index cb06041135..eaa706daad 100644 --- a/Gui/GuiPrivate.h +++ b/Gui/GuiPrivate.h @@ -270,6 +270,7 @@ struct GuiPrivate //To prevent recursion when we forward an uncaught event to the click focus widget int currentPanelFocusEventRecursion; + bool keyPressEventHasVisitedFocusWidget; bool wasLaskUserSeekDuringPlayback; diff --git a/Gui/Histogram.cpp b/Gui/Histogram.cpp index 6a4d199037..86e9b7dd34 100644 --- a/Gui/Histogram.cpp +++ b/Gui/Histogram.cpp @@ -1442,6 +1442,8 @@ Histogram::keyPressEvent(QKeyEvent* e) if (accept) { takeClickFocus(); e->accept(); + } else { + handleUnCaughtKeyPressEvent(); } } diff --git a/Gui/NodeGraph25.cpp b/Gui/NodeGraph25.cpp index c2aefbf9c1..c62e2ebc6d 100644 --- a/Gui/NodeGraph25.cpp +++ b/Gui/NodeGraph25.cpp @@ -442,6 +442,8 @@ NodeGraph::keyPressEvent(QKeyEvent* e) if (accept) { takeClickFocus(); e->accept(); + } else { + handleUnCaughtKeyPressEvent(); } } // keyPressEvent diff --git a/Gui/PanelWidget.cpp b/Gui/PanelWidget.cpp index b2bbd2e9fc..2266514191 100644 --- a/Gui/PanelWidget.cpp +++ b/Gui/PanelWidget.cpp @@ -88,6 +88,15 @@ PanelWidget::enterEventBase() } +bool +PanelWidget::isClickFocusPanel() const +{ + if (!_gui) { + return false; + } + return _gui->getCurrentPanelFocus() == this; +} + void PanelWidget::takeClickFocus() { @@ -126,4 +135,14 @@ PanelWidget::leaveEventBase() 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 index f3d2ea0744..4f6be29fd7 100644 --- a/Gui/PanelWidget.h +++ b/Gui/PanelWidget.h @@ -62,6 +62,14 @@ class PanelWidget : public ScriptObject void takeClickFocus(); + bool isClickFocusPanel() const; + + + /* + * @brief To be called when a keypress event is not accepted + */ + void handleUnCaughtKeyPressEvent(); + protected: virtual void notifyGuiClosing() {} @@ -75,6 +83,7 @@ class PanelWidget : public ScriptObject * @brief To be called in the leaveEvent handler of all derived classes. **/ void leaveEventBase(); + }; diff --git a/Gui/PropertiesBinWrapper.cpp b/Gui/PropertiesBinWrapper.cpp index 8e2e99bc40..b43b0e6648 100644 --- a/Gui/PropertiesBinWrapper.cpp +++ b/Gui/PropertiesBinWrapper.cpp @@ -60,4 +60,11 @@ 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 2daa697753..12cf57d2be 100644 --- a/Gui/PropertiesBinWrapper.h +++ b/Gui/PropertiesBinWrapper.h @@ -42,6 +42,7 @@ class PropertiesBinWrapper : public QWidget, public PanelWidget 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/ScriptEditor.cpp b/Gui/ScriptEditor.cpp index 8b3778190d..b1cd037de8 100644 --- a/Gui/ScriptEditor.cpp +++ b/Gui/ScriptEditor.cpp @@ -495,6 +495,8 @@ ScriptEditor::keyPressEvent(QKeyEvent* e) if (accept) { takeClickFocus(); e->accept(); + } else { + handleUnCaughtKeyPressEvent(); } } diff --git a/Gui/ViewerTab10.cpp b/Gui/ViewerTab10.cpp index 7c0ae1284d..e7ef7062ca 100644 --- a/Gui/ViewerTab10.cpp +++ b/Gui/ViewerTab10.cpp @@ -731,6 +731,8 @@ ViewerTab::keyPressEvent(QKeyEvent* e) if (accept) { takeClickFocus(); e->accept(); + } else { + handleUnCaughtKeyPressEvent(); } } // keyPressEvent diff --git a/Gui/ViewerTab20.cpp b/Gui/ViewerTab20.cpp index 7ddaba02ff..a4a3d97d09 100644 --- a/Gui/ViewerTab20.cpp +++ b/Gui/ViewerTab20.cpp @@ -747,6 +747,14 @@ ViewerTab::notifyOverlaysKeyDown(double scaleX, 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() ); From c2342754688a6cfb58461217b9d7a33b54786665 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Tue, 6 Oct 2015 16:44:29 +0200 Subject: [PATCH 152/178] When slider is focused draw cursor contour in orange --- Gui/ScaleSliderQWidget.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Gui/ScaleSliderQWidget.cpp b/Gui/ScaleSliderQWidget.cpp index eec0829484..b7195adf7f 100644 --- a/Gui/ScaleSliderQWidget.cpp +++ b/Gui/ScaleSliderQWidget.cpp @@ -517,8 +517,12 @@ 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 { + p.setPen(QColor(243,137,0)); + } p.drawLine( sliderBottomLeft.x(),sliderBottomLeft.y(),sliderBottomLeft.x(),sliderTopRight.y() ); p.drawLine( sliderBottomLeft.x(),sliderTopRight.y(),sliderTopRight.x(),sliderTopRight.y() ); From 1d345ef434411057e0f0b04fd1a57ba9847d9a8e Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Tue, 6 Oct 2015 18:22:02 +0200 Subject: [PATCH 153/178] Slider: Better focus --- Gui/ScaleSliderQWidget.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Gui/ScaleSliderQWidget.cpp b/Gui/ScaleSliderQWidget.cpp index b7195adf7f..63a75b9cae 100644 --- a/Gui/ScaleSliderQWidget.cpp +++ b/Gui/ScaleSliderQWidget.cpp @@ -398,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; @@ -521,7 +522,14 @@ ScaleSliderQWidget::paintEvent(QPaintEvent* /*e*/) if (!hasFocus()) { p.setPen(Qt::black); } else { - p.setPen(QColor(243,137,0)); + 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() ); From 5b5dae2da5ded50cf2901edc064ba9a423d46531 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Tue, 6 Oct 2015 18:22:22 +0200 Subject: [PATCH 154/178] Write back Ofx properties when changing dynamic Param properties. --- Engine/Knob.cpp | 9 +- Engine/Knob.h | 7 ++ Engine/OfxParamInstance.cpp | 203 ++++++++++++++++++++++++++++++++++++ Engine/OfxParamInstance.h | 134 ++++++++++++++++++++---- 4 files changed, 332 insertions(+), 21 deletions(-) diff --git a/Engine/Knob.cpp b/Engine/Knob.cpp index 16ab60b9a9..3f4427e6b6 100644 --- a/Engine/Knob.cpp +++ b/Engine/Knob.cpp @@ -2329,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 diff --git a/Engine/Knob.h b/Engine/Knob.h index bf8ac393bf..9c8253c495 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); diff --git a/Engine/OfxParamInstance.cpp b/Engine/OfxParamInstance.cpp index 495396ff20..398dd18ce7 100644 --- a/Engine/OfxParamInstance.cpp +++ b/Engine/OfxParamInstance.cpp @@ -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..a997a121b6 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; + 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(); From c7ad9282a12dbffe204465660bcdde7f80b7d6be Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Tue, 6 Oct 2015 18:23:12 +0200 Subject: [PATCH 155/178] fix compil --- Engine/OfxParamInstance.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Engine/OfxParamInstance.h b/Engine/OfxParamInstance.h index a997a121b6..26b0980083 100644 --- a/Engine/OfxParamInstance.h +++ b/Engine/OfxParamInstance.h @@ -527,7 +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; From 05762cd600ffaef3f69a4e0ebbc476f0e850c7d4 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Tue, 6 Oct 2015 18:31:26 +0200 Subject: [PATCH 156/178] Set script editor font to Courier New 12 by default --- Gui/ScriptEditor.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Gui/ScriptEditor.cpp b/Gui/ScriptEditor.cpp index b1cd037de8..e87d9b590c 100644 --- a/Gui/ScriptEditor.cpp +++ b/Gui/ScriptEditor.cpp @@ -228,16 +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); From 5f9e6b65f2fb0b34bba70e5a007e8e959ac552de Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Tue, 6 Oct 2015 18:40:49 +0200 Subject: [PATCH 157/178] PropertiesBin: don't stealfocus from SpinBox with wheel --- Gui/GuiPrivate.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Gui/GuiPrivate.cpp b/Gui/GuiPrivate.cpp index 4407dc2214..f8bf394f2f 100644 --- a/Gui/GuiPrivate.cpp +++ b/Gui/GuiPrivate.cpp @@ -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); From dfbc21e60262aca448bbeec5de557537b8bff068 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Tue, 6 Oct 2015 18:45:24 +0200 Subject: [PATCH 158/178] Missing slot connexions --- Engine/OfxImageEffectInstance.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Engine/OfxImageEffectInstance.cpp b/Engine/OfxImageEffectInstance.cpp index 3fd6fb85d2..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 From 609c15a75397e703c7145d63a5318a7302e25043 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Devernay?= Date: Tue, 6 Oct 2015 19:10:17 +0200 Subject: [PATCH 159/178] update openfx --- libs/OpenFX | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/OpenFX b/libs/OpenFX index 25b890140d..5902d46ab0 160000 --- a/libs/OpenFX +++ b/libs/OpenFX @@ -1 +1 @@ -Subproject commit 25b890140daeb67ccdb05ecd45ea9bd919f2939a +Subproject commit 5902d46ab0d9a58ba1884f47df23d4dd9bd7763a From 2c164a044519e6ba3630a0802ab54f011a80c7a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Devernay?= Date: Tue, 6 Oct 2015 19:12:14 +0200 Subject: [PATCH 160/178] update openfx --- libs/OpenFX | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/OpenFX b/libs/OpenFX index 5902d46ab0..0424cf839c 160000 --- a/libs/OpenFX +++ b/libs/OpenFX @@ -1 +1 @@ -Subproject commit 5902d46ab0d9a58ba1884f47df23d4dd9bd7763a +Subproject commit 0424cf839cda460d8403c0ddd7c6861a07fc05c7 From 1930e1a63bb340262977773e9b057167d418d263 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole-Andr=C3=A9=20Rodlie?= Date: Tue, 6 Oct 2015 21:11:39 +0200 Subject: [PATCH 161/178] Linux: launch script more compat --- tools/linux/include/scripts/Natron.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tools/linux/include/scripts/Natron.sh b/tools/linux/include/scripts/Natron.sh index 9fc26224aa..766a0f5da3 100644 --- a/tools/linux/include/scripts/Natron.sh +++ b/tools/linux/include/scripts/Natron.sh @@ -35,8 +35,12 @@ 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 -COMPAT_GCC=`$DIR/bin/strings $STDC_LIB | grep GLIBCXX_${COMPAT_VERSION}` 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." From a7fc1e973a5f76a5a9ea6247b0a4e989072fb5b7 Mon Sep 17 00:00:00 2001 From: MrKepzie Date: Tue, 6 Oct 2015 22:38:03 +0200 Subject: [PATCH 162/178] Default initialize pages visibility --- Global/GitVersion.h | 2 +- Gui/DockablePanel.cpp | 38 +++++------------------------------- Gui/DockablePanelPrivate.cpp | 26 ++++++++++++++++++++++-- Gui/DockablePanelPrivate.h | 2 ++ 4 files changed, 32 insertions(+), 36 deletions(-) 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/DockablePanel.cpp b/Gui/DockablePanel.cpp index 80f955bd5b..d2c223f69a 100644 --- a/Gui/DockablePanel.cpp +++ b/Gui/DockablePanel.cpp @@ -469,27 +469,9 @@ DockablePanel::onPageIndexChanged(int index) if (found == _imp->_pages.end()) { return; } + + _imp->refreshPagesSecretness(); - std::string stdName = name.toStdString(); - - const std::vector >& knobs = _imp->_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); - } - } - } Natron::EffectInstance* isEffect = dynamic_cast(_imp->_holder); if (isEffect && isEffect->getNode()->hasOverlay()) { isEffect->getApp()->redrawAllViewers(); @@ -568,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; } } @@ -682,19 +665,6 @@ 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); } @@ -710,6 +680,7 @@ DockablePanel::initializeKnobsInternal() } } } + _imp->refreshPagesSecretness(); } @@ -1041,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(); diff --git a/Gui/DockablePanelPrivate.cpp b/Gui/DockablePanelPrivate.cpp index 2142453fdb..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" @@ -720,5 +719,28 @@ DockablePanelPrivate::getOrCreatePage(KnobPage* page) 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 c770e8b68c..f1b278d797 100644 --- a/Gui/DockablePanelPrivate.h +++ b/Gui/DockablePanelPrivate.h @@ -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(); }; From 6c759b08ba93bf7b8084a7b0b22cc9ade202c142 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole-Andr=C3=A9=20Rodlie?= Date: Wed, 7 Oct 2015 01:42:48 +0200 Subject: [PATCH 163/178] Linux: support TAR and RPM on deploy --- tools/linux/include/natron/Natron.spec | 8 ++- .../linux/include/scripts/build-installer.sh | 61 +++++++++++++------ 2 files changed, 47 insertions(+), 22 deletions(-) diff --git a/tools/linux/include/natron/Natron.spec b/tools/linux/include/natron/Natron.spec index 03e7911739..f76ae139b8 100644 --- a/tools/linux/include/natron/Natron.spec +++ b/tools/linux/include/natron/Natron.spec @@ -3,7 +3,7 @@ Summary: open-source compositing software Name: Natron -Version: 2.0.0 +Version: REPLACE_VERSION Release: 1 License: GPLv2 @@ -25,8 +25,10 @@ cp -a /root/Natron/tools/linux/tmp/Natron-installer/packages/fr.inria.*/data/* $ 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 . +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) diff --git a/tools/linux/include/scripts/build-installer.sh b/tools/linux/include/scripts/build-installer.sh index d8a34e71db..6ecdfc1166 100644 --- a/tools/linux/include/scripts/build-installer.sh +++ b/tools/linux/include/scripts/build-installer.sh @@ -317,37 +317,60 @@ strip -s $ARENA_LIBS/* 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} - - REPO_DIR=$REPO_DIR_PREFIX$ONLINE_TAG - rm -rf $REPO_DIR/packages $REPO_DIR/installers +# Build repo and packages - 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!!!" From 71cdf6351599eaf380fabf91ae2916ff2f32db25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Devernay?= Date: Wed, 7 Oct 2015 08:04:08 +0200 Subject: [PATCH 164/178] Mac installer: use bzip2 compression --- tools/MacOSX/build-installer.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 From fad237e84f5220e9d3c26c9b3df8ba88a11ca99d Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Wed, 7 Oct 2015 10:57:31 +0200 Subject: [PATCH 165/178] Fix crash --- Gui/NodeGraph30.cpp | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/Gui/NodeGraph30.cpp b/Gui/NodeGraph30.cpp index 88eed4fdf3..d45768ea1c 100644 --- a/Gui/NodeGraph30.cpp +++ b/Gui/NodeGraph30.cpp @@ -83,8 +83,12 @@ NodeGraph::connectCurrentViewerToSelection(int inputNB) } - if ( !lastUsedViewer ) { - getGui()->getApp()->createNode( CreateNodeArgs(PLUGINID_NATRON_VIEWER, + 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, @@ -95,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( lastUsedViewer-> - 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; From 25604ea476f4d958da3825dada908f87657f1979 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Wed, 7 Oct 2015 12:52:27 +0200 Subject: [PATCH 166/178] Fix channel for mask --- Engine/EffectInstance.cpp | 2 +- Engine/EffectInstance.h | 3 ++- Engine/EffectInstanceRenderRoI.cpp | 11 ++++++----- Engine/Node.cpp | 20 ++++++++++++++++++++ 4 files changed, 29 insertions(+), 7 deletions(-) diff --git a/Engine/EffectInstance.cpp b/Engine/EffectInstance.cpp index 1cac7dff2d..d3a5b54b46 100644 --- a/Engine/EffectInstance.cpp +++ b/Engine/EffectInstance.cpp @@ -880,7 +880,7 @@ EffectInstance::getImage(int inputNb, getPreferredDepthAndComponents(inputNb, &prefComps, &prefDepth); assert(!prefComps.empty()); - inputImg = convertPlanesFormatsIfNeeded(getApp(), inputImg, pixelRoI, prefComps.front(), prefDepth, getNode()->usesAlpha0ToConvertFromRGBToRGBA(), outputPremult); + 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 diff --git a/Engine/EffectInstance.h b/Engine/EffectInstance.h index 89f6563f1f..a1b3e34c64 100644 --- a/Engine/EffectInstance.h +++ b/Engine/EffectInstance.h @@ -1684,7 +1684,8 @@ class EffectInstance const ImageComponents& targetComponents, ImageBitDepthEnum targetDepth, bool useAlpha0ForRGBToRGBAConversion, - ImagePremultiplicationEnum outputPremult); + ImagePremultiplicationEnum outputPremult, + int channelForAlpha); /** diff --git a/Engine/EffectInstanceRenderRoI.cpp b/Engine/EffectInstanceRenderRoI.cpp index a325b2352d..7d99232224 100644 --- a/Engine/EffectInstanceRenderRoI.cpp +++ b/Engine/EffectInstanceRenderRoI.cpp @@ -193,7 +193,8 @@ EffectInstance::convertPlanesFormatsIfNeeded(const AppInstance* app, const ImageComponents& targetComponents, ImageBitDepthEnum targetDepth, bool useAlpha0ForRGBToRGBAConversion, - ImagePremultiplicationEnum outputPremult) + ImagePremultiplicationEnum outputPremult, + int channelForAlpha) { bool imageConversionNeeded = targetComponents.getNumComponents() != inputImage->getComponents().getNumComponents() || targetDepth != inputImage->getBitDepth(); if (!imageConversionNeeded) { @@ -216,12 +217,12 @@ EffectInstance::convertPlanesFormatsIfNeeded(const AppInstance* app, inputImage->convertToFormatAlpha0( clippedRoi, app->getDefaultColorSpaceForBitDepth(inputImage->getBitDepth()), app->getDefaultColorSpaceForBitDepth(targetDepth), - -1, false, unPremultIfNeeded, tmp.get() ); + channelForAlpha, false, unPremultIfNeeded, tmp.get() ); } else { inputImage->convertToFormat( clippedRoi, app->getDefaultColorSpaceForBitDepth(inputImage->getBitDepth()), app->getDefaultColorSpaceForBitDepth(targetDepth), - -1, false, unPremultIfNeeded, tmp.get() ); + channelForAlpha, false, unPremultIfNeeded, tmp.get() ); } return tmp; @@ -576,7 +577,7 @@ EffectInstance::renderRoI(const RenderRoIArgs & args, premult = eImagePremultiplicationOpaque; } - ImagePtr tmp = convertPlanesFormatsIfNeeded(app, *it, args.roi, *compIt, inputArgs.bitdepth, useAlpha0ForRGBToRGBAConversion, premult); + ImagePtr tmp = convertPlanesFormatsIfNeeded(app, *it, args.roi, *compIt, inputArgs.bitdepth, useAlpha0ForRGBToRGBAConversion, premult, -1); assert(tmp); convertedPlanes.push_back(tmp); } @@ -1536,7 +1537,7 @@ EffectInstance::renderRoI(const RenderRoIArgs & args, } ///The image might need to be converted to fit the original requested format - it->second.downscaleImage = convertPlanesFormatsIfNeeded(getApp(), it->second.downscaleImage, roi, it->first, args.bitdepth, useAlpha0ForRGBToRGBAConversion, planesToRender.outputPremult); + 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); diff --git a/Engine/Node.cpp b/Engine/Node.cpp index 66e011a44c..a8a6d9023e 100644 --- a/Engine/Node.cpp +++ b/Engine/Node.cpp @@ -7001,6 +7001,26 @@ Node::refreshInputRelatedDataRecursiveInternal(std::list& markedN } } + /* + 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); From 80b59be10097395d05c5c97f515c342879ab4e15 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Wed, 7 Oct 2015 14:10:54 +0200 Subject: [PATCH 167/178] getUnmappedComponents() do not iterate recursively on identity inputs --- Engine/OfxClipInstance.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Engine/OfxClipInstance.cpp b/Engine/OfxClipInstance.cpp index 79ab463124..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; From 5bf33b1c39bc95d16ff355770a157be38d1239c1 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Wed, 7 Oct 2015 15:13:46 +0200 Subject: [PATCH 168/178] Re-order knobs added by Natron so it is consistent across plug-ins --- Engine/Node.cpp | 247 ++++++++++++++++++++++++++++++------------------ Engine/Node.h | 3 + 2 files changed, 157 insertions(+), 93 deletions(-) diff --git a/Engine/Node.cpp b/Engine/Node.cpp index a8a6d9023e..c04222a4b8 100644 --- a/Engine/Node.cpp +++ b/Engine/Node.cpp @@ -2538,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; - - } - } // for (int i = 0; i < inputsCount; ++i) { bool isReaderOrWriterOrTrackerOrGroup = _imp->liveInstance->isReader() || _imp->liveInstance->isWriter() || _imp->liveInstance->isTrackerNode() || dynamic_cast(_imp->liveInstance.get()); @@ -2622,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]); @@ -2637,29 +2562,58 @@ 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. + + ///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) { @@ -2671,11 +2625,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) { @@ -2690,13 +2648,104 @@ 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); + 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); + mainPage->addKnob(channel); + + boost::shared_ptr channelName = Natron::createKnob(_imp->liveInstance.get(), "",1,false); + channelName->setSecretByDefault(true); + channelName->setEvaluateOnChange(false); + 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"); @@ -2707,6 +2756,18 @@ Node::initializeKnobs(int renderScaleSupportPref) mainPage->addKnob(mixKnob); _imp->mixWithSource = mixKnob; } + + + /* + * Reposition the MaskInvert and Mix parameters declared by the plug-in + */ + 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); diff --git a/Engine/Node.h b/Engine/Node.h index 4bd8a1c97e..c55625f41c 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; From fc63746949e27e7da72b43e1148b5b0db8f3b633 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Wed, 7 Oct 2015 15:28:51 +0200 Subject: [PATCH 169/178] eValueChangedReasonSlaveRefresh and eValueChangedReasonRestoreDefault maps to kOfxChangeUserEdited and not kOfxChangePluginEdited --- Engine/OfxEffectInstance.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Engine/OfxEffectInstance.cpp b/Engine/OfxEffectInstance.cpp index eff756deed..5e36fc9fb4 100644 --- a/Engine/OfxEffectInstance.cpp +++ b/Engine/OfxEffectInstance.cpp @@ -2435,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; From c6ef0b2c175d49bed3a9830d08dcfdf794d0d3dc Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Wed, 7 Oct 2015 15:58:44 +0200 Subject: [PATCH 170/178] Fix reset plug-in to default functionality --- Engine/Knob.cpp | 7 +++++++ Engine/Knob.h | 2 ++ Engine/Node.cpp | 5 ++++- Gui/KnobUndoCommand.cpp | 27 +++++++++++++++++++++++++++ 4 files changed, 40 insertions(+), 1 deletion(-) diff --git a/Engine/Knob.cpp b/Engine/Knob.cpp index 3f4427e6b6..cbef12fbf7 100644 --- a/Engine/Knob.cpp +++ b/Engine/Knob.cpp @@ -3692,6 +3692,13 @@ KnobHolder::onDoEndChangesOnMainThreadTriggered() endChanges(); } +ChangesList +KnobHolder::getKnobChanges() const +{ + QMutexLocker l(&_imp->evaluationBlockedMutex); + return _imp->knobChanged; +} + void KnobHolder::endChanges(bool discardEverything) { diff --git a/Engine/Knob.h b/Engine/Knob.h index 9c8253c495..2d8ebd0b9c 100644 --- a/Engine/Knob.h +++ b/Engine/Knob.h @@ -2074,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/Node.cpp b/Engine/Node.cpp index c04222a4b8..68dd8ed3aa 100644 --- a/Engine/Node.cpp +++ b/Engine/Node.cpp @@ -6165,7 +6165,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 diff --git a/Gui/KnobUndoCommand.cpp b/Gui/KnobUndoCommand.cpp index 43489118d4..67c1ca6ea8 100644 --- a/Gui/KnobUndoCommand.cpp +++ b/Gui/KnobUndoCommand.cpp @@ -531,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) { @@ -550,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); } From 24dc1fb6d406046804eaca42fecf2e8a19d90fdf Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Wed, 7 Oct 2015 16:11:47 +0200 Subject: [PATCH 171/178] Add separator --- Engine/Node.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Engine/Node.cpp b/Engine/Node.cpp index 68dd8ed3aa..3c9ff5c5a0 100644 --- a/Engine/Node.cpp +++ b/Engine/Node.cpp @@ -2593,7 +2593,10 @@ Node::initializeKnobs(int renderScaleSupportPref) bool useSelectors = !dynamic_cast(_imp->liveInstance.get()); if (useSelectors) { - + + 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].first) { From afe18473d1d656ce3c3ddf0db30faf5ee2af498b Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Wed, 7 Oct 2015 16:14:28 +0200 Subject: [PATCH 172/178] Don't show empty sub labels --- Engine/Node.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Engine/Node.cpp b/Engine/Node.cpp index 3c9ff5c5a0..bb4911b296 100644 --- a/Engine/Node.cpp +++ b/Engine/Node.cpp @@ -5944,7 +5944,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 From 05ac0ead450a544270bf339dcd6fc4fa1dc00111 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Wed, 7 Oct 2015 16:18:21 +0200 Subject: [PATCH 173/178] Fix crash --- Engine/Node.cpp | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/Engine/Node.cpp b/Engine/Node.cpp index bb4911b296..518026eb3a 100644 --- a/Engine/Node.cpp +++ b/Engine/Node.cpp @@ -2698,7 +2698,9 @@ Node::initializeKnobs(int renderScaleSupportPref) "Turning this off will act as though the input was disconnected.").toStdString()); } enabled->setAnimationEnabled(false); - mainPage->addKnob(enabled); + if (mainPage) { + mainPage->addKnob(enabled); + } sel.enabled = enabled; @@ -2728,12 +2730,16 @@ Node::initializeKnobs(int renderScaleSupportPref) } sel.channel = channel; channel->setAddNewLine(false); - mainPage->addKnob(channel); + if (mainPage) { + mainPage->addKnob(channel); + } boost::shared_ptr channelName = Natron::createKnob(_imp->liveInstance.get(), "",1,false); channelName->setSecretByDefault(true); channelName->setEvaluateOnChange(false); - mainPage->addKnob(channelName); + if (mainPage) { + mainPage->addKnob(channelName); + } sel.channelName = channelName; //Make sure the first default param in the vector is MaskInvert @@ -2756,7 +2762,9 @@ Node::initializeKnobs(int renderScaleSupportPref) mixKnob->setMinimum(0.); mixKnob->setMaximum(1.); mixKnob->setDefaultValue(1.); - mainPage->addKnob(mixKnob); + if (mainPage) { + mainPage->addKnob(mixKnob); + } _imp->mixWithSource = mixKnob; } @@ -2764,10 +2772,12 @@ Node::initializeKnobs(int renderScaleSupportPref) /* * Reposition the MaskInvert and Mix parameters declared by the plug-in */ - 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); + 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); + } } } From 2e3df6a3acceae112c80eb68efa05df75274d99a Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Wed, 7 Oct 2015 16:52:53 +0200 Subject: [PATCH 174/178] Bug fix in addIdentityNodesRecursively --- Engine/DiskCacheNode.cpp | 2 +- Engine/DiskCacheNode.h | 2 +- Engine/EffectInstance.cpp | 4 +- Engine/EffectInstance.h | 2 +- Engine/EffectInstanceRenderRoI.cpp | 2 +- Engine/Node.cpp | 113 +++++++++++++++++++++++------ Engine/Node.h | 2 +- 7 files changed, 97 insertions(+), 30 deletions(-) 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 d3a5b54b46..2f5f8cc11c 100644 --- a/Engine/EffectInstance.cpp +++ b/Engine/EffectInstance.cpp @@ -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 diff --git a/Engine/EffectInstance.h b/Engine/EffectInstance.h index a1b3e34c64..fbb198335c 100644 --- a/Engine/EffectInstance.h +++ b/Engine/EffectInstance.h @@ -909,7 +909,7 @@ class EffectInstance const bool dontUpscale, RectI* roiPixel) WARN_UNUSED_RETURN; virtual void aboutToRestoreDefaultValues() OVERRIDE FINAL; - virtual bool shouldCacheOutput(bool isFrameVaryingOrAnimated) const; + 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. diff --git a/Engine/EffectInstanceRenderRoI.cpp b/Engine/EffectInstanceRenderRoI.cpp index 7d99232224..19e25e1a5c 100644 --- a/Engine/EffectInstanceRenderRoI.cpp +++ b/Engine/EffectInstanceRenderRoI.cpp @@ -679,7 +679,7 @@ EffectInstance::renderRoI(const RenderRoIArgs & args, bool draftModeSupported = getNode()->isDraftModeUsed(); bool isFrameVaryingOrAnimated = isFrameVaryingOrAnimated_Recursive(); - bool createInCache = shouldCacheOutput(isFrameVaryingOrAnimated); + bool createInCache = shouldCacheOutput(isFrameVaryingOrAnimated, args.time, args.view); Natron::ImageKey key(getNode().get(), nodeHash, isFrameVaryingOrAnimated, diff --git a/Engine/Node.cpp b/Engine/Node.cpp index 518026eb3a..2098102baf 100644 --- a/Engine/Node.cpp +++ b/Engine/Node.cpp @@ -6708,8 +6708,92 @@ Node::dequeueActions() } } +static void addIdentityNodesRecursively(const Node* caller, + const Node* node, + double time, + int view, + std::list* outputs, + std::list* markedNodes) +{ + if (std::find(markedNodes->begin(), markedNodes->end(), node) != markedNodes->end()) { + return; + } + + markedNodes->push_back(node); + + + 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 = 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) { + boost::shared_ptr collection = (*it)->getGroup(); + assert(collection); + NodeGroup* isGrp = dynamic_cast(collection.get()); + if (isGrp) { + std::list groupOutputs; + isGrp->getNode()->getOutputs_mt_safe(groupOutputs); + for (std::list::iterator it2 = groupOutputs.begin(); it2 != groupOutputs.end(); ++it2) { + outputsToAdd.push_back(*it2); + } + } + } + } + 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) const +Node::shouldCacheOutput(bool isFrameVaryingOrAnimated, double time, int view) const { /* * Here is a list of reasons when caching is enabled for a node: @@ -6726,29 +6810,12 @@ Node::shouldCacheOutput(bool isFrameVaryingOrAnimated) const * - The node does not support tiles */ - std::list outputs; + std::list outputs; { - QMutexLocker k(&_imp->outputsMutex); - outputs = _imp->outputs; + std::list markedNodes; + addIdentityNodesRecursively(this, this, time, view,&outputs,&markedNodes); } - std::list outputsToAdd; - for (std::list::iterator it = outputs.begin(); it != outputs.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) { - boost::shared_ptr collection = (*it)->getGroup(); - assert(collection); - NodeGroup* isGrp = dynamic_cast(collection.get()); - if (isGrp) { - std::list groupOutputs; - isGrp->getNode()->getOutputs_mt_safe(groupOutputs); - for (std::list::iterator it2 = groupOutputs.begin(); it2 != groupOutputs.end(); ++it2) { - outputsToAdd.push_back(*it2); - } - } - } - } - outputs.insert(outputs.end(), outputsToAdd.begin(),outputsToAdd.end()); + std::size_t sz = outputs.size(); if (sz > 1) { @@ -6757,7 +6824,7 @@ Node::shouldCacheOutput(bool isFrameVaryingOrAnimated) const } else { if (sz == 1) { - Node* output = outputs.front(); + const Node* output = outputs.front(); ViewerInstance* isViewer = dynamic_cast(output->getLiveInstance()); if (isViewer) { diff --git a/Engine/Node.h b/Engine/Node.h index c55625f41c..d65bc43343 100644 --- a/Engine/Node.h +++ b/Engine/Node.h @@ -546,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. From 1aeb892dba0246d726af767142aafab237c591fa Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Wed, 7 Oct 2015 16:57:34 +0200 Subject: [PATCH 175/178] Small fix --- Gui/NodeGraph25.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gui/NodeGraph25.cpp b/Gui/NodeGraph25.cpp index c62e2ebc6d..9cb0f6cec1 100644 --- a/Gui/NodeGraph25.cpp +++ b/Gui/NodeGraph25.cpp @@ -285,7 +285,7 @@ NodeGraph::keyPressEvent(QKeyEvent* e) createGroupFromSelection(); } else if ( isKeybind(kShortcutGroupNodegraph, kShortcutIDActionGraphExpandGroup, modifiers, key) ) { expandSelectedGroups(); - } else if (key == Qt::Key_Control && modCASIsNone(e)) { + } else if (key == Qt::Key_Control && e->modifiers() == Qt::ControlModifier) { _imp->setNodesBendPointsVisible(true); accept = false; } else if ( isKeybind(kShortcutGroupNodegraph, kShortcutIDActionGraphSelectUp, modifiers, key) || From 09d2db86a3fa64bf01dee2daae1300aa7f25d268 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Wed, 7 Oct 2015 17:18:32 +0200 Subject: [PATCH 176/178] Abort correctly when changing a parameter in a group --- Engine/EffectInstance.cpp | 12 +++++++++++- Engine/Node.cpp | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/Engine/EffectInstance.cpp b/Engine/EffectInstance.cpp index 2f5f8cc11c..4492f15648 100644 --- a/Engine/EffectInstance.cpp +++ b/Engine/EffectInstance.cpp @@ -4209,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) { diff --git a/Engine/Node.cpp b/Engine/Node.cpp index 2098102baf..f4b8394217 100644 --- a/Engine/Node.cpp +++ b/Engine/Node.cpp @@ -3189,7 +3189,7 @@ Node::hasOutputNodesConnected(std::list* writers { Natron::OutputEffectInstance* thisWriter = dynamic_cast(_imp->liveInstance.get()); - if (thisWriter && thisWriter->isOutput()) { + 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); From cb246ffcaca437b759b425241dfa1171bc3bdbad Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Wed, 7 Oct 2015 17:38:34 +0200 Subject: [PATCH 177/178] Update Changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) 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 From 91754b6e420099a56120040c24a776583e076f39 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Thu, 8 Oct 2015 09:20:22 +0200 Subject: [PATCH 178/178] Update tags --- tools/Windows/common.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 #----------------------------------------------------------