diff --git a/CMakeLists.txt b/CMakeLists.txt index ea371295d..9fc629ec9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -72,7 +72,9 @@ option (BUILD_SHARED "Build rawtherapee with shared libraries" OFF) option (WITH_BZIP "Build with Bzip2 support" ON) option (WITH_MYFILE_MMAP "Build using memory mapped file" ON) option (OPTION_OMP "Build with OpenMP support" ON) -option (BUILD_BUNDLE "Self-contained build" OFF) +option (PROTECT_VECTORS "Protect critical vectors by custom R/W Mutex, recommanded even if your std::vector is thread safe" ON) +option (TRACE_MYRWMUTEX "Trace RT's custom R/W Mutex (Debug builds only); redirecting std::out to a file is strongly recommended!" OFF) +option (AUTO_GDK_FLUSH "Use gdk_flush on all gdk_thread_leave other than the GUI thread; set it ON if you experience X Server warning/errors" OFF) # set install directories if (WIN32 OR APPLE) @@ -160,6 +162,25 @@ if (NOT BUILD_BUNDLE AND message (FATAL_ERROR "The paths has to be absolute or use -DBUILD_BUNDLE=ON") endif () +# MyRWMutex +if (PROTECT_VECTORS) + add_definitions (-DPROTECT_VECTORS=1) +else (PROTECT_VECTORS) + add_definitions (-DPROTECT_VECTORS=0) +endif (PROTECT_VECTORS) +if (TRACE_MYRWMUTEX) + # Note: it will be set to 0 for Debug builds (rtgui/guiutils.h) + add_definitions (-DTRACE_MYRWMUTEX=1) +else (TRACE_MYRWMUTEX) + add_definitions (-DTRACE_MYRWMUTEX=0) +endif (TRACE_MYRWMUTEX) + +if (AUTO_GDK_FLUSH) + add_definitions (-DAUTO_GDK_FLUSH=1) +else (AUTO_GDK_FLUSH) + add_definitions (-DAUTO_GDK_FLUSH=0) +endif (AUTO_GDK_FLUSH) + # check for libraries find_package(PkgConfig) pkg_check_modules (GTK REQUIRED gtk+-2.0>=2.12) diff --git a/rtengine/rtthumbnail.cc b/rtengine/rtthumbnail.cc index ae52b32d9..fd0d25008 100644 --- a/rtengine/rtthumbnail.cc +++ b/rtengine/rtthumbnail.cc @@ -40,7 +40,7 @@ namespace rtengine { -Thumbnail* Thumbnail::loadFromImage (const Glib::ustring& fname, int &w, int &h, int fixwh, int deg) { +Thumbnail* Thumbnail::loadFromImage (const Glib::ustring& fname, int &w, int &h, int fixwh) { StdImageSource imgSrc; if (imgSrc.load(fname)) { @@ -48,10 +48,6 @@ Thumbnail* Thumbnail::loadFromImage (const Glib::ustring& fname, int &w, int &h, } ImageIO* img = imgSrc.getImageIO(); - - if (deg) { - img->rotate(deg); - } Thumbnail* tpp = new Thumbnail (); @@ -607,10 +603,10 @@ IImage8* Thumbnail::processImage (const procparams::ProcParams& params, int rhei int rwidth; if (params.coarse.rotate==90 || params.coarse.rotate==270) { rwidth = rheight; - rheight = thumbImg->height * rwidth / thumbImg->width; + rheight = int(size_t(thumbImg->height) * size_t(rwidth) / size_t(thumbImg->width)); } else - rwidth = thumbImg->width * rheight / thumbImg->height; + rwidth = int(size_t(thumbImg->width) * size_t(rheight) / size_t(thumbImg->height)); Imagefloat* baseImg = resizeTo(rwidth, rheight, interp, thumbImg); diff --git a/rtengine/rtthumbnail.h b/rtengine/rtthumbnail.h index 3509bb202..46ecfbd15 100644 --- a/rtengine/rtthumbnail.h +++ b/rtengine/rtthumbnail.h @@ -81,7 +81,7 @@ namespace rtengine { static Thumbnail* loadQuickFromRaw (const Glib::ustring& fname, rtengine::RawMetaDataLocation& rml, int &w, int &h, int fixwh, bool rotate); static Thumbnail* loadFromRaw (const Glib::ustring& fname, RawMetaDataLocation& rml, int &w, int &h, int fixwh, bool rotate); - static Thumbnail* loadFromImage (const Glib::ustring& fname, int &w, int &h, int fixwh, int deg=0); + static Thumbnail* loadFromImage (const Glib::ustring& fname, int &w, int &h, int fixwh); void getCamWB (double& temp, double& green); void getAutoWB (double& temp, double& green); diff --git a/rtgui/batchqueue.cc b/rtgui/batchqueue.cc index e96d88e9e..8529634a1 100644 --- a/rtgui/batchqueue.cc +++ b/rtgui/batchqueue.cc @@ -65,6 +65,17 @@ BatchQueue::~BatchQueue () delete pmenu; } +void BatchQueue::resizeLoadedQueue() { + // TODO: Check for Linux + #if PROTECT_VECTORS + MYWRITERLOCK(l, entryRW); + #endif + + for (size_t i=0; iresize(getThumbnailHeight()); + } +} + // Reduce the max size of a thumb, since thumb is processed synchronously on adding to queue // leading to very long waiting when adding more images int BatchQueue::calcMaxThumbnailHeight() { @@ -76,23 +87,37 @@ int BatchQueue::getMaxThumbnailHeight() const { return calcMaxThumbnailHeight(); } +void BatchQueue::saveThumbnailHeight (int height) { + options.thumbSizeQueue = height; +} + +int BatchQueue::getThumbnailHeight () { + // The user could have manually forced the option to a too big value + return std::max(std::min(options.thumbSizeQueue, 200), 10); +} void BatchQueue::rightClicked (ThumbBrowserEntryBase* entry) { pmenu->popup (3, this->eventTime); } -void BatchQueue::addEntries ( std::vector &entries, bool head) +void BatchQueue::addEntries ( std::vector &entries, bool head, bool save) { { - // TODO: Check for Linux - #ifdef WIN32 - Glib::RWLock::WriterLock l(entryRW); - #endif + // TODO: Check for Linux + #if PROTECT_VECTORS + MYWRITERLOCK(l, entryRW); + #endif for( std::vector::iterator entry = entries.begin(); entry != entries.end();entry++ ){ (*entry)->setParent (this); - (*entry)->resize (std::min(options.thumbSize, getMaxThumbnailHeight())); // batch queue might have smaller, restricted size + + // BatchQueueButtonSet HAVE TO be added before resizing to take them into account + BatchQueueButtonSet* bqbs = new BatchQueueButtonSet (*entry); + bqbs->setButtonListener (this); + (*entry)->addButtonSet (bqbs); + + (*entry)->resize (getThumbnailHeight()); // batch queue might have smaller, restricted size Glib::ustring tempFile = getTempFilenameForParams( (*entry)->filename ); // recovery save @@ -114,13 +139,11 @@ void BatchQueue::addEntries ( std::vector &entries, bool head) } if ((*entry)->thumbnail) (*entry)->thumbnail->imageEnqueued (); + } + } - BatchQueueButtonSet* bqbs = new BatchQueueButtonSet (*entry); - bqbs->setButtonListener (this); - (*entry)->addButtonSet (bqbs); - } - saveBatchQueue( ); - } + if (save) + saveBatchQueue( ); redraw(); notifyListener (false); @@ -135,6 +158,12 @@ bool BatchQueue::saveBatchQueue( ) if (f==NULL) return false; + { + // TODO: Check for Linux + #if PROTECT_VECTORS + MYREADERLOCK(l, entryRW); + #endif + if (fd.size()) // The column's header is mandatory (the first line will be skipped when loaded) fprintf(f,"input image full path|param file full path|output image full path|file format|jpeg quality|jpeg subsampling|" @@ -152,186 +181,186 @@ bool BatchQueue::saveBatchQueue( ) bqe->saveFormat.saveParams, bqe->forceFormatOpts ); } + } fclose (f); return true; } -void BatchQueue::loadBatchQueue( ) +bool BatchQueue::loadBatchQueue( ) { - { + Glib::ustring savedQueueFile; + savedQueueFile = options.rtdir+"/batch/queue.csv"; + FILE *f = safe_g_fopen (savedQueueFile, "rt"); + + if (f!=NULL) { + char *buffer = new char[1024]; + unsigned numLoaded=0; + // skipping the first line + bool firstLine=true; + + // Yes, it's better to get the lock for the whole file reading, + // to update the list in one shot without any other concurrent access! + // TODO: Check for Linux -#ifdef WIN32 - Glib::RWLock::WriterLock l(entryRW); -#endif + #if PROTECT_VECTORS + MYWRITERLOCK(l, entryRW); + #endif - Glib::ustring savedQueueFile; - savedQueueFile = options.rtdir+"/batch/queue.csv"; - FILE *f = safe_g_fopen (savedQueueFile, "rt"); + while (fgets (buffer, 1024, f)){ - if (f!=NULL) { - char *buffer = new char[1024]; - unsigned numLoaded=0; - // skipping the first line - bool firstLine=true; - while (fgets (buffer, 1024, f)){ + if (firstLine) { + // skipping the column's title line + firstLine=false; + continue; + } - if (firstLine) { - // skipping the column's title line - firstLine=false; - continue; - } + size_t pos; + Glib::ustring source; + Glib::ustring paramsFile; + Glib::ustring outputFile; + Glib::ustring saveFmt(options.saveFormat.format); + int jpegQuality=options.saveFormat.jpegQuality, jpegSubSamp =options.saveFormat.jpegSubSamp; + int pngBits =options.saveFormat.pngBits, pngCompression =options.saveFormat.pngCompression; + int tiffBits =options.saveFormat.tiffBits, tiffUncompressed=options.saveFormat.tiffUncompressed; + int saveParams =options.saveFormat.saveParams; + int forceFormatOpts =options.forceFormatOpts; - size_t pos; - Glib::ustring source; - Glib::ustring paramsFile; - Glib::ustring outputFile; - Glib::ustring saveFmt(options.saveFormat.format); - int jpegQuality=options.saveFormat.jpegQuality, jpegSubSamp =options.saveFormat.jpegSubSamp; - int pngBits =options.saveFormat.pngBits, pngCompression =options.saveFormat.pngCompression; - int tiffBits =options.saveFormat.tiffBits, tiffUncompressed=options.saveFormat.tiffUncompressed; - int saveParams =options.saveFormat.saveParams; - int forceFormatOpts =options.forceFormatOpts; + Glib::ustring currLine(buffer); + int a = 0; + if (currLine.rfind('\n') != Glib::ustring::npos) a++; + if (currLine.rfind('\r') != Glib::ustring::npos) a++; + if (a) + currLine = currLine.substr(0, currLine.length()-a); - Glib::ustring currLine(buffer); - int a = 0; - if (currLine.rfind('\n') != Glib::ustring::npos) a++; - if (currLine.rfind('\r') != Glib::ustring::npos) a++; - if (a) - currLine = currLine.substr(0, currLine.length()-a); + // Looking for the image's full path + pos = currLine.find('|'); + if (pos != Glib::ustring::npos) { + source = currLine.substr(0, pos); + currLine = currLine.substr(pos+1); - // Looking for the image's full path - pos = currLine.find('|'); - if (pos != Glib::ustring::npos) { - source = currLine.substr(0, pos); + // Looking for the procparams' full path + pos = currLine.find('|'); + if (pos != Glib::ustring::npos) { + paramsFile = currLine.substr(0, pos); + currLine = currLine.substr(pos+1); + + // Looking for the full output path; if empty, it'll use the template string + pos = currLine.find('|'); + if (pos != Glib::ustring::npos) { + outputFile = currLine.substr(0, pos); currLine = currLine.substr(pos+1); - // Looking for the procparams' full path - pos = currLine.find('|'); - if (pos != Glib::ustring::npos) { - paramsFile = currLine.substr(0, pos); - currLine = currLine.substr(pos+1); + // No need to bother reading the last options, they will be ignored if outputFile is empty! + if (!outputFile.empty()) { - // Looking for the full output path; if empty, it'll use the template string + // Looking for the saving format pos = currLine.find('|'); if (pos != Glib::ustring::npos) { - outputFile = currLine.substr(0, pos); + saveFmt = currLine.substr(0, pos); currLine = currLine.substr(pos+1); - // No need to bother reading the last options, they will be ignored if outputFile is empty! - if (!outputFile.empty()) { + // Looking for the jpeg quality + pos = currLine.find('|'); + if (pos != Glib::ustring::npos) { + jpegQuality = atoi(currLine.substr(0, pos).c_str()); + currLine = currLine.substr(pos+1); - // Looking for the saving format - pos = currLine.find('|'); - if (pos != Glib::ustring::npos) { - saveFmt = currLine.substr(0, pos); - currLine = currLine.substr(pos+1); + // Looking for the jpeg subsampling + pos = currLine.find('|'); + if (pos != Glib::ustring::npos) { + jpegSubSamp = atoi(currLine.substr(0, pos).c_str()); + currLine = currLine.substr(pos+1); - // Looking for the jpeg quality - pos = currLine.find('|'); - if (pos != Glib::ustring::npos) { - jpegQuality = atoi(currLine.substr(0, pos).c_str()); - currLine = currLine.substr(pos+1); + // Looking for the png bit depth + pos = currLine.find('|'); + if (pos != Glib::ustring::npos) { + pngBits = atoi(currLine.substr(0, pos).c_str()); + currLine = currLine.substr(pos+1); - // Looking for the jpeg subsampling - pos = currLine.find('|'); - if (pos != Glib::ustring::npos) { - jpegSubSamp = atoi(currLine.substr(0, pos).c_str()); - currLine = currLine.substr(pos+1); + // Looking for the png compression + pos = currLine.find('|'); + if (pos != Glib::ustring::npos) { + pngCompression = atoi(currLine.substr(0, pos).c_str()); + currLine = currLine.substr(pos+1); - // Looking for the png bit depth - pos = currLine.find('|'); - if (pos != Glib::ustring::npos) { - pngBits = atoi(currLine.substr(0, pos).c_str()); - currLine = currLine.substr(pos+1); + // Looking for the tiff bit depth + pos = currLine.find('|'); + if (pos != Glib::ustring::npos) { + tiffBits = atoi(currLine.substr(0, pos).c_str()); + currLine = currLine.substr(pos+1); - // Looking for the png compression - pos = currLine.find('|'); - if (pos != Glib::ustring::npos) { - pngCompression = atoi(currLine.substr(0, pos).c_str()); - currLine = currLine.substr(pos+1); + // Looking for the tiff uncompression + pos = currLine.find('|'); + if (pos != Glib::ustring::npos) { + tiffUncompressed = atoi(currLine.substr(0, pos).c_str()); + currLine = currLine.substr(pos+1); - // Looking for the tiff bit depth - pos = currLine.find('|'); - if (pos != Glib::ustring::npos) { - tiffBits = atoi(currLine.substr(0, pos).c_str()); - currLine = currLine.substr(pos+1); + // Looking out if we have to save the procparams + pos = currLine.find('|'); + if (pos != Glib::ustring::npos) { + saveParams = atoi(currLine.substr(0, pos).c_str()); + currLine = currLine.substr(pos+1); - // Looking for the tiff uncompression - pos = currLine.find('|'); - if (pos != Glib::ustring::npos) { - tiffUncompressed = atoi(currLine.substr(0, pos).c_str()); - currLine = currLine.substr(pos+1); + // Looking out if we have to to use the format options + pos = currLine.find('|'); + if (pos != Glib::ustring::npos) { + forceFormatOpts = atoi(currLine.substr(0, pos).c_str()); + // currLine = currLine.substr(pos+1); - // Looking out if we have to save the procparams - pos = currLine.find('|'); - if (pos != Glib::ustring::npos) { - saveParams = atoi(currLine.substr(0, pos).c_str()); - currLine = currLine.substr(pos+1); + }}}}}}}}}}}}} - // Looking out if we have to to use the format options - pos = currLine.find('|'); - if (pos != Glib::ustring::npos) { - forceFormatOpts = atoi(currLine.substr(0, pos).c_str()); - // currLine = currLine.substr(pos+1); + if( !source.empty() && !paramsFile.empty() ){ + rtengine::procparams::ProcParams pparams; + if( pparams.load( paramsFile ) ) + continue; - }}}}}}}}}}}}} + ::Thumbnail *thumb = cacheMgr->getEntry( source, &pparams ); + if( thumb ){ + rtengine::ProcessingJob* job = rtengine::ProcessingJob::create(source, thumb->getType() == FT_Raw, pparams); - if( !source.empty() && !paramsFile.empty() ){ - rtengine::procparams::ProcParams pparams; - if( pparams.load( paramsFile ) ) - continue; + int prevh = getMaxThumbnailHeight(); + int prevw = prevh; + thumb->getThumbnailSize (prevw, prevh); - ::Thumbnail *thumb = cacheMgr->getEntry( source ); - if( thumb ){ - rtengine::ProcessingJob* job = rtengine::ProcessingJob::create(source, thumb->getType() == FT_Raw, pparams); + BatchQueueEntry *entry = new BatchQueueEntry(job, pparams,source, prevw, prevh, thumb); + entry->setParent(this); - int prevh = getMaxThumbnailHeight(); - int prevw = prevh; - guint8* prev = NULL; - double tmpscale; - rtengine::IImage8* img = thumb->processThumbImage(pparams, prevh, tmpscale); - if (img) { - prevw = img->getWidth(); - prevh = img->getHeight(); - prev = new guint8[prevw * prevh * 3]; - memcpy(prev, img->getData(), prevw * prevh * 3); - img->free(); - } - BatchQueueEntry *entry = new BatchQueueEntry(job, pparams,source, prev, prevw, prevh, thumb); - entry->setParent(this); - entry->resize(options.thumbSize); - entry->savedParamsFile = paramsFile; - entry->selected = false; - entry->outFileName = outputFile; - if (!outputFile.empty()) { - entry->saveFormat.format = saveFmt; - entry->saveFormat.jpegQuality = jpegQuality; - entry->saveFormat.jpegSubSamp = jpegSubSamp; - entry->saveFormat.pngBits = pngBits; - entry->saveFormat.pngCompression = pngCompression; - entry->saveFormat.tiffBits = tiffBits; - entry->saveFormat.tiffUncompressed = tiffUncompressed!=0; - entry->saveFormat.saveParams = saveParams!=0; - entry->forceFormatOpts = forceFormatOpts!=0; - } - else - entry->forceFormatOpts = false; - fd.push_back(entry); + // BatchQueueButtonSet HAVE TO be added before resizing to take them into account + BatchQueueButtonSet* bqbs = new BatchQueueButtonSet(entry); + bqbs->setButtonListener(this); + entry->addButtonSet(bqbs); - BatchQueueButtonSet* bqbs = new BatchQueueButtonSet(entry); - bqbs->setButtonListener(this); - entry->addButtonSet(bqbs); - numLoaded++; + //entry->resize(getThumbnailHeight()); + entry->savedParamsFile = paramsFile; + entry->selected = false; + entry->outFileName = outputFile; + if (!outputFile.empty()) { + entry->saveFormat.format = saveFmt; + entry->saveFormat.jpegQuality = jpegQuality; + entry->saveFormat.jpegSubSamp = jpegSubSamp; + entry->saveFormat.pngBits = pngBits; + entry->saveFormat.pngCompression = pngCompression; + entry->saveFormat.tiffBits = tiffBits; + entry->saveFormat.tiffUncompressed = tiffUncompressed!=0; + entry->saveFormat.saveParams = saveParams!=0; + entry->forceFormatOpts = forceFormatOpts!=0; } + else + entry->forceFormatOpts = false; + fd.push_back(entry); + + numLoaded++; } } - delete [] buffer; - fclose(f); } + delete [] buffer; + fclose(f); } redraw(); notifyListener(false); + + return !fd.empty(); } Glib::ustring BatchQueue::getTempFilenameForParams( const Glib::ustring filename ) @@ -359,13 +388,13 @@ int cancelItemUI (void* data) } void BatchQueue::cancelItems (std::vector* items) { - { + { // TODO: Check for Linux -#ifdef WIN32 - Glib::RWLock::WriterLock l(entryRW); -#endif + #if PROTECT_VECTORS + MYWRITERLOCK(l, entryRW); + #endif - for (size_t i=0; isize(); i++) { + for (size_t i=0; isize(); i++) { BatchQueueEntry* entry = (BatchQueueEntry*)(*items)[i]; if (entry->processing) continue; @@ -382,59 +411,61 @@ void BatchQueue::cancelItems (std::vector* items) { fd[i]->selected = false; lastClicked = NULL; selected.clear (); - - saveBatchQueue( ); } + saveBatchQueue( ); + redraw (); notifyListener (false); } void BatchQueue::headItems (std::vector* items) { - { - // TODO: Check for Linux -#ifdef WIN32 - Glib::RWLock::WriterLock l(entryRW); -#endif - for (int i=items->size()-1; i>=0; i--) { - BatchQueueEntry* entry = (BatchQueueEntry*)(*items)[i]; - if (entry->processing) - continue; - std::vector::iterator pos = std::find (fd.begin(), fd.end(), entry); - if (pos!=fd.end() && pos!=fd.begin()) { - fd.erase (pos); - // find the first item that is not under processing - for (pos=fd.begin(); pos!=fd.end(); pos++) - if (!(*pos)->processing) { - fd.insert (pos, entry); - break; - } - } + { + // TODO: Check for Linux + #if PROTECT_VECTORS + MYWRITERLOCK(l, entryRW); + #endif + + for (int i=items->size()-1; i>=0; i--) { + BatchQueueEntry* entry = (BatchQueueEntry*)(*items)[i]; + if (entry->processing) + continue; + std::vector::iterator pos = std::find (fd.begin(), fd.end(), entry); + if (pos!=fd.end() && pos!=fd.begin()) { + fd.erase (pos); + // find the first item that is not under processing + for (pos=fd.begin(); pos!=fd.end(); pos++) + if (!(*pos)->processing) { + fd.insert (pos, entry); + break; + } } - saveBatchQueue( ); } + } + saveBatchQueue( ); redraw (); } void BatchQueue::tailItems (std::vector* items) { - { - // TODO: Check for Linux -#ifdef WIN32 - Glib::RWLock::WriterLock l(entryRW); -#endif - for (size_t i=0; isize(); i++) { - BatchQueueEntry* entry = (BatchQueueEntry*)(*items)[i]; - if (entry->processing) - continue; - std::vector::iterator pos = std::find (fd.begin(), fd.end(), entry); - if (pos!=fd.end()) { - fd.erase (pos); - fd.push_back (entry); - } + { + // TODO: Check for Linux + #if PROTECT_VECTORS + MYWRITERLOCK(l, entryRW); + #endif + + for (size_t i=0; isize(); i++) { + BatchQueueEntry* entry = (BatchQueueEntry*)(*items)[i]; + if (entry->processing) + continue; + std::vector::iterator pos = std::find (fd.begin(), fd.end(), entry); + if (pos!=fd.end()) { + fd.erase (pos); + fd.push_back (entry); } - saveBatchQueue( ); } + } + saveBatchQueue( ); redraw (); } @@ -442,13 +473,13 @@ void BatchQueue::tailItems (std::vector* items) { void BatchQueue::selectAll () { { // TODO: Check for Linux -#ifdef WIN32 - Glib::RWLock::ReaderLock l(entryRW); -#endif + #if PROTECT_VECTORS + MYWRITERLOCK(l, entryRW); + #endif lastClicked = NULL; selected.clear (); - for (size_t i=0; iprocessing) continue; fd[i]->selected = true; @@ -459,17 +490,18 @@ void BatchQueue::selectAll () { } void BatchQueue::startProcessing () { - if (!processing && !fd.empty()) { - BatchQueueEntry* next; - { - // TODO: Check for Linux - #ifdef WIN32 - Glib::RWLock::WriterLock l(entryRW); - #endif + if (!processing) { + // TODO: Check for Linux + #if PROTECT_VECTORS + MYWRITERLOCK(l, entryRW); + #endif - next = static_cast(fd[0]); - // tag it as processing + if (!fd.empty()) { + BatchQueueEntry* next; + + next = static_cast(fd[0]); + // tag it as processing next->processing = true; processing = next; // remove from selection @@ -480,13 +512,17 @@ void BatchQueue::startProcessing () { processing->selected = false; } + #if PROTECT_VECTORS + MYWRITERLOCK_RELEASE(l); + #endif + // remove button set next->removeButtonSet (); - } - // start batch processing - rtengine::startBatchProcessing (next->job, this, options.tunnelMetaData); - queue_draw (); + // start batch processing + rtengine::startBatchProcessing (next->job, this, options.tunnelMetaData); + queue_draw (); + } } } @@ -522,11 +558,11 @@ rtengine::ProcessingJob* BatchQueue::imageReady (rtengine::IImage16* img) { err = img->saveAsJPEG (fname, saveFormat.jpegQuality, saveFormat.jpegSubSamp); img->free (); - if (err) throw "Unable to save output file"; + if (err) throw "Unable to save output file"; if (saveFormat.saveParams) { - // We keep the extension to avoid overwriting the profile when we have - // the same output filename with different extension + // We keep the extension to avoid overwriting the profile when we have + // the same output filename with different extension //processing->params.save (removeExtension(fname) + paramFileExtension); processing->params.save (fname + ".out" + paramFileExtension); } @@ -542,11 +578,11 @@ rtengine::ProcessingJob* BatchQueue::imageReady (rtengine::IImage16* img) { // delete from the queue delete processing; processing = NULL; bool queueEmptied=false; - { + { // TODO: Check for Linux -#ifdef WIN32 - Glib::RWLock::WriterLock l(entryRW); -#endif + #if PROTECT_VECTORS + MYWRITERLOCK(l, entryRW); + #endif fd.erase (fd.begin()); @@ -555,8 +591,8 @@ rtengine::ProcessingJob* BatchQueue::imageReady (rtengine::IImage16* img) { queueEmptied=true; } else if (listener && listener->canStartNext ()) { - BatchQueueEntry* next = static_cast(fd[0]); - // tag it as selected + BatchQueueEntry* next = static_cast(fd[0]); + // tag it as selected next->processing = true; processing = next; // remove from selection @@ -567,21 +603,34 @@ rtengine::ProcessingJob* BatchQueue::imageReady (rtengine::IImage16* img) { processing->selected = false; } // remove button set + { + // ButtonSet have Cairo::Surface which might be rendered while we're trying to delete them + GThreadLock lock; next->removeButtonSet (); - } - if (saveBatchQueue( )) { - safe_g_remove( processedParams ); - // Delete all files in directory \batch when finished, just to be sure to remove zombies - if( fd.empty() ){ - std::vector names; - Glib::ustring batchdir = options.rtdir+"/batch/"; - Glib::RefPtr dir = Gio::File::create_for_path (batchdir); - safe_build_file_list (dir, names, batchdir); - for(std::vector::iterator iter=names.begin(); iter != names.end();iter++ ) - safe_g_remove( *iter ); } } } + if (saveBatchQueue( )) { + safe_g_remove( processedParams ); + // Delete all files in directory \batch when finished, just to be sure to remove zombies + + // Not sure that locking is necessary, but it should be safer + // TODO: Check for Linux + #if PROTECT_VECTORS + MYREADERLOCK(l, entryRW); + #endif + if( fd.empty() ){ + #if PROTECT_VECTORS + MYREADERLOCK_RELEASE(l); + #endif + std::vector names; + Glib::ustring batchdir = options.rtdir+"/batch/"; + Glib::RefPtr dir = Gio::File::create_for_path (batchdir); + safe_build_file_list (dir, names, batchdir); + for(std::vector::iterator iter=names.begin(); iter != names.end();iter++ ) + safe_g_remove( *iter ); + } + } redraw (); notifyListener (queueEmptied); @@ -728,6 +777,7 @@ void BatchQueue::setProgress (double p) { if (processing) processing->progress = p; + // No need to acquire the GUI, setProgressUI will do it g_idle_add (setProgressUI, this); } @@ -751,6 +801,7 @@ struct NLParams { }; int bqnotifylistenerUI (void* data) { + GThreadLock lock; // All GUI acces from idle_add callbacks or separate thread HAVE to be protected NLParams* params = static_cast(data); params->listener->queueSizeChanged (params->qsize, params->queueEmptied); delete params; @@ -762,13 +813,19 @@ void BatchQueue::notifyListener (bool queueEmptied) { if (listener) { NLParams* params = new NLParams; params->listener = listener; + { + // TODO: Check for Linux + #if PROTECT_VECTORS + MYREADERLOCK(l, entryRW); + #endif params->qsize = fd.size(); + } params->queueEmptied = queueEmptied; g_idle_add (bqnotifylistenerUI, params); } } void BatchQueue::redrawNeeded (LWButton* button) { - + GThreadLock lock; queue_draw (); } diff --git a/rtgui/batchqueue.h b/rtgui/batchqueue.h index 73d3de48f..e9bd78fd3 100644 --- a/rtgui/batchqueue.h +++ b/rtgui/batchqueue.h @@ -28,6 +28,7 @@ class BatchQueueListener { public: + virtual ~BatchQueueListener () {} virtual void queueSizeChanged (int qsize, bool queueEmptied) =0; virtual bool canStartNext () =0; }; @@ -39,6 +40,8 @@ class BatchQueue : public ThumbBrowserBase, protected: int getMaxThumbnailHeight() const; + void saveThumbnailHeight (int height); + int getThumbnailHeight (); BatchQueueEntry* processing; // holds the currently processed image @@ -61,15 +64,22 @@ class BatchQueue : public ThumbBrowserBase, BatchQueue (); ~BatchQueue (); - void addEntries (std::vector &entries, bool head=false); + void addEntries (std::vector &entries, bool head=false, bool save=true); void cancelItems (std::vector* items); void headItems (std::vector* items); void tailItems (std::vector* items); void selectAll (); void startProcessing (); - - bool hasJobs () { return (!fd.empty()); } + + bool hasJobs () { + // not sure that this lock is necessary, but it's safer to keep it... + // TODO: Check for Linux + #if PROTECT_VECTORS + MYREADERLOCK(l, entryRW); + #endif + return (!fd.empty()); + } rtengine::ProcessingJob* imageReady (rtengine::IImage16* img); void setProgress (double p); @@ -79,7 +89,8 @@ class BatchQueue : public ThumbBrowserBase, void setBatchQueueListener (BatchQueueListener* l) { listener = l; } - void loadBatchQueue (); + bool loadBatchQueue (); + void resizeLoadedQueue(); static Glib::ustring calcAutoFileNameBase (const Glib::ustring& origFileName); static int calcMaxThumbnailHeight(); diff --git a/rtgui/batchqueueentry.cc b/rtgui/batchqueueentry.cc index 8063fd411..aadb6196e 100644 --- a/rtgui/batchqueueentry.cc +++ b/rtgui/batchqueueentry.cc @@ -27,16 +27,16 @@ bool BatchQueueEntry::iconsLoaded(false); Glib::RefPtr BatchQueueEntry::savedAsIcon; -BatchQueueEntry::BatchQueueEntry (rtengine::ProcessingJob* pjob, const rtengine::procparams::ProcParams& pparams, Glib::ustring fname, guint8* previmg, int prevw, int prevh, Thumbnail* thm) +BatchQueueEntry::BatchQueueEntry (rtengine::ProcessingJob* pjob, const rtengine::procparams::ProcParams& pparams, Glib::ustring fname, int prevw, int prevh, Thumbnail* thm) : ThumbBrowserEntryBase(fname), - opreview(previmg), origpw(prevw), origph(prevh), + opreview(NULL), origpw(prevw), origph(prevh), opreviewDone(false), job(pjob), progress(0), outFileName(""), forceFormatOpts(false) { thumbnail=thm; params = pparams; - #ifndef WIN32 - // The BatchQueueEntryIdleHelper tracks if an entry has been deleted while it was sitting wating for "idle" + #if 1 //ndef WIN32 + // The BatchQueueEntryIdleHelper tracks if an entry has been deleted while it was sitting waiting for "idle" bqih = new BatchQueueEntryIdleHelper; bqih->bqentry = this; bqih->destroyed = false; @@ -55,24 +55,28 @@ BatchQueueEntry::BatchQueueEntry (rtengine::ProcessingJob* pjob, const rtengine: BatchQueueEntry::~BatchQueueEntry () { batchQueueEntryUpdater.removeJobs (this); - delete [] opreview; opreview=NULL; + if (opreview) delete [] opreview; opreview=NULL; if (thumbnail) thumbnail->decreaseRef (); - #ifndef WIN32 if (bqih->pending) bqih->destroyed = true; else delete bqih; - #endif } void BatchQueueEntry::refreshThumbnailImage () { - if (!opreview) - return; - - batchQueueEntryUpdater.process (opreview, origpw, origph, preh, this); // this will asynchronously land at this.updateImage + if (!opreviewDone) { + // creating the image buffer first + //if (!opreview) opreview = new guint8[(origpw+1) * origph * 3]; + // this will asynchronously compute the original preview and land at this.updateImage + batchQueueEntryUpdater.process (NULL, origpw, origph, preh, this, ¶ms, thumbnail); + } + else { + // this will asynchronously land at this.updateImage + batchQueueEntryUpdater.process (opreview, origpw, origph, preh, this); + } } void BatchQueueEntry::calcThumbnailSize () { @@ -166,8 +170,6 @@ Glib::ustring BatchQueueEntry::getToolTip (int x, int y) { } -#ifndef WIN32 - struct BQUpdateParam { BatchQueueEntryIdleHelper* bqih; guint8* img; @@ -180,11 +182,13 @@ int updateImageUIThread (void* data) { BatchQueueEntryIdleHelper* bqih = params->bqih; - // If the BQEntry was destroyed meanwhile, remove all the IdleHelper if all entries came through + GThreadLock tLock; // Acquire the GUI + + // If the BQEntry was destroyed meanwhile, remove all the IdleHelper if all entries came through if (bqih->destroyed) { if (bqih->pending == 1) delete bqih; - else + else bqih->pending--; delete [] params->img; delete params; @@ -198,35 +202,29 @@ int updateImageUIThread (void* data) { delete params; return 0; } -#endif // Starts a copy of img->preview via GTK thread -void BatchQueueEntry::updateImage (guint8* img, int w, int h) { - // TODO: Check for Linux/Mac -#ifdef WIN32 +void BatchQueueEntry::updateImage (guint8* img, int w, int h, int origw, int origh, guint8* newOPreview) { + // since the update itself is already called in an async thread and there are problem with accessing opreview in thumbbrowserbase, - // it's safer to do this synchrously + // it's safer to do this synchronously { GThreadLock lock; _updateImage(img,w,h); } -#else - bqih->pending++; - - BQUpdateParam* param = new BQUpdateParam (); - param->bqih = bqih; - param->img = img; - param->w = w; - param->h = h; - g_idle_add (updateImageUIThread, param); -#endif } void BatchQueueEntry::_updateImage (guint8* img, int w, int h) { if (preh == h) { + + #if PROTECT_VECTORS + MYWRITERLOCK(l, lockRW); + #endif + prew = w; + assert (preview == NULL); preview = new guint8 [prew*preh*3]; memcpy (preview, img, prew*preh*3); if (parent) diff --git a/rtgui/batchqueueentry.h b/rtgui/batchqueueentry.h index 7f2b7a56f..4df6388a9 100644 --- a/rtgui/batchqueueentry.h +++ b/rtgui/batchqueueentry.h @@ -37,6 +37,7 @@ class BatchQueueEntry : public ThumbBrowserEntryBase, public BQEntryUpdateListen guint8* opreview; int origpw, origph; BatchQueueEntryIdleHelper* bqih; + bool opreviewDone; static bool iconsLoaded; public: @@ -51,7 +52,7 @@ public: SaveFormat saveFormat; bool forceFormatOpts; - BatchQueueEntry (rtengine::ProcessingJob* job, const rtengine::procparams::ProcParams& pparams, Glib::ustring fname, guint8* previmg, int prevw, int prevh, Thumbnail* thm=NULL); + BatchQueueEntry (rtengine::ProcessingJob* job, const rtengine::procparams::ProcParams& pparams, Glib::ustring fname, int prevw, int prevh, Thumbnail* thm=NULL); ~BatchQueueEntry (); void refreshThumbnailImage (); @@ -66,7 +67,7 @@ public: virtual Glib::ustring getToolTip (int x, int y); // bqentryupdatelistener interface - void updateImage (guint8* img, int w, int h); + void updateImage (guint8* img, int w, int h, int origw, int origh, guint8* newOPreview); void _updateImage (guint8* img, int w, int h); // inside gtk thread }; diff --git a/rtgui/batchqueuepanel.cc b/rtgui/batchqueuepanel.cc index 04c8720fb..dcad1dd62 100644 --- a/rtgui/batchqueuepanel.cc +++ b/rtgui/batchqueuepanel.cc @@ -25,6 +25,16 @@ #include "../rtengine/safegtk.h" #include "rtimage.h" +struct BQProcessLoaded { + BatchQueue* bq; +}; + +int processLoadedBatchQueueUIThread (void* data) { + + BatchQueue* bq = static_cast(data); + bq->resizeLoadedQueue(); + return 0; +} BatchQueuePanel::BatchQueuePanel () { @@ -132,7 +142,9 @@ BatchQueuePanel::BatchQueuePanel () { batchQueue->setBatchQueueListener (this); show_all (); - batchQueue->loadBatchQueue (); + if (batchQueue->loadBatchQueue ()) { + g_idle_add_full (G_PRIORITY_LOW, processLoadedBatchQueueUIThread, batchQueue, NULL); + } } diff --git a/rtgui/batchtoolpanelcoord.cc b/rtgui/batchtoolpanelcoord.cc index 9db0c7861..8b60fa569 100644 --- a/rtgui/batchtoolpanelcoord.cc +++ b/rtgui/batchtoolpanelcoord.cc @@ -27,6 +27,7 @@ using namespace rtengine::procparams; BatchToolPanelCoordinator::BatchToolPanelCoordinator (FilePanel* parent) : ToolPanelCoordinator(), parent(parent) { + blockedUpdate = false; // remove exif panel and iptc panel std::vector::iterator epi = std::find (toolPanels.begin(), toolPanels.end(), exifpanel); if (epi!=toolPanels.end()) @@ -56,13 +57,13 @@ void BatchToolPanelCoordinator::selectionChanged (const std::vector& void BatchToolPanelCoordinator::closeSession (bool save) { - pparamsEdited.set (false); + pparamsEdited.set (false); for (size_t i=0; iremoveThumbnailListener (this); if (somethingChanged && save) { - + // read new values from the gui for (size_t i=0; iwrite (&pparams, &pparamsEdited); @@ -73,11 +74,11 @@ void BatchToolPanelCoordinator::closeSession (bool save) { newParams = initialPP[i]; pparamsEdited.combine (newParams, pparams, selected.size()==1); - // trim new adjuster's values to the adjuster's limits - for (unsigned int j=0; jtrimValues (&newParams); + // trim new adjuster's values to the adjuster's limits + for (unsigned int j=0; jtrimValues (&newParams); - selected[i]->setProcParams (newParams, NULL, BATCHEDITOR, true); + selected[i]->setProcParams (newParams, NULL, BATCHEDITOR, true); } } for (size_t i=0; i 50) // Arbitrary amount + parent->set_sensitive(false); +} + +// The end of a batch pparams change triggers a close/initsession +void BatchToolPanelCoordinator::endBatchPParamsChange() { + //printf("BatchToolPanelCoordinator::endBatchPParamsChange / Nouvelle session!\n"); + closeSession (false); + initSession (); + blockedUpdate = false; + parent->set_sensitive(true); +} + /* * WARNING: profileChange is actually called by the History only. * Using a Profile panel in the batch tool panel editor is actually diff --git a/rtgui/batchtoolpanelcoord.h b/rtgui/batchtoolpanelcoord.h index 9bf18c94c..b1db46294 100644 --- a/rtgui/batchtoolpanelcoord.h +++ b/rtgui/batchtoolpanelcoord.h @@ -30,6 +30,7 @@ class FilePanel; class BatchToolPanelCoordinator : public ToolPanelCoordinator, public FileSelectionChangeListener, + public BatchPParamsChangeListener, public ThumbnailListener { protected: @@ -39,6 +40,7 @@ class BatchToolPanelCoordinator : std::vector selFileNames; std::vector initialPP; bool somethingChanged; + bool blockedUpdate; FilePanel* parent; void closeSession (bool save=true); @@ -63,7 +65,11 @@ class BatchToolPanelCoordinator : // thumbnaillistener interface void procParamsChanged (Thumbnail* thm, int whoChangedIt); - + + // batchpparamschangelistener interface + void beginBatchPParamsChange(int numberOfEntries); + void endBatchPParamsChange(); + // imageareatoollistener interface void spotWBselected (int x, int y, Thumbnail* thm=NULL); void cropSelectionReady (); diff --git a/rtgui/bqentryupdater.cc b/rtgui/bqentryupdater.cc index f5463bda3..8c806f850 100644 --- a/rtgui/bqentryupdater.cc +++ b/rtgui/bqentryupdater.cc @@ -23,12 +23,17 @@ BatchQueueEntryUpdater batchQueueEntryUpdater; BatchQueueEntryUpdater::BatchQueueEntryUpdater () - : tostop(false), stopped(true), qMutex(NULL) { + : tostop(false), stopped(true), thread(NULL), qMutex(NULL) { } -void BatchQueueEntryUpdater::process (guint8* oimg, int ow, int oh, int newh, BQEntryUpdateListener* listener) { +void BatchQueueEntryUpdater::process (guint8* oimg, int ow, int oh, int newh, BQEntryUpdateListener* listener, rtengine::ProcParams* pparams, Thumbnail* thumbnail) { + if (!oimg && (!pparams || !thumbnail)) { + //printf("WARNING! !oimg && (!pparams || !thumbnail)\n"); + return; + } + if (!qMutex) - qMutex = new Glib::Mutex (); + qMutex = new Glib::Mutex (); qMutex->lock (); // look up if an older version is in the queue @@ -39,6 +44,8 @@ void BatchQueueEntryUpdater::process (guint8* oimg, int ow, int oh, int newh, BQ i->oh = oh; i->newh = newh; i->listener = listener; + i->pparams = pparams; + i->thumbnail = thumbnail; break; } @@ -50,6 +57,8 @@ void BatchQueueEntryUpdater::process (guint8* oimg, int ow, int oh, int newh, BQ j.oh = oh; j.newh = newh; j.listener = listener; + j.pparams = pparams; + j.thumbnail = thumbnail; jqueue.push_back (j); } qMutex->unlock (); @@ -79,11 +88,37 @@ void BatchQueueEntryUpdater::processThread () { } qMutex->unlock (); - if (!isEmpty && current.listener) { + rtengine::IImage8* img = NULL; + bool newBuffer = false; + if (current.thumbnail && current.pparams) { + // the thumbnail and the pparams are provided, it means that we have to build the original preview image + double tmpscale; + img = current.thumbnail->processThumbImage (*current.pparams, current.oh, tmpscale); + //current.thumbnail->decreaseRef (); // WARNING: decreasing refcount (and maybe deleting) thumbnail, with or without processed image + if (img) { + int prevw = img->getWidth(); + int prevh = img->getHeight(); +#ifndef NDEBUG + if (current.ow != img->getW() || current.oh != img->getH()) + printf("WARNING! Expected image size: %dx%d ; image size is: %dx%d\n", current.ow, current.oh, img->getW(), img->getH()); + assert ((current.ow+1)*current.oh >= img->getW()*img->getH()); +#endif + current.ow = prevw; + current.oh = prevh; + if (!current.oimg) { + current.oimg = new guint8[prevw*prevh*3]; + newBuffer = true; + } + memcpy(current.oimg, img->getData(), prevw * prevh * 3); + img->free(); + } + } + + if (current.oimg && !isEmpty && current.listener) { int neww = current.newh * current.ow / current.oh; guint8* img = new guint8 [current.newh*neww*3]; thumbInterp (current.oimg, current.ow, current.oh, img, neww, current.newh); - current.listener->updateImage (img, neww, current.newh); + current.listener->updateImage (img, neww, current.newh, current.ow, current.oh, newBuffer?current.oimg:NULL); } } @@ -114,12 +149,11 @@ void BatchQueueEntryUpdater::terminate () { if (!qMutex || stopped) return; if (!stopped) { - // Yield to currenly running thread and wait till it's finished - gdk_threads_leave(); - tostop = true; + // Yield to currently running thread and wait till it's finished + GThreadUnLock(); + tostop = true; Glib::Thread::self()->yield(); if (!stopped) thread->join (); - gdk_threads_enter(); } // Remove remaining jobs diff --git a/rtgui/bqentryupdater.h b/rtgui/bqentryupdater.h index affc088ce..058486e09 100644 --- a/rtgui/bqentryupdater.h +++ b/rtgui/bqentryupdater.h @@ -26,7 +26,8 @@ class BQEntryUpdateListener { public: - virtual void updateImage (guint8* img, int w, int h) {} + virtual ~BQEntryUpdateListener () {} + virtual void updateImage (guint8* img, int w, int h, int origw, int origh, guint8* newOPreview) {} }; class BatchQueueEntryUpdater { @@ -35,6 +36,8 @@ class BatchQueueEntryUpdater { guint8* oimg; int ow, oh, newh; BQEntryUpdateListener* listener; + rtengine::ProcParams* pparams; + Thumbnail* thumbnail; }; protected: @@ -47,7 +50,7 @@ class BatchQueueEntryUpdater { public: BatchQueueEntryUpdater (); - void process (guint8* oimg, int ow, int oh, int newh, BQEntryUpdateListener* listener); + void process (guint8* oimg, int ow, int oh, int newh, BQEntryUpdateListener* listener, rtengine::ProcParams* pparams=NULL, Thumbnail* thumbnail=NULL); void removeJobs (BQEntryUpdateListener* listener); void terminate (); diff --git a/rtgui/cacheimagedata.cc b/rtgui/cacheimagedata.cc index 9b207a160..0846ebcb4 100644 --- a/rtgui/cacheimagedata.cc +++ b/rtgui/cacheimagedata.cc @@ -33,8 +33,13 @@ int CacheImageData::load (const Glib::ustring& fname) { rtengine::SafeKeyFile keyFile; try { - if (!keyFile.load_from_file (fname)) + bool loaded = keyFile.load_from_file (fname); + if (!loaded) { +#ifndef NDEBUG + printf("Failed to load_from_file(%s)\n", fname.c_str()); +#endif return 1; + } if (keyFile.has_group ("General")) { if (keyFile.has_key ("General", "MD5")) md5 = keyFile.get_string ("General", "MD5"); @@ -58,32 +63,31 @@ int CacheImageData::load (const Glib::ustring& fname) { if (keyFile.has_key ("DateTime", "MSec")) msec = keyFile.get_integer ("DateTime", "MSec"); } - exifValid = false; - - if (keyFile.has_group ("ExifInfo")) { - exifValid = true; - if (keyFile.has_key ("ExifInfo", "Valid")) exifValid = keyFile.get_boolean ("ExifInfo", "Valid"); + exifValid = false; - if (exifValid) { - if (keyFile.has_key ("ExifInfo", "FNumber")) fnumber = keyFile.get_double ("ExifInfo", "FNumber"); - if (keyFile.has_key ("ExifInfo", "Shutter")) shutter = keyFile.get_double ("ExifInfo", "Shutter"); - if (keyFile.has_key ("ExifInfo", "FocalLen")) focalLen = keyFile.get_double ("ExifInfo", "FocalLen"); + if (keyFile.has_group ("ExifInfo")) { + exifValid = true; + if (keyFile.has_key ("ExifInfo", "Valid")) exifValid = keyFile.get_boolean ("ExifInfo", "Valid"); + + if (exifValid) { + if (keyFile.has_key ("ExifInfo", "FNumber")) fnumber = keyFile.get_double ("ExifInfo", "FNumber"); + if (keyFile.has_key ("ExifInfo", "Shutter")) shutter = keyFile.get_double ("ExifInfo", "Shutter"); + if (keyFile.has_key ("ExifInfo", "FocalLen")) focalLen = keyFile.get_double ("ExifInfo", "FocalLen"); if (keyFile.has_key ("ExifInfo", "FocalLen35mm")) focalLen35mm = keyFile.get_double ("ExifInfo", "FocalLen35mm"); else focalLen35mm=focalLen; // prevent crashes on old files if (keyFile.has_key ("ExifInfo", "FocusDist")) focusDist = keyFile.get_double ("ExifInfo", "FocusDist"); else focusDist=0; - if (keyFile.has_key ("ExifInfo", "ISO")) iso = keyFile.get_integer ("ExifInfo", "ISO"); - if (keyFile.has_key ("ExifInfo", "ExpComp")) expcomp = keyFile.get_string ("ExifInfo", "ExpComp"); - } - if (keyFile.has_key ("ExifInfo", "Lens")) lens = keyFile.get_string ("ExifInfo", "Lens"); - if (keyFile.has_key ("ExifInfo", "Camera")) camera = keyFile.get_string ("ExifInfo", "Camera"); - } - - if (keyFile.has_group ("FileInfo")) { - if (keyFile.has_key ("FileInfo", "Filetype")) filetype = keyFile.get_string ("FileInfo", "Filetype"); - } - - + if (keyFile.has_key ("ExifInfo", "ISO")) iso = keyFile.get_integer ("ExifInfo", "ISO"); + if (keyFile.has_key ("ExifInfo", "ExpComp")) expcomp = keyFile.get_string ("ExifInfo", "ExpComp"); + } + if (keyFile.has_key ("ExifInfo", "Lens")) lens = keyFile.get_string ("ExifInfo", "Lens"); + if (keyFile.has_key ("ExifInfo", "Camera")) camera = keyFile.get_string ("ExifInfo", "Camera"); + } + + if (keyFile.has_group ("FileInfo")) { + if (keyFile.has_key ("FileInfo", "Filetype")) filetype = keyFile.get_string ("FileInfo", "Filetype"); + } + if (format==FT_Raw && keyFile.has_group ("ExtraRawInfo")) { if (keyFile.has_key ("ExtraRawInfo", "ThumbImageType")) thumbImgType = keyFile.get_integer ("ExtraRawInfo", "ThumbImageType"); if (keyFile.has_key ("ExtraRawInfo", "ThumbImageOffset")) thumbOffset = keyFile.get_integer ("ExtraRawInfo", "ThumbImageOffset"); @@ -94,9 +98,10 @@ int CacheImageData::load (const Glib::ustring& fname) { } return 0; } - catch (Glib::Error) { - return 1; + catch (Glib::Error &err) { + printf("Error code %d while reading values from \"%s\":\n%s\n", err.code(), fname.c_str(), err.what().c_str()); } + return 1; } int CacheImageData::save (const Glib::ustring& fname) { @@ -151,5 +156,6 @@ int CacheImageData::save (const Glib::ustring& fname) { fprintf (f, "%s", keyFile.to_data().c_str()); fclose (f); return 0; - }} + } +} diff --git a/rtgui/cachemanager.cc b/rtgui/cachemanager.cc index 9100d5323..c392f6f04 100644 --- a/rtgui/cachemanager.cc +++ b/rtgui/cachemanager.cc @@ -64,7 +64,11 @@ void CacheManager::init () { safe_g_mkdir_with_parents (Glib::ustring(Glib::build_filename (baseDir, "data")), 511); } -Thumbnail* CacheManager::getEntry (const Glib::ustring& fname) { +/* + * if pparams is NULL (default), get the ProcParams from the cache; + * if pparams is non NULL, compute the thumbnails with the provided pparams + */ +Thumbnail* CacheManager::getEntry (const Glib::ustring& fname, const rtengine::procparams::ProcParams *pparams) { Thumbnail* res = NULL; @@ -93,8 +97,8 @@ Thumbnail* CacheManager::getEntry (const Glib::ustring& fname) { if (safe_file_test (cfname, Glib::FILE_TEST_EXISTS)) { CacheImageData* cfs = new CacheImageData (); int e = cfs->load (cfname); - if (!e && cfs->supported==true) - res = new Thumbnail (this, fname, cfs); + if (!e && cfs->supported==true) + res = new Thumbnail (this, fname, cfs, pparams); if (res && !res->isSupported ()) { delete res; res = NULL; @@ -104,7 +108,7 @@ Thumbnail* CacheManager::getEntry (const Glib::ustring& fname) { // if not, create a new one if (!res) { - res = new Thumbnail (this, fname, md5); + res = new Thumbnail (this, fname, md5, pparams); if (!res->isSupported ()) { delete res; res = NULL; diff --git a/rtgui/cachemanager.h b/rtgui/cachemanager.h index aa441706c..a592b8f07 100644 --- a/rtgui/cachemanager.h +++ b/rtgui/cachemanager.h @@ -24,6 +24,7 @@ #include #include "thumbnail.h" #include +#include "../rtengine/procparams.h" class Thumbnail; @@ -45,7 +46,7 @@ class CacheManager { static CacheManager* getInstance(void); void init (); - Thumbnail* getEntry (const Glib::ustring& fname); + Thumbnail* getEntry (const Glib::ustring& fname, const rtengine::procparams::ProcParams *pparams=NULL); void deleteEntry (const Glib::ustring& fname); void renameEntry (const std::string& oldfilename, const std::string& oldmd5, const std::string& newfilename); diff --git a/rtgui/colorappearance.cc b/rtgui/colorappearance.cc index 1e05a4150..7e2b088f2 100644 --- a/rtgui/colorappearance.cc +++ b/rtgui/colorappearance.cc @@ -893,14 +893,14 @@ void ColorAppearance::setDefaults (const ProcParams* defParams, const ParamsEdit } } int autoCamChangedUI (void* data) { + GThreadLock lock; // All GUI acces from idle_add callbacks or separate thread HAVE to be protected (static_cast(data))->autoCamComputed_ (); return 0; } void ColorAppearance::autoCamChanged (double ccam) { nextCcam = ccam; - g_idle_add (autoCamChangedUI, this); - // Glib::signal_idle().connect (sigc::mem_fun(*this, &ColorAppearance::autoCamComputed_)); + g_idle_add (autoCamChangedUI, this); } bool ColorAppearance::autoCamComputed_ () { @@ -913,14 +913,14 @@ bool ColorAppearance::autoCamComputed_ () { return false; } int adapCamChangedUI (void* data) { + GThreadLock lock; // All GUI acces from idle_add callbacks or separate thread HAVE to be protected (static_cast(data))->adapCamComputed_ (); return 0; } void ColorAppearance::adapCamChanged (double cadap) { nextCadap = cadap; - g_idle_add (adapCamChangedUI, this); - // Glib::signal_idle().connect (sigc::mem_fun(*this, &ColorAppearance::autoCamComputed_)); + g_idle_add (adapCamChangedUI, this); } bool ColorAppearance::adapCamComputed_ () { diff --git a/rtgui/crop.cc b/rtgui/crop.cc index 3fbdfed3e..e68dff804 100644 --- a/rtgui/crop.cc +++ b/rtgui/crop.cc @@ -469,11 +469,13 @@ void Crop::enabledChanged () { } int notifyListenerUI (void* data) { + GThreadLock lock; // All GUI acces from idle_add callbacks or separate thread HAVE to be protected (static_cast(data))->notifyListener (); return 0; } int refreshSpinsUI (void* data) { + GThreadLock lock; // All GUI acces from idle_add callbacks or separate thread HAVE to be protected RefreshSpinHelper* rsh = static_cast(data); rsh->crop->refreshSpins (rsh->notify); delete rsh; @@ -587,23 +589,23 @@ void Crop::ratioChanged () { // Correct current crop if it doesn't fit void Crop::adjustCropToRatio() { - if (fixr->get_active() && !fixr->get_inconsistent()) { + if (fixr->get_active() && !fixr->get_inconsistent()) { -// int W = w->get_value (); -// int H = h->get_value (); - int W = nw; - int H = nh; - int X = nx; - int Y = ny; - if (W>=H) - cropWidth2Resized (X, Y, W, H); - else - cropHeight2Resized (X, Y, W, H); +// int W = w->get_value (); +// int H = h->get_value (); + int W = nw; + int H = nh; + int X = nx; + int Y = ny; + if (W>=H) + cropWidth2Resized (X, Y, W, H); + else + cropHeight2Resized (X, Y, W, H); } - // This will safe the options + // This will save the options g_idle_add (refreshSpinsUI, new RefreshSpinHelper (this, true)); - } +} void Crop::refreshSize () { @@ -667,6 +669,7 @@ struct setdimparams { }; int sizeChangedUI (void* data) { + GThreadLock lock; // All GUI acces from idle_add callbacks or separate thread HAVE to be protected setdimparams* params = static_cast(data); params->crop->setDimensions (params->x, params->y); delete params; @@ -958,12 +961,11 @@ void Crop::cropResized (int &x, int &y, int& x2, int& y2) { nh = H; g_idle_add (refreshSpinsUI, new RefreshSpinHelper (this, false)); -// Glib::signal_idle().connect (sigc::mem_fun(*this, &Crop::refreshSpins)); } void Crop::cropManipReady () { - g_idle_add (notifyListenerUI, this); + g_idle_add (notifyListenerUI, this); } double Crop::getRatio () { diff --git a/rtgui/crophandler.cc b/rtgui/crophandler.cc index 792262216..4f612ccd0 100644 --- a/rtgui/crophandler.cc +++ b/rtgui/crophandler.cc @@ -186,11 +186,11 @@ int createpixbufs (void* data) { if (!ch->enabled) { delete [] ch->cropimg; ch->cropimg = NULL; - delete [] ch->cropimgtrue; + delete [] ch->cropimgtrue; ch->cropimgtrue = NULL; ch->cimg.unlock (); return 0; - } + } if (ch->cropimg) { if (ch->cix==ch->cropX && ch->ciy==ch->cropY && ch->ciw==ch->cropW && ch->cih==ch->cropH && ch->cis==(ch->zoom>=1000?1:ch->zoom)) { @@ -207,15 +207,15 @@ int createpixbufs (void* data) { ch->cropPixbuf = Gdk::Pixbuf::create (Gdk::COLORSPACE_RGB, false, 8, imw, imh); tmpPixbuf->scale (ch->cropPixbuf, 0, 0, imw, imh, 0, 0, czoom/1000.0, czoom/1000.0, Gdk::INTERP_NEAREST); tmpPixbuf.clear (); - - Glib::RefPtr tmpPixbuftrue = Gdk::Pixbuf::create_from_data (ch->cropimgtrue, Gdk::COLORSPACE_RGB, false, 8, ch->cropimg_width, 2*ch->cropimg_height, 3*ch->cropimg_width); + + Glib::RefPtr tmpPixbuftrue = Gdk::Pixbuf::create_from_data (ch->cropimgtrue, Gdk::COLORSPACE_RGB, false, 8, ch->cropimg_width, 2*ch->cropimg_height, 3*ch->cropimg_width); ch->cropPixbuftrue = Gdk::Pixbuf::create (Gdk::COLORSPACE_RGB, false, 8, imw, imh); tmpPixbuftrue->scale (ch->cropPixbuftrue, 0, 0, imw, imh, 0, 0, czoom/1000.0, czoom/1000.0, Gdk::INTERP_NEAREST); tmpPixbuftrue.clear (); } delete [] ch->cropimg; ch->cropimg = NULL; - delete [] ch->cropimgtrue; + delete [] ch->cropimgtrue; ch->cropimgtrue = NULL; } ch->cimg.unlock (); @@ -233,7 +233,7 @@ int createpixbufs (void* data) { } void CropHandler::setDetailedCrop (IImage8* im, IImage8* imtrue, rtengine::procparams::ColorManagementParams cmp, - rtengine::procparams::CropParams cp, int ax, int ay, int aw, int ah, int askip) { + rtengine::procparams::CropParams cp, int ax, int ay, int aw, int ah, int askip) { if (!enabled) return; @@ -255,9 +255,9 @@ void CropHandler::setDetailedCrop (IImage8* im, IImage8* imtrue, rtengine::procp cropimg_width = im->getWidth (); cropimg_height = im->getHeight (); cropimg = new unsigned char [3*cropimg_width*cropimg_height]; - cropimgtrue = new unsigned char [3*cropimg_width*cropimg_height]; + cropimgtrue = new unsigned char [3*cropimg_width*cropimg_height]; memcpy (cropimg, im->getData(), 3*cropimg_width*cropimg_height); - memcpy (cropimgtrue, imtrue->getData(), 3*cropimg_width*cropimg_height); + memcpy (cropimgtrue, imtrue->getData(), 3*cropimg_width*cropimg_height); cix = ax; ciy = ay; ciw = aw; diff --git a/rtgui/dirbrowser.cc b/rtgui/dirbrowser.cc index 3c5a7b972..88d73c1d4 100644 --- a/rtgui/dirbrowser.cc +++ b/rtgui/dirbrowser.cc @@ -29,9 +29,12 @@ #include "rtimage.h" #define CHECKTIME 5000 -extern Glib::ustring argv0; -DirBrowser::DirBrowser () { +DirBrowser::DirBrowser () : expandSuccess(false) + #ifdef WIN32 + , volumes(0) + #endif +{ dirtree = Gtk::manage ( new Gtk::TreeView() ); scrolledwindow4 = Gtk::manage ( new Gtk::ScrolledWindow() ); @@ -144,7 +147,7 @@ void DirBrowser::updateVolumes () { int nvolumes = GetLogicalDrives (); if (nvolumes!=volumes) { - GThreadLock lock; + GThreadLock lock; for (int i=0; i<32; i++) if (((volumes >> i) & 1) && !((nvolumes >> i) & 1)) { // volume i has been deleted @@ -237,23 +240,26 @@ void DirBrowser::updateDir (const Gtk::TreeModel::iterator& iter) { for (Gtk::TreeModel::iterator it=iter->children().begin(); it!=iter->children().end(); it++) if (!safe_file_test (it->get_value (dtColumns.dirname), Glib::FILE_TEST_EXISTS) || !safe_file_test (it->get_value (dtColumns.dirname), Glib::FILE_TEST_IS_DIR)) { + GThreadLock lock; dirTreeModel->erase (it); change = true; break; } } // test if new files are created - std::vector subDirs; + std::vector subDirs; Glib::RefPtr dir = Gio::File::create_for_path (iter->get_value (dtColumns.dirname)); - safe_build_subdir_list (dir, subDirs, options.fbShowHidden); + safe_build_subdir_list (dir, subDirs, options.fbShowHidden); for (int i=0; ichildren().begin(); it!=iter->children().end() && !found ; it++) found = (it->get_value (dtColumns.filename)==subDirs[i]); - if (!found) + if (!found) { + GThreadLock lock; addDir (iter, subDirs[i]); + } } } @@ -353,9 +359,7 @@ void DirBrowser::file_changed (const Glib::RefPtr& file, const Glib:: if (!file || !safe_file_test (dirName, Glib::FILE_TEST_IS_DIR) || event_type==Gio::FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED) return; - gdk_threads_enter(); updateDir (iter); - gdk_threads_leave(); } void DirBrowser::selectDir (Glib::ustring dir) { diff --git a/rtgui/dirselectionlistener.h b/rtgui/dirselectionlistener.h index 6dd09a3a6..2c1eaa0ed 100644 --- a/rtgui/dirselectionlistener.h +++ b/rtgui/dirselectionlistener.h @@ -24,6 +24,7 @@ class DirSelectionListener { public: + virtual ~DirSelectionListener () {} virtual void dirSelected (const Glib::ustring& dirname, const Glib::ustring& openfile="") {} }; diff --git a/rtgui/editorpanel.cc b/rtgui/editorpanel.cc index 00e420b08..91d2ea371 100644 --- a/rtgui/editorpanel.cc +++ b/rtgui/editorpanel.cc @@ -597,6 +597,7 @@ struct spparams { int setprogressStrUI( void *p ) { + GThreadLock lock; // All GUI acces from idle_add callbacks or separate thread HAVE to be protected spparams *s= static_cast(p); if( ! s->str.empty() ) @@ -662,6 +663,7 @@ void EditorPanel::refreshProcessingState (bool inProcessingP) { s->val = 1.0; #ifdef WIN32 + // Maybe accessing "parent", which is a Gtk object, can justify to get the Gtk lock... if (!firstProcessingDone && static_cast(parent)->getIsFullscreen()) { parent->fullscreen(); } #endif firstProcessingDone = true; @@ -1108,19 +1110,11 @@ BatchQueueEntry* EditorPanel::createBatchQueueEntry () { ipc->getParams (&pparams); //rtengine::ProcessingJob* job = rtengine::ProcessingJob::create (ipc->getInitialImage(), pparams); rtengine::ProcessingJob* job = rtengine::ProcessingJob::create (openThm->getFileName (), openThm->getType()==FT_Raw, pparams); - int prevh = options.maxThumbnailHeight; - int prevw = prevh; - guint8* prev = NULL;//(guint8*) previewHandler->getImagePreview (prevw, prevh); - double tmpscale; - rtengine::IImage8* img = openThm->processThumbImage (pparams, options.maxThumbnailHeight, tmpscale); - if (img) { - prevw = img->getWidth (); - prevh = img->getHeight (); - prev = new guint8 [prevw*prevh*3]; - memcpy (prev, img->getData (), prevw*prevh*3); - img->free(); - } - return new BatchQueueEntry (job, pparams, openThm->getFileName(), prev, prevw, prevh, openThm); + int fullW=0, fullH=0; + isrc->getImageSource()->getFullSize(fullW, fullH, pparams.coarse.rotate==90 || pparams.coarse.rotate==270 ? TR_R90 : TR_NONE); + int prevh = BatchQueue::calcMaxThumbnailHeight(); + int prevw = int((size_t)fullW * (size_t)prevh / (size_t)fullH); + return new BatchQueueEntry (job, pparams, openThm->getFileName(), prevw, prevh, openThm); } diff --git a/rtgui/filebrowser.cc b/rtgui/filebrowser.cc index 8ed06a437..805c0f7cc 100644 --- a/rtgui/filebrowser.cc +++ b/rtgui/filebrowser.cc @@ -29,6 +29,7 @@ #include "../rtengine/dfmanager.h" #include "../rtengine/ffmanager.h" #include "rtimage.h" +#include "guiutils.h" extern Options options; @@ -106,7 +107,7 @@ FileBrowser::FileBrowser () /*********************** * external programs * *********************/ -#ifdef WIN32 +#if PROTECT_VECTORS Gtk::manage(miOpenDefaultViewer=new Gtk::MenuItem (M("FILEBROWSER_OPENDEFAULTVIEWER"))); #else miOpenDefaultViewer=NULL; @@ -301,6 +302,12 @@ FileBrowser::~FileBrowser () void FileBrowser::rightClicked (ThumbBrowserEntryBase* entry) { + { + // TODO: Check for Linux + #if PROTECT_VECTORS + MYREADERLOCK(l, entryRW); + #endif + trash->set_sensitive (false); untrash->set_sensitive (false); for (size_t i=0; iset_sensitive (clipboard.hasProcParams()); copyprof->set_sensitive (selected.size()==1); clearprof->set_sensitive (!selected.empty()); + } // submenu applmenu int p = 0; @@ -336,10 +344,10 @@ void FileBrowser::rightClicked (ThumbBrowserEntryBase* entry) { Gtk::Menu* applpartmenu = Gtk::manage (new Gtk::Menu ()); //std::vector profnames = profileStore.getProfileNames (); // this is already created for submenu applmenu above for (size_t i=0; iattach (*mi, 0, 1, p, p+1); p++; - mi->signal_activate().connect (sigc::bind(sigc::mem_fun(*this, &FileBrowser::applyPartialMenuItemActivated), profnames[i])); - mi->show (); + Gtk::MenuItem* mi = Gtk::manage (new Gtk::MenuItem (profnames[i])); + applpartmenu->attach (*mi, 0, 1, p, p+1); p++; + mi->signal_activate().connect (sigc::bind(sigc::mem_fun(*this, &FileBrowser::applyPartialMenuItemActivated), profnames[i])); + mi->show (); } applypartprof->set_submenu (*applpartmenu); @@ -428,7 +436,7 @@ void FileBrowser::addEntry (FileBrowserEntry* entry) { } void FileBrowser::addEntry_ (FileBrowserEntry* entry) { - + GThreadLock lock; // All GUI acces from idle_add callbacks or separate thread HAVE to be protected entry->selected = false; entry->drawable = false; entry->framed = editedFiles.find (entry->filename)!=editedFiles.end(); @@ -439,14 +447,14 @@ void FileBrowser::addEntry_ (FileBrowserEntry* entry) { entry->getThumbButtonSet()->setColorLabel (entry->thumbnail->getColorLabel()); entry->getThumbButtonSet()->setInTrash (entry->thumbnail->getStage()==1); entry->getThumbButtonSet()->setButtonListener (this); - entry->resize (getCurrentThumbSize()); + entry->resize (getThumbnailHeight()); // find place in abc order - { - // TODO: Check for Linux - #ifdef WIN32 - Glib::RWLock::WriterLock l(entryRW); - #endif + { + // TODO: Check for Linux + #if PROTECT_VECTORS + MYWRITERLOCK(l, entryRW); + #endif std::vector::iterator i = fd.begin(); while (i!=fd.end() && *entry < *((FileBrowserEntry*)*i)) @@ -455,15 +463,15 @@ void FileBrowser::addEntry_ (FileBrowserEntry* entry) { fd.insert (i, entry); initEntry (entry); - } + } redraw (); } FileBrowserEntry* FileBrowser::delEntry (const Glib::ustring& fname) { - // TODO: Check for Linux - #ifdef WIN32 - Glib::RWLock::WriterLock l(entryRW); - #endif + // TODO: Check for Linux + #if PROTECT_VECTORS + MYWRITERLOCK(l, entryRW); + #endif for (std::vector::iterator i=fd.begin(); i!=fd.end(); i++) if ((*i)->filename==fname) { @@ -472,8 +480,8 @@ FileBrowserEntry* FileBrowser::delEntry (const Glib::ustring& fname) { fd.erase (i); std::vector::iterator j = std::find (selected.begin(), selected.end(), entry); - #ifdef WIN32 - l.release(); + #if PROTECT_VECTORS + MYWRITERLOCK_RELEASE(l); #endif if (j!=selected.end()) { @@ -486,7 +494,7 @@ FileBrowserEntry* FileBrowser::delEntry (const Glib::ustring& fname) { lastClicked = NULL; redraw (); - return (static_cast(entry)); + return (static_cast(entry)); } return NULL; } @@ -502,22 +510,33 @@ void FileBrowser::close () { fbih->destroyed = false; fbih->pending = 0; - { - // TODO: Check for Linux - #ifdef WIN32 - Glib::RWLock::WriterLock l(entryRW); - #endif - - selected.clear (); - notifySelectionListener (); + { + // TODO: Check for Linux + #if PROTECT_VECTORS + MYWRITERLOCK(l, entryRW); + #endif + + selected.clear (); + + // TODO: Check for Linux + #if PROTECT_VECTORS + MYWRITERLOCK_RELEASE(l); // notifySelectionListener will need read access! + #endif + + notifySelectionListener (); + + // TODO: Check for Linux + #if PROTECT_VECTORS + MYWRITERLOCK_ACQUIRE(l); + #endif // The listener merges parameters with old values, so delete afterwards - for (size_t i=0; i mselected; + + { + // TODO: Check for Linux + #if PROTECT_VECTORS + MYREADERLOCK(l, entryRW); + #endif + for (size_t i=0; i(selected[i])); + mselected.push_back (static_cast(selected[i])); + } + if (!tbl || (m!=selall && mselected.empty()) ) return; @@ -560,8 +588,8 @@ void FileBrowser::menuItemActivated (Gtk::MenuItem* m) { // Build vector of all file names std::vector selFileNames; - for (int i=0; ithumbnail->getFileName(); + for (int i=0; ithumbnail->getFileName(); // Maybe batch processed version if (pAct->target==2) fn = Glib::ustring::compose ("%1.%2", BatchQueue::calcAutoFileNameBase(fn), options.saveFormatBatch.format); @@ -576,7 +604,7 @@ void FileBrowser::menuItemActivated (Gtk::MenuItem* m) { if (m==open) { std::vector entries; - for (size_t i=0; ithumbnail); tbl->openRequested (entries); } @@ -598,19 +626,19 @@ void FileBrowser::menuItemActivated (Gtk::MenuItem* m) { tbl->renameRequested (mselected); else if (m==selall) { lastClicked = NULL; - { - // TODO: Check for Linux - #ifdef WIN32 - Glib::RWLock::ReaderLock l(entryRW); - #endif + { + // TODO: Check for Linux + #if PROTECT_VECTORS + MYWRITERLOCK(l, entryRW); + #endif selected.clear (); - for (size_t i=0; iselected = true; selected.push_back (fd[i]); } - } + } queue_draw (); notifySelectionListener (); } @@ -623,86 +651,103 @@ void FileBrowser::menuItemActivated (Gtk::MenuItem* m) { } else if (m==autoDF){ - for (size_t i=0; ithumbnail->getProcParams(); - pp.raw.df_autoselect= true; - pp.raw.dark_frame.clear(); - mselected[i]->thumbnail->setProcParams(pp,NULL,FILEBROWSER,false); - } + if (!mselected.empty() && bppcl) + bppcl->beginBatchPParamsChange(mselected.size()); + for (size_t i=0; ithumbnail->getProcParams(); + pp.raw.df_autoselect= true; + pp.raw.dark_frame.clear(); + mselected[i]->thumbnail->setProcParams(pp,NULL,FILEBROWSER,false); + } + if (!mselected.empty() && bppcl) + bppcl->endBatchPParamsChange(); + }else if (m==selectDF){ - if( !mselected.empty() ){ - rtengine::procparams::ProcParams pp=mselected[0]->thumbnail->getProcParams(); - Gtk::FileChooserDialog fc("Dark Frame",Gtk::FILE_CHOOSER_ACTION_OPEN ); - FileChooserLastFolderPersister persister(&fc, options.lastDarkframeDir); - fc.add_button( Gtk::StockID("gtk-cancel"), Gtk::RESPONSE_CANCEL); - fc.add_button( Gtk::StockID("gtk-apply"), Gtk::RESPONSE_APPLY); - if(!pp.raw.dark_frame.empty()) - fc.set_filename( pp.raw.dark_frame ); - if( fc.run() == Gtk::RESPONSE_APPLY ){ - for (size_t i=0; ithumbnail->getProcParams(); - pp.raw.dark_frame= fc.get_filename(); - pp.raw.df_autoselect= false; - mselected[i]->thumbnail->setProcParams(pp,NULL,FILEBROWSER,false); - } - } - } + if( !mselected.empty() ){ + rtengine::procparams::ProcParams pp=mselected[0]->thumbnail->getProcParams(); + Gtk::FileChooserDialog fc("Dark Frame",Gtk::FILE_CHOOSER_ACTION_OPEN ); + FileChooserLastFolderPersister persister(&fc, options.lastDarkframeDir); + fc.add_button( Gtk::StockID("gtk-cancel"), Gtk::RESPONSE_CANCEL); + fc.add_button( Gtk::StockID("gtk-apply"), Gtk::RESPONSE_APPLY); + if(!pp.raw.dark_frame.empty()) + fc.set_filename( pp.raw.dark_frame ); + if( fc.run() == Gtk::RESPONSE_APPLY ) { + if (bppcl) + bppcl->beginBatchPParamsChange(mselected.size()); + for (size_t i=0; ithumbnail->getProcParams(); + pp.raw.dark_frame= fc.get_filename(); + pp.raw.df_autoselect= false; + mselected[i]->thumbnail->setProcParams(pp,NULL,FILEBROWSER,false); + } + if (bppcl) + bppcl->endBatchPParamsChange(); + } + } }else if( m==thisIsDF){ - if( !options.rtSettings.darkFramesPath.empty()) { - if (Gio::File::create_for_path(options.rtSettings.darkFramesPath)->query_exists() ){ - for (size_t i=0; i file = Gio::File::create_for_path ( mselected[i]->filename ); - if( !file )continue; - Glib::ustring destName = options.rtSettings.darkFramesPath+ "/" + file->get_basename(); - Glib::RefPtr dest = Gio::File::create_for_path ( destName ); - file->move( dest ); - } - // Reinit cache - rtengine::dfm.init( options.rtSettings.darkFramesPath ); - } - else { - // Target directory creation failed, we clear the darkFramesPath setting - options.rtSettings.darkFramesPath.clear(); - Glib::ustring msg_ = Glib::ustring::compose (M("MAIN_MSG_PATHDOESNTEXIST"), options.rtSettings.darkFramesPath) - +"\n\n"+M("MAIN_MSG_OPERATIONCANCELLED"); - Gtk::MessageDialog msgd (msg_, true, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true); - msgd.set_title(M("TP_DARKFRAME_LABEL")); - msgd.run (); - } - } - else { - Glib::ustring msg_ = M("MAIN_MSG_SETPATHFIRST")+"\n\n"+M("MAIN_MSG_OPERATIONCANCELLED"); - Gtk::MessageDialog msgd (msg_, true, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true); - msgd.set_title(M("TP_DARKFRAME_LABEL")); - msgd.run (); - } + if( !options.rtSettings.darkFramesPath.empty()) { + if (Gio::File::create_for_path(options.rtSettings.darkFramesPath)->query_exists() ){ + for (size_t i=0; i file = Gio::File::create_for_path ( mselected[i]->filename ); + if( !file )continue; + Glib::ustring destName = options.rtSettings.darkFramesPath+ "/" + file->get_basename(); + Glib::RefPtr dest = Gio::File::create_for_path ( destName ); + file->move( dest ); + } + // Reinit cache + rtengine::dfm.init( options.rtSettings.darkFramesPath ); + } + else { + // Target directory creation failed, we clear the darkFramesPath setting + options.rtSettings.darkFramesPath.clear(); + Glib::ustring msg_ = Glib::ustring::compose (M("MAIN_MSG_PATHDOESNTEXIST"), options.rtSettings.darkFramesPath) + +"\n\n"+M("MAIN_MSG_OPERATIONCANCELLED"); + Gtk::MessageDialog msgd (msg_, true, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true); + msgd.set_title(M("TP_DARKFRAME_LABEL")); + msgd.run (); + } + } + else { + Glib::ustring msg_ = M("MAIN_MSG_SETPATHFIRST")+"\n\n"+M("MAIN_MSG_OPERATIONCANCELLED"); + Gtk::MessageDialog msgd (msg_, true, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true); + msgd.set_title(M("TP_DARKFRAME_LABEL")); + msgd.run (); + } } else if (m==autoFF){ - for (size_t i=0; ithumbnail->getProcParams(); - pp.raw.ff_AutoSelect= true; - pp.raw.ff_file.clear(); - mselected[i]->thumbnail->setProcParams(pp,NULL,FILEBROWSER,false); - } + if (!mselected.empty() && bppcl) + bppcl->beginBatchPParamsChange(mselected.size()); + for (size_t i=0; ithumbnail->getProcParams(); + pp.raw.ff_AutoSelect= true; + pp.raw.ff_file.clear(); + mselected[i]->thumbnail->setProcParams(pp,NULL,FILEBROWSER,false); + } + if (!mselected.empty() && bppcl) + bppcl->endBatchPParamsChange(); } else if (m==selectFF){ - if( !mselected.empty() ){ - rtengine::procparams::ProcParams pp=mselected[0]->thumbnail->getProcParams(); - Gtk::FileChooserDialog fc("Flat Field",Gtk::FILE_CHOOSER_ACTION_OPEN ); - FileChooserLastFolderPersister persister(&fc, options.lastFlatfieldDir); - fc.add_button( Gtk::StockID("gtk-cancel"), Gtk::RESPONSE_CANCEL); - fc.add_button( Gtk::StockID("gtk-apply"), Gtk::RESPONSE_APPLY); - if(!pp.raw.ff_file.empty()) - fc.set_filename( pp.raw.ff_file ); - if( fc.run() == Gtk::RESPONSE_APPLY ){ - for (size_t i=0; ithumbnail->getProcParams(); - pp.raw.ff_file= fc.get_filename(); - pp.raw.ff_AutoSelect= false; - mselected[i]->thumbnail->setProcParams(pp,NULL,FILEBROWSER,false); - } - } - } + if( !mselected.empty() ){ + rtengine::procparams::ProcParams pp=mselected[0]->thumbnail->getProcParams(); + Gtk::FileChooserDialog fc("Flat Field",Gtk::FILE_CHOOSER_ACTION_OPEN ); + FileChooserLastFolderPersister persister(&fc, options.lastFlatfieldDir); + fc.add_button( Gtk::StockID("gtk-cancel"), Gtk::RESPONSE_CANCEL); + fc.add_button( Gtk::StockID("gtk-apply"), Gtk::RESPONSE_APPLY); + if(!pp.raw.ff_file.empty()) + fc.set_filename( pp.raw.ff_file ); + if( fc.run() == Gtk::RESPONSE_APPLY ) { + if (bppcl) + bppcl->beginBatchPParamsChange(mselected.size()); + for (size_t i=0; ithumbnail->getProcParams(); + pp.raw.ff_file= fc.get_filename(); + pp.raw.ff_AutoSelect= false; + mselected[i]->thumbnail->setProcParams(pp,NULL,FILEBROWSER,false); + } + if (bppcl) + bppcl->endBatchPParamsChange(); + } + } } else if( m==thisIsFF){ if( !options.rtSettings.flatFieldsPath.empty()) { @@ -745,13 +790,17 @@ void FileBrowser::menuItemActivated (Gtk::MenuItem* m) { mselected[i]->thumbnail->clearProcParams (FILEBROWSER); queue_draw (); } else if (m==execcustprof) { - for (size_t i=0; ibeginBatchPParamsChange(mselected.size()); + for (size_t i=0; ithumbnail->createProcParamsForUpdate (false, true); // Empty run to update the thumb rtengine::procparams::ProcParams params = mselected[i]->thumbnail->getProcParams (); mselected[i]->thumbnail->setProcParams (params, NULL, FILEBROWSER); - } + } + if (!mselected.empty() && bppcl) + bppcl->endBatchPParamsChange(); } else if (m==clearFromCache) { for (size_t i=0; iclearFromCacheRequested (mselected, false); @@ -767,6 +816,10 @@ void FileBrowser::menuItemActivated (Gtk::MenuItem* m) { } void FileBrowser::copyProfile () { + // TODO: Check for Linux + #if PROTECT_VECTORS + MYREADERLOCK(l, entryRW); + #endif if (selected.size()==1) clipboard.setProcParams ((static_cast(selected[0]))->thumbnail->getProcParams()); @@ -776,12 +829,21 @@ void FileBrowser::pasteProfile () { if (clipboard.hasProcParams()) { std::vector mselected; + { + // TODO: Check for Linux + #if PROTECT_VECTORS + MYREADERLOCK(l, entryRW); + #endif + for (unsigned int i=0; i(selected[i])); + } if (!tbl || mselected.empty()) return; + if (!mselected.empty() && bppcl) + bppcl->beginBatchPParamsChange(mselected.size()); for (unsigned int i=0; ithumbnail->setProcParams (*pastedPartProf.pparams, pastedPartProf.pedited, FILEBROWSER); pastedPartProf.deleteInstance(); } + if (!mselected.empty() && bppcl) + bppcl->endBatchPParamsChange(); queue_draw (); } @@ -801,8 +865,15 @@ void FileBrowser::partPasteProfile () { if (clipboard.hasProcParams()) { std::vector mselected; + { + // TODO: Check for Linux + #if PROTECT_VECTORS + MYREADERLOCK(l, entryRW); + #endif + for (unsigned int i=0; i(selected[i])); + } if (!tbl || mselected.empty()) return; @@ -810,6 +881,8 @@ void FileBrowser::partPasteProfile () { int i = partialPasteDlg.run (); if (i == Gtk::RESPONSE_OK) { + if (!mselected.empty() && bppcl) + bppcl->beginBatchPParamsChange(mselected.size()); for (unsigned int i=0; ithumbnail->createProcParamsForUpdate(false,false); // this can execute customprofilebuilder to generate param file @@ -823,6 +896,8 @@ void FileBrowser::partPasteProfile () { mselected[i]->thumbnail->setProcParams (*pastedPartProf.pparams, pastedPartProf.pedited, FILEBROWSER); pastedPartProf.deleteInstance(); } + if (!mselected.empty() && bppcl) + bppcl->endBatchPParamsChange(); queue_draw (); } @@ -832,9 +907,17 @@ void FileBrowser::partPasteProfile () { void FileBrowser::openDefaultViewer (int destination) { bool success=true; + + { + // TODO: Check for Linux + #if PROTECT_VECTORS + MYREADERLOCK(l, entryRW); + #endif + if (selected.size()==1) success=(static_cast(selected[0]))->thumbnail->openDefaultViewer(destination); - + } + if (!success) { Gtk::MessageDialog msgd (M("MAIN_MSG_IMAGEUNPROCESSED"), true, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true); msgd.run (); @@ -956,18 +1039,47 @@ bool FileBrowser::keyPressed (GdkEventKey* event) { return false; } +void FileBrowser::saveThumbnailHeight (int height) { + if (!options.sameThumbSize && inTabMode) + options.thumbSizeTab = height; + else + options.thumbSize = height; +} + +int FileBrowser::getThumbnailHeight () { + // The user could have manually forced the option to a too big value + if (!options.sameThumbSize && inTabMode) + return std::max(std::min(options.thumbSizeTab, 200), 10); + else + return std::max(std::min(options.thumbSize, 200), 10); +} + void FileBrowser::applyMenuItemActivated (Glib::ustring ppname) { + // TODO: Check for Linux + #if PROTECT_VECTORS + MYREADERLOCK(l, entryRW); + #endif + const rtengine::procparams::PartialProfile* partProfile = profileStore.getProfile (ppname); if (partProfile->pparams && !selected.empty()) { - for (size_t i=0; ibeginBatchPParamsChange(selected.size()); + for (size_t i=0; i(selected[i]))->thumbnail->setProcParams (*partProfile->pparams, partProfile->pedited, FILEBROWSER); + if (bppcl) + bppcl->endBatchPParamsChange(); queue_draw (); } } void FileBrowser::applyPartialMenuItemActivated (Glib::ustring ppname) { + // TODO: Check for Linux + #if PROTECT_VECTORS + MYREADERLOCK(l, entryRW); + #endif + if (!tbl || selected.empty()) return; @@ -976,6 +1088,8 @@ void FileBrowser::applyPartialMenuItemActivated (Glib::ustring ppname) { if (srcProfiles->pparams) { if (partialPasteDlg.run()==Gtk::RESPONSE_OK) { + if (bppcl) + bppcl->beginBatchPParamsChange(selected.size()); for (size_t i=0; ithumbnail->createProcParamsForUpdate(false, false); // this can execute customprofilebuilder to generate param file @@ -986,6 +1100,8 @@ void FileBrowser::applyPartialMenuItemActivated (Glib::ustring ppname) { (static_cast(selected[i]))->thumbnail->setProcParams (*dstProfile.pparams, dstProfile.pedited, FILEBROWSER); dstProfile.deleteInstance(); } + if (bppcl) + bppcl->endBatchPParamsChange(); queue_draw (); } partialPasteDlg.hide (); @@ -999,16 +1115,16 @@ void FileBrowser::applyFilter (const BrowserFilter& filter) { // remove items not complying the filter from the selection bool selchanged = false; numFiltered=0; - { - // TODO: Check for Linux - #ifdef WIN32 - Glib::RWLock::ReaderLock l(entryRW); // Don't make this a writer lock! - #endif + { + // TODO: Check for Linux + #if PROTECT_VECTORS + MYWRITERLOCK(l, entryRW); // Don't make this a writer lock! HOMBRE: Why? 'selected' is modified here + #endif - for (size_t i=0; iselected ) { + for (size_t i=0; iselected ) { fd[i]->selected = false; std::vector::iterator j = std::find (selected.begin(), selected.end(), fd[i]); selected.erase (j); @@ -1017,7 +1133,7 @@ void FileBrowser::applyFilter (const BrowserFilter& filter) { selchanged = true; } } - } + } if (selchanged) notifySelectionListener (); @@ -1138,42 +1254,59 @@ void FileBrowser::fromTrashRequested (std::vector tbe) { void FileBrowser::rankingRequested (std::vector tbe, int rank) { - for (size_t i=0; ithumbnail->createProcParamsForUpdate(false, false); // this can execute customprofilebuilder to generate param file + if (!tbe.empty() && bppcl) + bppcl->beginBatchPParamsChange(tbe.size()); - // notify listeners TODO: should do this ONLY when params changed by customprofilebuilder? - tbe[i]->thumbnail->notifylisterners_procParamsChanged(FILEBROWSER); + for (size_t i=0; ithumbnail->createProcParamsForUpdate(false, false); // this can execute customprofilebuilder to generate param file + + // notify listeners TODO: should do this ONLY when params changed by customprofilebuilder? + tbe[i]->thumbnail->notifylisterners_procParamsChanged(FILEBROWSER); tbe[i]->thumbnail->setRank (rank); tbe[i]->thumbnail->updateCache (); // needed to save the colorlabel to disk in the procparam file(s) and the cache image data file //TODO? - should update pparams instead? if (tbe[i]->getThumbButtonSet()) - tbe[i]->getThumbButtonSet()->setRank (tbe[i]->thumbnail->getRank()); + tbe[i]->getThumbButtonSet()->setRank (tbe[i]->thumbnail->getRank()); } applyFilter (filter); + + if (!tbe.empty() && bppcl) + bppcl->endBatchPParamsChange(); } void FileBrowser::colorlabelRequested (std::vector tbe, int colorlabel) { - for (size_t i=0; ithumbnail->createProcParamsForUpdate(false, false); // this can execute customprofilebuilder to generate param file + if (!tbe.empty() && bppcl) + bppcl->beginBatchPParamsChange(tbe.size()); - // notify listeners TODO: should do this ONLY when params changed by customprofilebuilder? - tbe[i]->thumbnail->notifylisterners_procParamsChanged(FILEBROWSER); + for (size_t i=0; ithumbnail->createProcParamsForUpdate(false, false); // this can execute customprofilebuilder to generate param file + + // notify listeners TODO: should do this ONLY when params changed by customprofilebuilder? + tbe[i]->thumbnail->notifylisterners_procParamsChanged(FILEBROWSER); tbe[i]->thumbnail->setColorLabel (colorlabel); tbe[i]->thumbnail->updateCache(); // needed to save the colorlabel to disk in the procparam file(s) and the cache image data file //TODO? - should update pparams instead? if (tbe[i]->getThumbButtonSet()) - tbe[i]->getThumbButtonSet()->setColorLabel (tbe[i]->thumbnail->getColorLabel()); + tbe[i]->getThumbButtonSet()->setColorLabel (tbe[i]->thumbnail->getColorLabel()); } applyFilter (filter); + + if (!tbe.empty() && bppcl) + bppcl->endBatchPParamsChange(); } void FileBrowser::requestRanking(int rank){ + // TODO: Check for Linux + #if PROTECT_VECTORS + MYREADERLOCK(l, entryRW); + #endif std::vector mselected; for (size_t i=0; i(selected[i])); @@ -1182,6 +1315,10 @@ void FileBrowser::requestRanking(int rank){ } void FileBrowser::requestColorLabel(int colorlabel){ + // TODO: Check for Linux + #if PROTECT_VECTORS + MYREADERLOCK(l, entryRW); + #endif std::vector mselected; for (size_t i=0; i(selected[i])); @@ -1219,8 +1356,8 @@ void FileBrowser::buttonPressed (LWButton* button, int actionCode, void* actionD void FileBrowser::openNextImage () { // TODO: Check for Linux - #ifdef WIN32 - Glib::RWLock::ReaderLock l(entryRW); + #if PROTECT_VECTORS + MYWRITERLOCK(l, entryRW); #endif if (!fd.empty() && selected.size()>0 && !options.tabbedUI) { @@ -1240,8 +1377,18 @@ void FileBrowser::openNextImage () { fd[k]->selected = true; selected.push_back (fd[k]); //queue_draw (); + + #if PROTECT_VECTORS + MYWRITERLOCK_RELEASE(l); + #endif + + // this will require a read access notifySelectionListener (); + #if PROTECT_VECTORS + MYWRITERLOCK_ACQUIRE(l); + #endif + // scroll to the selected position double h1, v1; getScrollPosition(h1,v1); @@ -1249,15 +1396,22 @@ void FileBrowser::openNextImage () { double h2=selected[0]->getStartX(); double v2=selected[0]->getStartY(); + Thumbnail* thumb = (static_cast(fd[k]))->thumbnail; + int minWidth = get_width()-fd[k]->getMinimalWidth(); + + #if PROTECT_VECTORS + MYWRITERLOCK_RELEASE(l); + #endif + // scroll only when selected[0] is outside of the displayed bounds - if (h2+fd[k]->getMinimalWidth()-h1 > get_width()) - setScrollPosition(h2-(get_width()-fd[k]->getMinimalWidth()),v2); + if (h2+minWidth-h1 > get_width()) + setScrollPosition(h2-minWidth,v2); if (h1>h2) setScrollPosition(h2,v2); // open the selected image std::vector entries; - entries.push_back ((static_cast(fd[k]))->thumbnail); + entries.push_back (thumb); tbl->openRequested (entries); return; } @@ -1270,8 +1424,8 @@ void FileBrowser::openNextImage () { void FileBrowser::openPrevImage () { // TODO: Check for Linux - #ifdef WIN32 - Glib::RWLock::ReaderLock l(entryRW); + #if PROTECT_VECTORS + MYWRITERLOCK(l, entryRW); #endif if (!fd.empty() && selected.size()>0 && !options.tabbedUI) { @@ -1291,8 +1445,18 @@ void FileBrowser::openPrevImage () { fd[k]->selected = true; selected.push_back (fd[k]); //queue_draw (); + + #if PROTECT_VECTORS + MYWRITERLOCK_RELEASE(l); + #endif + + // this will require a read access notifySelectionListener (); + #if PROTECT_VECTORS + MYWRITERLOCK_ACQUIRE(l); + #endif + // scroll to the selected position double h1, v1; getScrollPosition(h1,v1); @@ -1300,15 +1464,22 @@ void FileBrowser::openPrevImage () { double h2=selected[0]->getStartX(); double v2=selected[0]->getStartY(); + Thumbnail* thumb = (static_cast(fd[k]))->thumbnail; + int minWidth = get_width()-fd[k]->getMinimalWidth(); + + #if PROTECT_VECTORS + MYWRITERLOCK_RELEASE(l); + #endif + // scroll only when selected[0] is outside of the displayed bounds - if (h2+fd[k]->getMinimalWidth()-h1 > get_width()) - setScrollPosition(h2-(get_width()-fd[k]->getMinimalWidth()),v2); + if (h2+minWidth-h1 > get_width()) + setScrollPosition(h2-minWidth,v2); if (h1>h2) setScrollPosition(h2,v2); // open the selected image std::vector entries; - entries.push_back ((static_cast(fd[k]))->thumbnail); + entries.push_back (thumb); tbl->openRequested (entries); return; } @@ -1324,6 +1495,11 @@ void FileBrowser::selectImage (Glib::ustring fname) { // need to clear the filter in filecatalog + // TODO: Check for Linux + #if PROTECT_VECTORS + MYWRITERLOCK(l, entryRW); + #endif + if (!fd.empty() && !options.tabbedUI) { for (size_t i=0; ifilename && !fd[i]->filtered) { @@ -1338,11 +1514,26 @@ void FileBrowser::selectImage (Glib::ustring fname) { fd[i]->selected = true; selected.push_back (fd[i]); queue_draw (); + + #if PROTECT_VECTORS + MYWRITERLOCK_RELEASE(l); + #endif + + // this will require a read access notifySelectionListener (); + #if PROTECT_VECTORS + MYWRITERLOCK_ACQUIRE(l); + #endif + // scroll to the selected position double h=selected[0]->getStartX(); double v=selected[0]->getStartY(); + + #if PROTECT_VECTORS + MYWRITERLOCK_RELEASE(l); + #endif + setScrollPosition(h,v); return; @@ -1373,6 +1564,7 @@ void FileBrowser::_thumbRearrangementNeeded () { } void FileBrowser::thumbRearrangementNeeded () { + // refreshThumbImagesUI will handle thread safety itself g_idle_add (refreshThumbImagesUI, this); } @@ -1384,15 +1576,20 @@ void FileBrowser::selectionChanged () { void FileBrowser::notifySelectionListener () { if (tbl) { + // TODO: Check for Linux + #if PROTECT_VECTORS + MYREADERLOCK(l, entryRW); + #endif + std::vector thm; - for (size_t i=0; i(selected[i]))->thumbnail); + for (size_t i=0; i(selected[i]))->thumbnail); tbl->selectionChanged (thm); - } + } } void FileBrowser::redrawNeeded (LWButton* button) { - + GThreadLock lock; queue_draw (); } FileBrowser::type_trash_changed FileBrowser::trash_changed () { diff --git a/rtgui/filebrowser.h b/rtgui/filebrowser.h index c4c818ef4..4486e70c4 100644 --- a/rtgui/filebrowser.h +++ b/rtgui/filebrowser.h @@ -25,6 +25,7 @@ #include "exiffiltersettings.h" #include "filebrowserentry.h" #include "browserfilter.h" +#include "pparamschangelistener.h" #include "partialpastedlg.h" #include "exportpanel.h" #include "extprog.h" @@ -34,6 +35,7 @@ class FileBrowserEntry; class FileBrowserListener { public: + virtual ~FileBrowserListener () {} virtual void openRequested (std::vector tbe) {} virtual void developRequested (std::vector tbe, bool fastmode) {} virtual void renameRequested (std::vector tbe) {} @@ -112,6 +114,7 @@ class FileBrowser : public ThumbBrowserBase, Glib::RefPtr pmaccelgroup; + BatchPParamsChangeListener* bppcl; FileBrowserListener* tbl; BrowserFilter filter; int numFiltered; @@ -140,6 +143,7 @@ class FileBrowser : public ThumbBrowserBase, FileBrowserEntry* delEntry (const Glib::ustring& fname); // return the entry if found here return NULL otherwise void close (); + void setBatchPParamsChangeListener (BatchPParamsChangeListener* l) { bppcl = l; } void setFileBrowserListener (FileBrowserListener* l) { tbl = l; } void menuItemActivated (Gtk::MenuItem* m); @@ -156,6 +160,9 @@ class FileBrowser : public ThumbBrowserBase, void doubleClicked (ThumbBrowserEntryBase* entry); bool keyPressed (GdkEventKey* event); + void saveThumbnailHeight (int height); + int getThumbnailHeight (); + void openNextImage (); void openPrevImage (); void copyProfile (); diff --git a/rtgui/filebrowserentry.cc b/rtgui/filebrowserentry.cc index 6603eaaf7..b9091bbe2 100644 --- a/rtgui/filebrowserentry.cc +++ b/rtgui/filebrowserentry.cc @@ -203,11 +203,14 @@ void FileBrowserEntry::updateImage (rtengine::IImage8* img, double scale, rtengi param->img = img; param->scale = scale; param->cropParams = cropParams; - g_idle_add (updateImageUI, param); + g_idle_add_full (G_PRIORITY_LOW, updateImageUI, param, NULL); } void FileBrowserEntry::_updateImage (rtengine::IImage8* img, double s, rtengine::procparams::CropParams cropParams) { - Glib::RWLock::WriterLock l(lockRW); + + #if PROTECT_VECTORS + MYWRITERLOCK(l, lockRW); + #endif redrawRequests--; scale = s; @@ -219,6 +222,8 @@ void FileBrowserEntry::_updateImage (rtengine::IImage8* img, double s, rtengine: if (preh == img->getHeight ()) { prew = img->getWidth (); + GThreadLock lock; + // Check if image has been rotated since last time rotated = preview!=NULL && newLandscape!=landscape; diff --git a/rtgui/filecatalog.cc b/rtgui/filecatalog.cc index 1c3ef5dd0..8ba2ba71d 100644 --- a/rtgui/filecatalog.cc +++ b/rtgui/filecatalog.cc @@ -38,8 +38,6 @@ using namespace std; #define CHECKTIME 2000 -extern Glib::ustring argv0; - FileCatalog::FileCatalog (CoarsePanel* cp, ToolBar* tb, FilePanel* filepanel) : filepanel(filepanel), selectedDirectoryId(1), @@ -462,9 +460,12 @@ void FileCatalog::closeDir () { // remove entries selectedDirectory = ""; fileBrowser->close (); - fileNameList.clear (); - + fileNameList.clear (); + + { + Glib::Mutex::Lock lock(filterMutex); dirEFS.clear (); + } hasValidCurrentEFS = false; redrawAll (); } @@ -537,13 +538,15 @@ void FileCatalog::_refreshProgressBar () { // Also mention that this progress bar only measures the FIRST pass (quick thumbnails) // The second, usually longer pass is done multithreaded down in the single entries and is NOT measured by this if (!inTabMode) { - Gtk::Notebook *nb =(Gtk::Notebook *)(filepanel->get_parent()); - Gtk::Box* hbb=NULL; - Gtk::Label *label=NULL; - if( options.mainNBVertical ) - hbb = Gtk::manage (new Gtk::VBox ()); - else - hbb = Gtk::manage (new Gtk::HBox ()); + GThreadLock lock; // All GUI acces from idle_add callbacks or separate thread HAVE to be protected + + Gtk::Notebook *nb =(Gtk::Notebook *)(filepanel->get_parent()); + Gtk::Box* hbb=NULL; + Gtk::Label *label=NULL; + if( options.mainNBVertical ) + hbb = Gtk::manage (new Gtk::VBox ()); + else + hbb = Gtk::manage (new Gtk::HBox ()); if (!previewsToLoad ) { hbb->pack_start (*Gtk::manage (new Gtk::Image (Gtk::Stock::DIRECTORY, Gtk::ICON_SIZE_MENU))); int filteredCount=fileBrowser->getNumFiltered(); @@ -573,17 +576,9 @@ int refreshProgressBarUI (void* data) { } void FileCatalog::previewReady (int dir_id, FileBrowserEntry* fdn) { - GThreadLock lock; - previewReadyUI (dir_id,fdn); -} -// Called WITHIN gtk thread -void FileCatalog::previewReadyUI (int dir_id, FileBrowserEntry* fdn) { - - if ( dir_id != selectedDirectoryId ) - { - return; - } + if ( dir_id != selectedDirectoryId ) + return; // put it into the "full directory" browser fdn->setImageAreaToolListener (iatlistener); @@ -591,6 +586,9 @@ void FileCatalog::previewReadyUI (int dir_id, FileBrowserEntry* fdn) { // update exif filter settings (minimal & maximal values of exif tags, cameras, lenses, etc...) const CacheImageData* cfs = fdn->thumbnail->getCacheImageData(); + + { + Glib::Mutex::Lock lock(filterMutex); if (cfs->exifValid) { if (cfs->fnumber < dirEFS.fnumberFrom) dirEFS.fnumberFrom = cfs->fnumber; @@ -613,13 +611,14 @@ void FileCatalog::previewReadyUI (int dir_id, FileBrowserEntry* fdn) { dirEFS.cameras.insert (cfs->camera); dirEFS.lenses.insert (cfs->lens); dirEFS.expcomp.insert (cfs->expcomp); + } + previewsLoaded++; g_idle_add (refreshProgressBarUI, this); } int prevfinished (void* data) { - GThreadLock lock; (static_cast(data))->previewsFinishedUI (); return 0; } @@ -627,26 +626,30 @@ int prevfinished (void* data) { // Called within GTK UI thread void FileCatalog::previewsFinishedUI () { - redrawAll (); - previewsToLoad = 0; + { + GThreadLock lock; // All GUI acces from idle_add callbacks or separate thread HAVE to be protected + redrawAll (); + previewsToLoad = 0; - if (filterPanel) { - filterPanel->set_sensitive (true); - if ( !hasValidCurrentEFS ){ - currentEFS = dirEFS; - filterPanel->setFilter ( dirEFS,true ); - }else { - filterPanel->setFilter ( currentEFS,false ); - } - } - - if (exportPanel) - exportPanel->set_sensitive (true); + if (filterPanel) { + filterPanel->set_sensitive (true); + Glib::Mutex::Lock lock(filterMutex); + if ( !hasValidCurrentEFS ){ + currentEFS = dirEFS; + filterPanel->setFilter ( dirEFS,true ); + }else { + filterPanel->setFilter ( currentEFS,false ); + } + } - // restart anything that might have been loaded low quality - fileBrowser->refreshQuickThumbImages(); - fileBrowser->applyFilter (getFilter()); // refresh total image count - _refreshProgressBar(); + if (exportPanel) + exportPanel->set_sensitive (true); + + // restart anything that might have been loaded low quality + fileBrowser->refreshQuickThumbImages(); + fileBrowser->applyFilter (getFilter()); // refresh total image count + _refreshProgressBar(); + } filepanel->loadingThumbs(M("PROGRESSBAR_READY"),0); if (!imageToSelect_fname.empty()){ @@ -668,8 +671,10 @@ void FileCatalog::previewsFinished (int dir_id) { return; } - if (!hasValidCurrentEFS) + if (!hasValidCurrentEFS) { + Glib::Mutex::Lock lock(filterMutex); currentEFS = dirEFS; + } g_idle_add (prevfinished, this); } @@ -689,9 +694,9 @@ void FileCatalog::refreshThumbImages () { void FileCatalog::refreshHeight () { int newHeight=fileBrowser->getEffectiveHeight() + buttonBar->get_height(); if (!options.FileBrowserToolbarSingleRow) { - newHeight += hbToolBar1->get_height(); + newHeight += hbToolBar1->get_height(); } - set_size_request(0, newHeight); + set_size_request(0, newHeight+2); // HOMBRE: yeah, +2, there's always 2 pixels missing... sorry for this dirty hack O:) } void FileCatalog::_openImage (std::vector tmb) { @@ -879,11 +884,14 @@ void FileCatalog::developRequested (std::vector tbe, bool fas if (listener) { std::vector entries; - #pragma omp parallel for ordered + // TODO: (HOMBRE) should we still use parallelization here, now that thumbnails are processed asynchronously...? + //#pragma omp parallel for ordered for (size_t i=0; ithumbnail->getProcParams(); + FileBrowserEntry* fbe = tbe[i]; + Thumbnail* th = fbe->thumbnail; + rtengine::procparams::ProcParams params = th->getProcParams(); - // if fast mode is selected, override (disable) prams + // if fast mode is selected, override (disable) params // controlling time and resource consuming tasks // and also those which effect is not pronounced after reducing the image size // TODO!!! could expose selections below via preferences @@ -921,30 +929,19 @@ void FileCatalog::developRequested (std::vector tbe, bool fas params.resize.height = options.fastexport_resize_height ; } - rtengine::ProcessingJob* pjob = rtengine::ProcessingJob::create (tbe[i]->filename, tbe[i]->thumbnail->getType()==FT_Raw, params); - double tmpscale; - rtengine::IImage8* img = tbe[i]->thumbnail->processThumbImage (params, BatchQueue::calcMaxThumbnailHeight(), tmpscale); + rtengine::ProcessingJob* pjob = rtengine::ProcessingJob::create (fbe->filename, th->getType()==FT_Raw, params); - int pw, ph; - guint8* prev=NULL; - - if (img) { - pw = img->getWidth (); - ph = img->getHeight (); - prev = new guint8 [pw*ph*3]; - memcpy (prev, img->getData (), pw*ph*3); - img->free(); - - } else { - tbe[i]->thumbnail->getThumbnailSize (pw, ph); - } + int pw; + int ph = BatchQueue::calcMaxThumbnailHeight(); + th->getThumbnailSize (pw, ph); // processThumbImage is the processing intensive part, but adding to queue must be ordered - #pragma omp ordered - { - entries.push_back(new BatchQueueEntry (pjob, params, tbe[i]->filename, prev, pw, ph, tbe[i]->thumbnail)); - } - } + //#pragma omp ordered + //{ + BatchQueueEntry* bqh = new BatchQueueEntry (pjob, params, fbe->filename, pw, ph, th); + entries.push_back(bqh); + //} + } listener->addBatchQueueJobs( entries ); } @@ -1324,8 +1321,10 @@ BrowserFilter FileCatalog::getFilter () { if (!filterPanel) filter.exifFilterEnabled = false; else { - if (!hasValidCurrentEFS) + Glib::Mutex::Lock lock(filterMutex); + if (!hasValidCurrentEFS) { filter.exifFilter = dirEFS; + } else filter.exifFilter = currentEFS; filter.exifFilterEnabled = filterPanel->isEnabled (); @@ -1408,15 +1407,14 @@ void FileCatalog::winDirChanged () { void FileCatalog::on_dir_changed (const Glib::RefPtr& file, const Glib::RefPtr& other_file, Gio::FileMonitorEvent event_type, bool internal) { - if (options.has_retained_extention(file->get_parse_name())) { - if (!internal) - gdk_threads_enter(); - - if (event_type == Gio::FILE_MONITOR_EVENT_CREATED || event_type == Gio::FILE_MONITOR_EVENT_DELETED || event_type == Gio::FILE_MONITOR_EVENT_CHANGED) - reparseDirectory (); - - if (!internal) - gdk_threads_leave(); + if (options.has_retained_extention(file->get_parse_name()) + && (event_type == Gio::FILE_MONITOR_EVENT_CREATED || event_type == Gio::FILE_MONITOR_EVENT_DELETED || event_type == Gio::FILE_MONITOR_EVENT_CHANGED)) { + if (!internal) { + GThreadLock lock; + reparseDirectory (); + } + else + reparseDirectory (); } } @@ -1454,7 +1452,7 @@ void FileCatalog::addAndOpenFile (const Glib::ustring& fname) { Thumbnail* tmb = cacheMgr->getEntry (file->get_parse_name()); if (tmb) { FileBrowserEntry* entry = new FileBrowserEntry (tmb, file->get_parse_name()); - previewReadyUI (selectedDirectoryId,entry); + previewReady (selectedDirectoryId,entry); // open the file FCOIParams* params = new FCOIParams; params->catalog = this; @@ -1511,7 +1509,9 @@ void FileCatalog::selectionChanged (std::vector tbe) { void FileCatalog::exifFilterChanged () { - currentEFS = filterPanel->getFilter (); + // not sure that locking is necessary here... + Glib::Mutex::Lock lock(filterMutex); + currentEFS = filterPanel->getFilter (); hasValidCurrentEFS = true; fileBrowser->applyFilter (getFilter ()); _refreshProgressBar(); @@ -1557,9 +1557,11 @@ bool FileCatalog::Query_key_pressed (GdkEventKey *event){ FileCatalog::buttonQueryClearPressed (); return true; } + break; default: - return false; + break; } + return false; } void FileCatalog::updateFBQueryTB (bool singleRow) { @@ -1625,9 +1627,11 @@ bool FileCatalog::BrowsePath_key_pressed (GdkEventKey *event){ BrowsePath->select_region(BrowsePath->get_text_length(), BrowsePath->get_text_length()); return true; } + break; default: - return false; + break; } + return false; } void FileCatalog::tbLeftPanel_1_visible (bool visible){ @@ -1806,6 +1810,7 @@ bool FileCatalog::handleShortcutKey (GdkEventKey* event) { FileCatalog::buttonBrowsePathPressed (); return true; } + break; } } diff --git a/rtgui/filecatalog.h b/rtgui/filecatalog.h index 83a5e2381..43d1ba536 100644 --- a/rtgui/filecatalog.h +++ b/rtgui/filecatalog.h @@ -133,6 +133,7 @@ class FileCatalog : public Gtk::VBox, Gtk::Button* zoomInButton; Gtk::Button* zoomOutButton; + Glib::Mutex filterMutex; ExifFilterSettings dirEFS; ExifFilterSettings currentEFS; bool hasValidCurrentEFS; @@ -172,21 +173,20 @@ class FileCatalog : public Gtk::VBox, void dirSelected (const Glib::ustring& dirname, const Glib::ustring& openfile=""); void closeDir (); void refreshEditedState (const std::set& efiles); - + // previewloaderlistener interface - void previewReadyUI (int dir_id, FileBrowserEntry* fdn); - void previewReady (int dir_id, FileBrowserEntry* fdn); - void previewsFinished (int dir_id); + void previewReady (int dir_id, FileBrowserEntry* fdn); + void previewsFinished (int dir_id); void previewsFinishedUI (); void _refreshProgressBar (); - // filterpanel interface - void exifFilterChanged (); - - // exportpanel interface - void exportRequested(); - - Glib::ustring lastSelectedDir () { return selectedDirectory; } + // filterpanel interface + void exifFilterChanged (); + + // exportpanel interface + void exportRequested(); + + Glib::ustring lastSelectedDir () { return selectedDirectory; } void setEnabled (bool e); // if not enabled, it does not open image void enableTabMode(bool enable); // sets progress bar @@ -210,18 +210,18 @@ class FileCatalog : public Gtk::VBox, void setImageAreaToolListener (ImageAreaToolListener* l) { iatlistener = l; } void setDirBrowserRemoteInterface (DirBrowserRemoteInterface* l) { dirlistener = l; } - void setFilterPanel (FilterPanel* fpanel); - void setExportPanel (ExportPanel* expanel); - void exifInfoButtonToggled(); + void setFilterPanel (FilterPanel* fpanel); + void setExportPanel (ExportPanel* expanel); + void exifInfoButtonToggled(); void categoryButtonToggled (Gtk::ToggleButton* b, bool isMouseClick); bool capture_event(GdkEventButton* event); void filterChanged (); void runFilterDialog (); void on_realize(); - void reparseDirectory (); + void reparseDirectory (); void _openImage (std::vector tmb); - + void zoomIn (); void zoomOut (); diff --git a/rtgui/filepanel.cc b/rtgui/filepanel.cc index bf670a4a0..9a487de16 100644 --- a/rtgui/filepanel.cc +++ b/rtgui/filepanel.cc @@ -87,6 +87,7 @@ FilePanel::FilePanel () : parent(NULL) { fileCatalog->setFilterPanel (filterPanel); fileCatalog->setExportPanel (exportPanel); fileCatalog->setImageAreaToolListener (tpc); + fileCatalog->fileBrowser->setBatchPParamsChangeListener (tpc); //------------------ @@ -134,6 +135,7 @@ void FilePanel::setAspect () { void FilePanel::init () { + GThreadLock lock; // All GUI acces from idle_add callbacks or separate thread HAVE to be protected dirBrowser->fillDirTree (); placesBrowser->refreshPlacesList (); @@ -170,40 +172,49 @@ bool FilePanel::fileSelected (Thumbnail* thm) { ProgressConnector *ld = new ProgressConnector(); ld->startFunc (sigc::bind(sigc::ptr_fun(&rtengine::InitialImage::load), thm->getFileName (), thm->getType()==FT_Raw, &error, parent->getProgressListener()), - sigc::bind(sigc::mem_fun(*this,&FilePanel::imageLoaded), thm, ld) ); + sigc::bind(sigc::mem_fun(*this,&FilePanel::imageLoaded), thm, ld) ); return true; } bool FilePanel::imageLoaded( Thumbnail* thm, ProgressConnector *pc ){ - if (pc->returnValue() && thm) { - + if (pc->returnValue() && thm) { + if (options.tabbedUI) { - EditorPanel* epanel = Gtk::manage (new EditorPanel ()); + EditorPanel* epanel; + { + GThreadLock lock; // Acquiring the GUI... not sure that it's necessary, but it shouldn't harm + epanel = Gtk::manage (new EditorPanel ()); parent->addEditorPanel (epanel,Glib::path_get_basename (thm->getFileName())); + } epanel->open(thm, pc->returnValue() ); } else { + { + GThreadLock lock; // Acquiring the GUI... not sure that it's necessary, but it shouldn't harm parent->SetEditorCurrent(); - parent->epanel->open(thm, pc->returnValue() ); + } + parent->epanel->open(thm, pc->returnValue() ); } + } else { + Glib::ustring msg_ = Glib::ustring("") + M("MAIN_MSG_CANNOTLOAD") + " \"" + thm->getFileName() + "\" .\n"; + Gtk::MessageDialog msgd (msg_, true, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true); + msgd.run (); + } + delete pc; - } else { - Glib::ustring msg_ = Glib::ustring("") + M("MAIN_MSG_CANNOTLOAD") + " \"" + thm->getFileName() + "\" .\n"; - Gtk::MessageDialog msgd (msg_, true, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true); - msgd.run (); - } - delete pc; - - parent->setProgress(0.); - parent->setProgressStr(""); - thm->imageLoad( false ); + { + GThreadLock lock; // Acquiring the GUI... not sure that it's necessary, but it shouldn't harm + parent->setProgress(0.); + parent->setProgressStr(""); + } + thm->imageLoad( false ); - return false; // MUST return false from idle function + return false; // MUST return false from idle function } void FilePanel::saveOptions () { - int winW, winH; - parent->get_size(winW, winH); + int winW, winH; + parent->get_size(winW, winH); options.dirBrowserWidth = dirpaned->get_position (); options.dirBrowserHeight = placespaned->get_position (); options.browserToolPanelWidth = winW - get_position(); @@ -262,6 +273,7 @@ bool FilePanel::handleShortcutKey (GdkEventKey* event) { void FilePanel::loadingThumbs(Glib::ustring str, double rate) { + GThreadLock lock; // All GUI acces from idle_add callbacks or separate thread HAVE to be protected if( !str.empty()) parent->setProgressStr(str); parent->setProgress( rate ); diff --git a/rtgui/guiutils.cc b/rtgui/guiutils.cc index 0ed52e6b0..47883985a 100644 --- a/rtgui/guiutils.cc +++ b/rtgui/guiutils.cc @@ -28,6 +28,11 @@ using namespace std; +#if TRACE_MYRWMUTEX==1 && !defined NDEBUG +unsigned int MyReaderLock::readerLockCounter = 0; +unsigned int MyWriterLock::writerLockCounter = 0; +#endif + Glib::ustring escapeHtmlChars(const Glib::ustring &src) { // Sources chars to be escaped diff --git a/rtgui/guiutils.h b/rtgui/guiutils.h index 45eaf99ef..7d9214f31 100644 --- a/rtgui/guiutils.h +++ b/rtgui/guiutils.h @@ -20,7 +20,10 @@ #define __GUI_UTILS_ #include +#include #include "../rtengine/rtengine.h" +#include +#include Glib::ustring escapeHtmlChars(const Glib::ustring &src); bool removeIfThere (Gtk::Container* cont, Gtk::Widget* w, bool increference=true); @@ -74,6 +77,455 @@ public: } }; +#ifdef NDEBUG + // We don't trace mutex + #undef TRACE_MYRWMUTEX + #define TRACE_MYRWMUTEX 0 +#endif + + +// Uncomment this if you want to bypass the CMakeList options and force the values +// Of course, DO NOT COMMIT! :) + +//#undef PROTECT_VECTORS +//#define PROTECT_VECTORS 1 +//#undef TRACE_MYRWMUTEX +//#define TRACE_MYRWMUTEX 1 + + +/** + * @brief Custom RWLock with debugging feature, to replace the buggy Glib::RWLock (can have negative reader_count value!) + * + * It may be slower, but thread safe! + */ +class MyRWMutex { +public: + Glib::Mutex handlerMutex; + Glib::Cond access; + size_t writerCount; + size_t readerCount; +#if TRACE_MYRWMUTEX + Glib::ustring lastWriterFile; + int lastWriterLine; + // Unfortunately, ownerThread may not be the culprit of a deadlock, it can be another concurrent Reader... + void* ownerThread; + + MyRWMutex() : writerCount(0), readerCount(0), lastWriterLine(0), ownerThread(NULL) {} +#else + MyRWMutex() : writerCount(0), readerCount(0) {} +#endif +}; + +/** + * @brief Custom ReaderLock with debugging feature, to replace the buggy Glib::RWLock (can have negative reader_count value!) + * + */ +class MyReaderLock { + + MyRWMutex& rwMutex; + bool locked; + + #if TRACE_MYRWMUTEX + static unsigned int readerLockCounter; + int locknumber; + +public: + inline MyReaderLock(MyRWMutex& mutex, const char* name, const char* file, const int line) : rwMutex(mutex), locked(false), locknumber(0) + #else +public: + inline MyReaderLock(MyRWMutex& mutex) : rwMutex(mutex) + #endif + + { + // to operate safely + rwMutex.handlerMutex.lock(); + + #if TRACE_MYRWMUTEX + locknumber = readerLockCounter++; + void* thread = Glib::Thread::self(); + std::cout << thread << "/" << locknumber << ":" << name << " / " << file << " : " << line << " - locking - R"; + #endif + + if (!rwMutex.writerCount) { + // There's no writer operating, we can increment the writer count which will lock writers + ++rwMutex.writerCount; + #if TRACE_MYRWMUTEX + std::cout << " ++ new owner"; + #endif + } + else { + // The writer count is non null, but we can be the owner of the writer lock + // It will be the case if the reader count is non null too. + if (!rwMutex.readerCount) { + // the mutex is in real write mode, we're waiting to see it null + #if TRACE_MYRWMUTEX + std::cout << " waiting..." << std::endl << "Current writer owner: " << rwMutex.lastWriterFile << " : " << rwMutex.lastWriterLine << std::endl; + #endif + while (rwMutex.writerCount) + rwMutex.access.wait(rwMutex.handlerMutex); + ++rwMutex.writerCount; + #if TRACE_MYRWMUTEX + rwMutex.lastWriterFile = file; + rwMutex.lastWriterLine = line; + rwMutex.ownerThread = thread; + std::cout << thread << "/" << locknumber << ":" << name << " / " << file << " : " << line << " - locking - R ++ new owner"; + #endif + } + } + // then we can increment the reader count + ++rwMutex.readerCount; + + #if TRACE_MYRWMUTEX + std::cout << " - ReaderCount: " << rwMutex.readerCount << " - WriterCount: " << rwMutex.writerCount << std::endl; + #endif + + rwMutex.handlerMutex.unlock(); + + locked = true; + } + #if TRACE_MYRWMUTEX + // locks the MyRWMutex with Read access if this MyReaderLock has not already locked it, otherwise return safely + inline void acquire(const char* file, const int line) + #else + // locks the MyRWMutex with Read access if this MyReaderLock has not already locked it, otherwise return safely + inline void acquire() + #endif + { + #if TRACE_MYRWMUTEX + void* thread = Glib::Thread::self(); + #endif + if (!locked) { + // to operate safely + rwMutex.handlerMutex.lock(); + + #if TRACE_MYRWMUTEX + std::cout << thread << "/" << locknumber << ":" << file << " : " << line << " - locking - R (lock)"; + #endif + + if (!rwMutex.writerCount) { + // There's no writer operating, we can increment the writer count which will lock writers + ++rwMutex.writerCount; + #if TRACE_MYRWMUTEX + std::cout << " ++ new owner"; + #endif + } + else { + // The writer count is non null, but a reader can be the owner of the writer lock, + // it will be the case if the reader count is non null too. + if (!rwMutex.readerCount) { + // the mutex is in real write mode, we're waiting to see it null + #if TRACE_MYRWMUTEX + std::cout << " waiting..." << std::endl << "Current writer owner: " << rwMutex.lastWriterFile << " : " << rwMutex.lastWriterLine << std::endl; + #endif + while (rwMutex.writerCount) + rwMutex.access.wait(rwMutex.handlerMutex); + ++rwMutex.writerCount; + #if TRACE_MYRWMUTEX + rwMutex.lastWriterFile = file; + rwMutex.lastWriterLine = line; + rwMutex.ownerThread = thread; + std::cout << thread << "/" << locknumber << ":" << file << " : " << line << " - locking - R (lock) ++ new owner"; + #endif + } + } + // then we can increment the reader count + ++rwMutex.readerCount; + + #if TRACE_MYRWMUTEX + std::cout << " - ReaderCount: " << rwMutex.readerCount << " - WriterCount: " << rwMutex.writerCount << std::endl; + #endif + + rwMutex.handlerMutex.unlock(); + + locked = true; + } + #if TRACE_MYRWMUTEX + else std::cout << thread << "/" << locknumber << " / already locked by this object - R (lock)" << std::endl; + #endif + } + inline ~MyReaderLock() { + #if TRACE_MYRWMUTEX + void* thread = Glib::Thread::self(); + #endif + if (locked) { + // to operate safely + rwMutex.handlerMutex.lock(); + + // decrement the writer number first + --rwMutex.readerCount; + + #if TRACE_MYRWMUTEX + std::cout << thread << "/" << locknumber << " / unlocking - R - ReaderCount: " << rwMutex.readerCount; + #endif + + if (!rwMutex.readerCount) { + // no more reader, so we decrement the writer count + --rwMutex.writerCount; + #if TRACE_MYRWMUTEX + rwMutex.lastWriterFile = ""; + rwMutex.lastWriterLine = 0; + rwMutex.ownerThread = NULL; + std::cout << " -- new owner possible!" << " >>> ReaderCount: " << rwMutex.readerCount << " - WriterCount: " << rwMutex.writerCount; + #endif + // and signal the next waiting reader/writer that it's free + rwMutex.access.broadcast(); + } + #if TRACE_MYRWMUTEX + std::cout << std::endl; + #endif + + rwMutex.handlerMutex.unlock(); + } + #if TRACE_MYRWMUTEX + else std::cout << thread << "/" << locknumber << " / already unlocked by this object - R" << std::endl; + #endif + } + #if TRACE_MYRWMUTEX + // releases the MyRWMutex with Write access if this MyWriterLock has already locked it, otherwise return safely + inline void release(const char* file, const int line) + #else + // releases the MyRWMutex with Write access if this MyWriterLock has already locked it, otherwise return safely + inline void release() + #endif + { + #if TRACE_MYRWMUTEX + void* thread = Glib::Thread::self(); + #endif + if (locked) { + // to operate safely + rwMutex.handlerMutex.lock(); + + // decrement the writer number first + --rwMutex.readerCount; + + #if TRACE_MYRWMUTEX + std::cout << thread << "/" << locknumber << " / unlocking - R (release) - ReaderCount: " << rwMutex.readerCount; + #endif + + if (!rwMutex.readerCount) { + // no more reader, so we decrement the writer count + --rwMutex.writerCount; + #if TRACE_MYRWMUTEX + rwMutex.lastWriterFile = ""; + rwMutex.lastWriterLine = 0; + rwMutex.ownerThread = NULL; + std::cout << " -- new owner possible!" << " >>> ReaderCount: " << rwMutex.readerCount << " - WriterCount: " << rwMutex.writerCount; + #endif + // and signal the next waiting reader/writer that it's free + rwMutex.access.broadcast(); + } + #if TRACE_MYRWMUTEX + std::cout << std::endl; + #endif + + rwMutex.handlerMutex.unlock(); + + locked = false; + } + #if TRACE_MYRWMUTEX + else std::cout << thread << "/" << locknumber << " / already unlocked - R (release)" << std::endl; + #endif + } +}; + +/** + * @brief Custom WriterLock with debugging feature, to replace the buggy Glib::RWLock (can have negative reader_count value!) + * + */ +class MyWriterLock { + + MyRWMutex& rwMutex; + bool locked; + + #if TRACE_MYRWMUTEX + static unsigned int writerLockCounter; + int locknumber; +public: + inline MyWriterLock(MyRWMutex& mutex, const char* name, const char* file, const int line) : rwMutex(mutex), locked(false), locknumber(0) + #else +public: + inline MyWriterLock(MyRWMutex& mutex) : rwMutex(mutex) + #endif + { + // to operate safely + rwMutex.handlerMutex.lock(); + + #if TRACE_MYRWMUTEX + locknumber = writerLockCounter++; + void* thread = Glib::Thread::self(); + std::cout << thread << "/" << locknumber << ":" << name << " / " << file << " : " << line << " - locking - W"; + #endif + + if (rwMutex.writerCount) { + // The writer count is non null, so we have to wait for it to be null again + #if TRACE_MYRWMUTEX + std::cout << " waiting..." << std::endl << "Current writer owner: " << rwMutex.lastWriterFile << " : " << rwMutex.lastWriterLine << std::endl; + #endif + while (rwMutex.writerCount) + rwMutex.access.wait(rwMutex.handlerMutex); + #if TRACE_MYRWMUTEX + std::cout << thread << "/" << locknumber << ":" << file << " : " << line << " - locking - W"; + #endif + } + // then we can increment the writer count + ++rwMutex.writerCount; + + #if TRACE_MYRWMUTEX + rwMutex.lastWriterFile = file; + rwMutex.lastWriterLine = line; + rwMutex.ownerThread = thread; + std::cout << " ++ new owner <<< ReaderCount: " << rwMutex.readerCount << " - WriterCount: " << rwMutex.writerCount << std::endl; + #endif + + rwMutex.handlerMutex.unlock(); + + locked = true; + } + #if TRACE_MYRWMUTEX + // locks the MyRWMutex with Read access if this MyReaderLock has not already locked it, otherwise return safely + inline void acquire(const char* file, const int line) + #else + // locks the MyRWMutex with Read access if this MyReaderLock has not already locked it, otherwise return safely + inline void acquire() + #endif + { + #if TRACE_MYRWMUTEX + void* thread = Glib::Thread::self(); + #endif + if (!locked) { + // to operate safely + rwMutex.handlerMutex.lock(); + + #if TRACE_MYRWMUTEX + std::cout << thread << "/" << locknumber << ":" << file << " : " << line << " - locking - W (lock)"; + #endif + + if (rwMutex.writerCount) { + // The writer count is non null, so we have to wait for it to be null again + #if TRACE_MYRWMUTEX + std::cout << " waiting..." << std::endl << "Current writer owner: " << rwMutex.lastWriterFile << " : " << rwMutex.lastWriterLine << std::endl; + #endif + while (rwMutex.writerCount) + rwMutex.access.wait(rwMutex.handlerMutex); + #if TRACE_MYRWMUTEX + std::cout << thread << "/" << locknumber << ":" << file << " : " << line << " - locking - W (lock)"; + #endif + } + // then we can increment the reader count + ++rwMutex.writerCount; + + #if TRACE_MYRWMUTEX + rwMutex.lastWriterFile = file; + rwMutex.lastWriterLine = line; + rwMutex.ownerThread = thread; + std::cout << " ++ new owner <<< ReaderCount: " << rwMutex.readerCount << " - WriterCount: " << rwMutex.writerCount << std::endl; + #endif + + rwMutex.handlerMutex.unlock(); + + locked = true; + } + #if TRACE_MYRWMUTEX + else std::cout << thread << "/" << locknumber << " / already locked by this object - W (lock)" << std::endl; + #endif + } + inline ~MyWriterLock() { + #if TRACE_MYRWMUTEX + void* thread = Glib::Thread::self(); + #endif + if (locked) { + // to operate safely + rwMutex.handlerMutex.lock(); + + // decrement the writer number first + --rwMutex.writerCount; + + #if TRACE_MYRWMUTEX + std::cout << thread << "/" << locknumber << " / unlocking - W"; + #endif + + if (!rwMutex.writerCount) { + #if TRACE_MYRWMUTEX + rwMutex.lastWriterFile = ""; + rwMutex.lastWriterLine = 0; + rwMutex.ownerThread = NULL; + std::cout << " -- new owner possible!"; + #endif + // The writer count is null again, so we can wake up the next writer or reader + rwMutex.access.broadcast(); + } + #if TRACE_MYRWMUTEX + std::cout << " <<< ReaderCount: " << rwMutex.readerCount << " - WriterCount: " << rwMutex.writerCount << std::endl; + #endif + + rwMutex.handlerMutex.unlock(); + } + #if TRACE_MYRWMUTEX + else std::cout << thread << "/" << locknumber << " / already unlocked by this object - W" << std::endl; + #endif + } + #if TRACE_MYRWMUTEX + // releases the MyRWMutex with Write access if this MyWriterLock has already locked it, otherwise return safely + inline void release(const char* file, const int line) + #else + // releases the MyRWMutex with Write access if this MyWriterLock has already locked it, otherwise return safely + inline void release() + #endif + { + #if TRACE_MYRWMUTEX + void* thread = Glib::Thread::self(); + #endif + if (locked) { + // to operate safely + rwMutex.handlerMutex.lock(); + + // decrement the writer number first + --rwMutex.writerCount; + + #if TRACE_MYRWMUTEX + std::cout << thread << "/" << locknumber << " / unlocking - W (release)"; + #endif + + if (!rwMutex.writerCount) { + #if TRACE_MYRWMUTEX + rwMutex.lastWriterFile = ""; + rwMutex.lastWriterLine = 0; + rwMutex.ownerThread = NULL; + std::cout << " -- new owner possible!"; + #endif + // The writer count is null again, so we can wake up the next writer or reader + rwMutex.access.broadcast(); + } + #if TRACE_MYRWMUTEX + std::cout << " <<< ReaderCount: " << rwMutex.readerCount << " - WriterCount: " << rwMutex.writerCount << std::endl; + #endif + + rwMutex.handlerMutex.unlock(); + + locked = false; + } + #if TRACE_MYRWMUTEX + else std::cout << thread << "/" << locknumber << " / already unlocked by this object - W (release)" << std::endl; + #endif + } +}; + +#if TRACE_MYRWMUTEX +#define MYREADERLOCK(ln, e) MyReaderLock ln(e, #e, __FILE__, __LINE__); +#define MYWRITERLOCK(ln, e) MyWriterLock ln(e, #e, __FILE__, __LINE__); +#define MYREADERLOCK_ACQUIRE(ln) ln.acquire(__FILE__, __LINE__); +#define MYWRITERLOCK_ACQUIRE(ln) ln.acquire(__FILE__, __LINE__); +#define MYREADERLOCK_RELEASE(ln) ln.release(__FILE__, __LINE__); +#define MYWRITERLOCK_RELEASE(ln) ln.release(__FILE__, __LINE__); +#else +#define MYREADERLOCK(ln, e) MyReaderLock ln(e); +#define MYWRITERLOCK(ln, e) MyWriterLock ln(e); +#define MYREADERLOCK_ACQUIRE(ln) ln.acquire(); +#define MYWRITERLOCK_ACQUIRE(ln) ln.acquire(); +#define MYREADERLOCK_RELEASE(ln) ln.release(); +#define MYWRITERLOCK_RELEASE(ln) ln.release(); +#endif + /** * @brief subclass of Gtk::ScrolledWindow in order to handle the scrollwheel */ diff --git a/rtgui/histogrampanel.cc b/rtgui/histogrampanel.cc index fd66bcff8..54e568328 100644 --- a/rtgui/histogrampanel.cc +++ b/rtgui/histogrampanel.cc @@ -353,6 +353,9 @@ void HistogramRGBArea::renderRGBMarks (int r, int g, int b) { return; } + // Mostly not necessary, but should be in some case + GThreadLock lock; // All GUI acces from idle_add callbacks or separate thread HAVE to be protected + Glib::RefPtr window = get_window(); int winx, winy, winw, winh, wind; window->get_geometry(winx, winy, winw, winh, wind); @@ -420,8 +423,6 @@ void HistogramRGBArea::renderRGBMarks (int r, int g, int b) { int histrgbupdate (void* data) { - gdk_threads_enter (); - HistogramRGBAreaIdleHelper* harih = static_cast(data); if (harih->destroyed) { @@ -429,7 +430,6 @@ int histrgbupdate (void* data) { delete harih; else harih->pending--; - gdk_threads_leave (); return 0; } @@ -437,7 +437,6 @@ int histrgbupdate (void* data) { harih->harea->queue_draw (); harih->pending--; - gdk_threads_leave (); return 0; } @@ -453,7 +452,7 @@ void HistogramRGBArea::update (int valh, int rh, int gh, int bh) { } else valid = false; - + harih->pending++; g_idle_add (histrgbupdate, harih); } @@ -618,8 +617,9 @@ void HistogramArea::update (LUTu &histRed, LUTu &histGreen, LUTu &histBlue, LUTu } else valid = false; - + haih->pending++; + // Can be done outside of the GUI thread g_idle_add (histupdateUI, haih); } diff --git a/rtgui/lwbutton.cc b/rtgui/lwbutton.cc index 628c7fa6e..ea4bf182f 100644 --- a/rtgui/lwbutton.cc +++ b/rtgui/lwbutton.cc @@ -17,6 +17,7 @@ * along with RawTherapee. If not, see . */ #include "lwbutton.h" +#include "guiutils.h" LWButton::LWButton (Cairo::RefPtr i, int aCode, void* aData, Alignment ha, Alignment va, Glib::ustring tooltip) : halign(ha), valign(va), icon(i), state(Normal), listener(NULL), actionCode(aCode), actionData(aData), toolTip(tooltip) { @@ -145,6 +146,7 @@ bool LWButton::releaseNotify (int x, int y) { void LWButton::redraw (Cairo::RefPtr context) { + GThreadLock lock; // All GUI acces from idle_add callbacks or separate thread HAVE to be protected context->set_line_width (1.0); context->set_antialias (Cairo::ANTIALIAS_SUBPIXEL); context->rectangle (xpos+0.5, ypos+0.5, w-1.0, h-1.0); @@ -162,10 +164,10 @@ void LWButton::redraw (Cairo::RefPtr context) { if (state==Pressed_In) dilat++; - if (icon) { - context->set_source (icon, xpos+dilat, ypos+dilat); - context->paint (); - } + if (icon) { + context->set_source (icon, xpos+dilat, ypos+dilat); + context->paint (); + } } void LWButton::getAlignment (Alignment& ha, Alignment& va) { diff --git a/rtgui/lwbutton.h b/rtgui/lwbutton.h index 23ff322a6..b50478abd 100644 --- a/rtgui/lwbutton.h +++ b/rtgui/lwbutton.h @@ -25,6 +25,7 @@ class LWButton; class LWButtonListener { public: + virtual ~LWButtonListener () {} virtual void buttonPressed (LWButton* button, int actionCode, void* actionData) {} virtual void redrawNeeded (LWButton* button) {} }; diff --git a/rtgui/lwbuttonset.cc b/rtgui/lwbuttonset.cc index b024e0914..fea24d41c 100644 --- a/rtgui/lwbuttonset.cc +++ b/rtgui/lwbuttonset.cc @@ -85,7 +85,7 @@ void LWButtonSet::arrangeButtons (int x, int y, int w, int h) { void LWButtonSet::move (int nx, int ny) { - for (size_t i=0; igetPosition (x, y); buttons[i]->setPosition (x+nx-ax, y+ny-ay); @@ -97,7 +97,7 @@ void LWButtonSet::move (int nx, int ny) { void LWButtonSet::redraw (Cairo::RefPtr context) { - for (size_t i=0; iredraw (context); } diff --git a/rtgui/main.cc b/rtgui/main.cc index 3bffa2f93..8ecd9798e 100644 --- a/rtgui/main.cc +++ b/rtgui/main.cc @@ -24,6 +24,7 @@ // This file is for your program, I won't touch it again! #include "config.h" +#include #include #include #include @@ -53,6 +54,22 @@ Glib::ustring creditsPath; Glib::ustring licensePath; Glib::ustring argv1; bool simpleEditor; +Glib::Thread* mainThread; + + +// This recursive mutex will be used by g_thread_enter/leave instead of a simple mutex +static Glib::RecMutex myGdkRecMutex; + +static void myGdkLockEnter() { myGdkRecMutex.lock(); } +static void myGdkLockLeave() { + // Automatic gdk_flush for non main tread + #if AUTO_GDK_FLUSH + if (Glib::Thread::self() != mainThread) { + gdk_flush(); + } + #endif + myGdkRecMutex.unlock(); +} /* Process line command options * Returns @@ -105,9 +122,12 @@ int main(int argc, char **argv) #endif Glib::thread_init(); + gdk_threads_set_lock_functions(G_CALLBACK(myGdkLockEnter), ((GCallback) (myGdkLockLeave))); gdk_threads_init(); Gio::init (); + mainThread = Glib::Thread::self(); + Options::load (); extProgStore->init(); SoundManager::init(); @@ -158,9 +178,8 @@ int main(int argc, char **argv) printf("Error: no default settings to update!\n"); #endif - - RTWindow *rtWindow = new class RTWindow(); gdk_threads_enter (); + RTWindow *rtWindow = new class RTWindow(); // alerting users if the default raw and image profiles are missing if (options.is_defProfRawMissing()) { diff --git a/rtgui/mydiagonalcurve.cc b/rtgui/mydiagonalcurve.cc index 9399e9e86..e48c13a95 100644 --- a/rtgui/mydiagonalcurve.cc +++ b/rtgui/mydiagonalcurve.cc @@ -806,6 +806,7 @@ void MyDiagonalCurve::updateBackgroundHistogram (LUTu & hist) { bghistvalid = false; mcih->pending++; + // Can be done outside of the GUI thread g_idle_add (diagonalmchistupdateUI, mcih); } diff --git a/rtgui/options.cc b/rtgui/options.cc index ca9aa2abf..5613517b3 100644 --- a/rtgui/options.cc +++ b/rtgui/options.cc @@ -250,6 +250,8 @@ void Options::setDefaults () { version = "0.0.0.0"; // temporary value; will be correctly set in RTWindow::on_realize thumbSize = 240; thumbSizeTab = 80; + thumbSizeQueue = 100; + sameThumbSize = true; // preferring speed of switch between file browser and single editor tab showHistory = true; showFilePanelState = 0; // Not used anymore ; was the thumb strip state showInfo = true; @@ -588,6 +590,8 @@ if (keyFile.has_group ("Profiles")) { if (keyFile.has_group ("File Browser")) { if (keyFile.has_key ("File Browser", "ThumbnailSize")) thumbSize = keyFile.get_integer ("File Browser", "ThumbnailSize"); if (keyFile.has_key ("File Browser", "ThumbnailSizeTab")) thumbSizeTab = keyFile.get_integer ("File Browser", "ThumbnailSizeTab"); + if (keyFile.has_key ("File Browser", "ThumbnailSizeQueue")) thumbSizeQueue = keyFile.get_integer ("File Browser", "ThumbnailSizeQueue"); + if (keyFile.has_key ("File Browser", "SameThumbSize")) sameThumbSize = keyFile.get_integer ("File Browser", "SameThumbSize"); if (keyFile.has_key ("File Browser", "BrowseOnlyRaw")) fbOnlyRaw = keyFile.get_boolean ("File Browser", "BrowseOnlyRaw"); if (keyFile.has_key ("File Browser", "BrowserShowsDate")) fbShowDateTime = keyFile.get_boolean ("File Browser", "BrowserShowsDate"); if (keyFile.has_key ("File Browser", "BrowserShowsExif")) fbShowBasicExif = keyFile.get_boolean ("File Browser", "BrowserShowsExif"); @@ -816,6 +820,8 @@ int Options::saveToFile (Glib::ustring fname) { keyFile.set_boolean ("File Browser", "BrowserShowsHidden", fbShowHidden); keyFile.set_integer ("File Browser", "ThumbnailSize", thumbSize); keyFile.set_integer ("File Browser", "ThumbnailSizeTab", thumbSizeTab); + keyFile.set_integer ("File Browser", "ThumbnailSizeQueue", thumbSizeQueue); + keyFile.set_integer ("File Browser", "SameThumbSize", sameThumbSize); keyFile.set_integer ("File Browser", "MaxPreviewHeight", maxThumbnailHeight); keyFile.set_integer ("File Browser", "MaxCacheEntries", maxCacheEntries); Glib::ArrayHandle pext = parseExtensions; diff --git a/rtgui/options.h b/rtgui/options.h index bf4bbc49a..1ec326b50 100644 --- a/rtgui/options.h +++ b/rtgui/options.h @@ -122,7 +122,8 @@ class Options { bool multiUser; static Glib::ustring rtdir; Glib::ustring version; - int thumbSize,thumbSizeTab; + int thumbSize,thumbSizeTab, thumbSizeQueue; + bool sameThumbSize; // Will use only one thumb size for the file browser and the single editor tab, and avoid recomputing them bool showHistory; int showFilePanelState; // 0: normal, 1: maximized, 2: normal, 3: hidden bool showInfo; diff --git a/rtgui/pparamschangelistener.h b/rtgui/pparamschangelistener.h index 14cc865a7..10eff09a5 100644 --- a/rtgui/pparamschangelistener.h +++ b/rtgui/pparamschangelistener.h @@ -26,9 +26,18 @@ class PParamsChangeListener { public: + virtual ~PParamsChangeListener() {} virtual void procParamsChanged (rtengine::procparams::ProcParams* params, rtengine::ProcEvent ev, Glib::ustring descr, ParamsEdited* paramsEdited=NULL) {} virtual void clearParamChanges () {} }; +class BatchPParamsChangeListener { + + public: + virtual ~BatchPParamsChangeListener() {} + virtual void beginBatchPParamsChange(int numberOfEntries) {} + virtual void endBatchPParamsChange() {} +}; + #endif diff --git a/rtgui/previewhandler.cc b/rtgui/previewhandler.cc index a7d2b2081..6f409efde 100644 --- a/rtgui/previewhandler.cc +++ b/rtgui/previewhandler.cc @@ -23,8 +23,8 @@ using namespace rtengine; using namespace rtengine::procparams; -PreviewHandler::PreviewHandler () : image(NULL) { - +PreviewHandler::PreviewHandler () : image(NULL), previewScale(1.) { + pih = new PreviewHandlerIdleHelper; pih->phandler = this; pih->destroyed = false; diff --git a/rtgui/previewhandler.h b/rtgui/previewhandler.h index da7bcee88..c51a2c4ef 100644 --- a/rtgui/previewhandler.h +++ b/rtgui/previewhandler.h @@ -26,6 +26,7 @@ class PreviewListener { public: + virtual ~PreviewListener () {} virtual void previewImageChanged () {} }; @@ -43,7 +44,7 @@ class PreviewHandler : public rtengine::PreviewImageListener { friend int imageReadyUI (void* data); protected: - rtengine::IImage8* image; + rtengine::IImage8* image; rtengine::procparams::CropParams cropParams; double previewScale; PreviewHandlerIdleHelper* pih; diff --git a/rtgui/profilechangelistener.h b/rtgui/profilechangelistener.h index 6465f64c9..b2673391a 100644 --- a/rtgui/profilechangelistener.h +++ b/rtgui/profilechangelistener.h @@ -25,9 +25,19 @@ class ProfileChangeListener { public: + virtual ~ProfileChangeListener() {} virtual void profileChange (const rtengine::procparams::PartialProfile* nparams, rtengine::ProcEvent event, const Glib::ustring& descr, const ParamsEdited* paramsEdited=NULL) {} virtual void setDefaults (rtengine::procparams::ProcParams* defparams) {} }; +class BatchProfileChangeListener { + + public: + virtual ~BatchProfileChangeListener() {} + virtual void beginBatchProfileChange(int numberOfEntries) {} + virtual void endBatchProfileChange() {} +}; + + #endif diff --git a/rtgui/thumbbrowserbase.cc b/rtgui/thumbbrowserbase.cc index fa8d5f7d1..248ea2218 100644 --- a/rtgui/thumbbrowserbase.cc +++ b/rtgui/thumbbrowserbase.cc @@ -58,19 +58,25 @@ ThumbBrowserBase::ThumbBrowserBase () } void ThumbBrowserBase::scrollChanged () { - for (size_t i=0; isetOffset ((int)(hscroll.get_value()), (int)(vscroll.get_value())); + } internal.setPosition ((int)(hscroll.get_value()), (int)(vscroll.get_value())); - if (!internal.isDirty()) { + if (!internal.isDirty()) { internal.setDirty (); internal.queue_draw (); -// gdk_window_process_updates (get_window()->gobj(), true); } } void ThumbBrowserBase::scroll (int direction) { + // GUI already acquired when here if (arrangement==TB_Vertical) vscroll.set_value (vscroll.get_value() + (direction==GDK_SCROLL_DOWN ? +1 : -1) * vscroll.get_adjustment()->get_step_increment()); else @@ -78,6 +84,7 @@ void ThumbBrowserBase::scroll (int direction) { } void ThumbBrowserBase::scrollPage (int direction) { + // GUI already acquired when here if (arrangement==TB_Vertical) vscroll.set_value (vscroll.get_value() + (direction==GDK_SCROLL_DOWN ? +1 : -1) * vscroll.get_adjustment()->get_page_increment()); else @@ -107,6 +114,9 @@ void ThumbBrowserBase::internalAreaResized (Gtk::Allocation& req) { void ThumbBrowserBase::configScrollBars () { + // HOMBRE:DELETE ME? + GThreadLock tLock; // Acquire the GUI + if (inW>0 && inH>0) { int iw = internal.get_width (); @@ -136,10 +146,14 @@ void ThumbBrowserBase::configScrollBars () { } void ThumbBrowserBase::arrangeFiles () { - // TODO: Check for Linux - #ifdef WIN32 - Glib::RWLock::ReaderLock l(entryRW); - #endif + + #if PROTECT_VECTORS + MYREADERLOCK(l, entryRW); + #endif + + // GUI already locked by ::redraw, the only caller of this method for now. + // We could lock it one more time, there's no harm excepted (negligible) speed penalty + //GThreadLock lock; int N = fd.size (); // apply filter @@ -152,7 +166,7 @@ void ThumbBrowserBase::arrangeFiles () { if (!fd[i]->filtered && fd[i]->getMinimalHeight() > rowHeight) rowHeight = fd[i]->getMinimalHeight (); - if (arrangement==TB_Horizontal) { + if (arrangement==TB_Horizontal) { int numOfRows = 1; // if (rowHeight>0) { @@ -183,6 +197,10 @@ void ThumbBrowserBase::arrangeFiles () { } currx += maxw; } + #if PROTECT_VECTORS + MYREADERLOCK_RELEASE(l); + #endif + // This will require a Writer access resizeThumbnailArea (currx, numOfRows*rowHeight); } else { @@ -234,13 +252,17 @@ void ThumbBrowserBase::arrangeFiles () { if (currx>0) // there were thumbnails placed in the row curry += rowHeight; } + #if PROTECT_VECTORS + MYREADERLOCK_RELEASE(l); + #endif + // This will require a Writer access resizeThumbnailArea (colsWidth, curry); } } -void ThumbBrowserBase::Internal::on_realize() -{ +void ThumbBrowserBase::Internal::on_realize() { + // Gtk signals automatically acquire the GUI (i.e. this method is enclosed by gdk_thread_enter and gdk_thread_leave) Cairo::FontOptions cfo; cfo.set_antialias (Cairo::ANTIALIAS_SUBPIXEL); get_pango_context()->set_cairo_font_options (cfo); @@ -255,12 +277,20 @@ void ThumbBrowserBase::Internal::on_realize() } bool ThumbBrowserBase::Internal::on_query_tooltip (int x, int y, bool keyboard_tooltip, const Glib::RefPtr& tooltip) { + // Gtk signals automatically acquire the GUI (i.e. this method is enclosed by gdk_thread_enter and gdk_thread_leave) Glib::ustring ttip = ""; + + { + #if PROTECT_VECTORS + MYREADERLOCK(l, parent->entryRW); + #endif + for (size_t i=0; ifd.size(); i++) if (parent->fd[i]->drawable && parent->fd[i]->inside (x, y)) { ttip = parent->fd[i]->getToolTip (x, y); break; } + } if (ttip!="") { tooltip->set_text (ttip); return true; @@ -270,8 +300,8 @@ bool ThumbBrowserBase::Internal::on_query_tooltip (int x, int y, bool keyboard_t } void ThumbBrowserBase::styleChanged (const Glib::RefPtr& style) { - - refreshThumbImages (); + // GUI will be acquired by refreshThumbImages + refreshThumbImages (); } ThumbBrowserBase::Internal::Internal () : ofsX(0), ofsY(0), parent(NULL), dirty(true) { @@ -287,10 +317,12 @@ void ThumbBrowserBase::Internal::setPosition (int x, int y) { } bool ThumbBrowserBase::Internal::on_key_press_event (GdkEventKey* event) { + // Gtk signals automatically acquire the GUI (i.e. this method is enclosed by gdk_thread_enter and gdk_thread_leave) return parent->keyPressed (event); } bool ThumbBrowserBase::Internal::on_button_press_event (GdkEventButton* event) { + // Gtk signals automatically acquire the GUI (i.e. this method is enclosed by gdk_thread_enter and gdk_thread_leave) grab_focus (); parent->eventTime = event->time; @@ -310,30 +342,43 @@ bool ThumbBrowserBase::Internal::on_button_press_event (GdkEventButton* event) { } void ThumbBrowserBase::buttonPressed (int x, int y, int button, GdkEventType type, int state, int clx, int cly, int clw, int clh) { + // GUI already acquired + ThumbBrowserEntryBase* fileDescr = NULL; bool handled = false; - + { - for (size_t i=0; idrawable) { - if (fd[i]->inside (x, y) && fd[i]->insideWindow (clx, cly, clw, clh)) + if (fd[i]->inside (x, y) && fd[i]->insideWindow (clx, cly, clw, clh)) fileDescr = fd[i]; bool b = fd[i]->pressNotify (button, type, state, x, y); handled = handled || b; - } + } } if (handled || (fileDescr && fileDescr->processing)) return; - + + { + #if PROTECT_VECTORS + MYWRITERLOCK(l, entryRW); + #endif + if (selected.size()==1 && type==GDK_2BUTTON_PRESS && button==1) doubleClicked (selected[0]); else if (button==1 && type==GDK_BUTTON_PRESS) { - if (fileDescr && state & GDK_SHIFT_MASK) { + if (fileDescr && (state & GDK_SHIFT_MASK)) { if (selected.empty()) { selected.push_back (fileDescr); fileDescr->selected = true; lastClicked = fileDescr; + #if PROTECT_VECTORS + MYWRITERLOCK_RELEASE(l); + #endif selectionChanged (); } else { @@ -363,16 +408,19 @@ void ThumbBrowserBase::buttonPressed (int x, int y, int button, GdkEventType typ selected[i]->selected = false; selected.clear (); // select thumbnails in the interval - for (size_t i=startx; i<=endx; i++) { + for (size_t i=startx; i<=endx; i++) { if (!fd[i]->filtered) { fd[i]->selected = true; selected.push_back (fd[i]); } } + #if PROTECT_VECTORS + MYWRITERLOCK_RELEASE(l); + #endif selectionChanged (); } } - else if (fileDescr && state & GDK_CONTROL_MASK) { + else if (fileDescr && (state & GDK_CONTROL_MASK)) { std::vector::iterator i = std::find (selected.begin(), selected.end(), fileDescr); if (i!=selected.end()) { (*i)->selected = false; @@ -383,10 +431,13 @@ void ThumbBrowserBase::buttonPressed (int x, int y, int button, GdkEventType typ fileDescr->selected = true; } lastClicked = fileDescr; + #if PROTECT_VECTORS + MYWRITERLOCK_RELEASE(l); + #endif selectionChanged (); } else { - for (size_t i=0; iselected = false; selected.clear (); if (fileDescr) { @@ -394,28 +445,36 @@ void ThumbBrowserBase::buttonPressed (int x, int y, int button, GdkEventType typ fileDescr->selected = true; } lastClicked = fileDescr; + #if PROTECT_VECTORS + MYWRITERLOCK_RELEASE(l); + #endif selectionChanged (); } } else if (fileDescr && button==3 && type==GDK_BUTTON_PRESS) { if (!fileDescr->selected) { - for (size_t i=0; iselected = false; selected.clear (); fileDescr->selected = true; selected.push_back (fileDescr); lastClicked = fileDescr; + #if PROTECT_VECTORS + MYWRITERLOCK_RELEASE(l); + #endif selectionChanged (); } + #if PROTECT_VECTORS + MYWRITERLOCK_RELEASE(l); + #endif rightClicked (fileDescr); } + } // end of MYWRITERLOCK(l, entryRW); + } bool ThumbBrowserBase::Internal::on_expose_event(GdkEventExpose* event) { - // TODO: Check for Linux - #ifdef WIN32 - Glib::RWLock::ReaderLock l(parent->entryRW); - #endif + // Gtk signals automatically acquire the GUI (i.e. this method is enclosed by gdk_thread_enter and gdk_thread_leave) dirty = false; @@ -428,6 +487,12 @@ bool ThumbBrowserBase::Internal::on_expose_event(GdkEventExpose* event) { // draw thumbnails Glib::RefPtr context = get_pango_context (); context->set_font_description (get_style()->get_font()); + + { + #if PROTECT_VECTORS + MYWRITERLOCK(l, parent->entryRW); + #endif + for (size_t i=0; ifd.size() && !dirty; i++) { // if dirty meanwhile, cancel and wait for next redraw if (!parent->fd[i]->drawable || !parent->fd[i]->insideWindow (0, 0, w, h)) parent->fd[i]->updatepriority = false; @@ -436,36 +501,56 @@ bool ThumbBrowserBase::Internal::on_expose_event(GdkEventExpose* event) { parent->fd[i]->draw (); } } + } return true; } bool ThumbBrowserBase::Internal::on_button_release_event (GdkEventButton* event) { + // Gtk signals automatically acquire the GUI (i.e. this method is enclosed by gdk_thread_enter and gdk_thread_leave) int w = get_width(); int h = get_height(); + #if PROTECT_VECTORS + MYREADERLOCK(l, parent->entryRW); + #endif + for (size_t i=0; ifd.size(); i++) if (parent->fd[i]->drawable && parent->fd[i]->insideWindow (0, 0, w, h)) { - parent->fd[i]->releaseNotify (event->button, event->type, event->state, (int)event->x, (int)event->y); + ThumbBrowserEntryBase* tbe = parent->fd[i]; + #if PROTECT_VECTORS + MYREADERLOCK_RELEASE(l); + #endif + // This will require a Writer access... + tbe->releaseNotify (event->button, event->type, event->state, (int)event->x, (int)event->y); + #if PROTECT_VECTORS + MYREADERLOCK_ACQUIRE(l); + #endif } return true; } bool ThumbBrowserBase::Internal::on_motion_notify_event (GdkEventMotion* event) { + // Gtk signals automatically acquire the GUI (i.e. this method is enclosed by gdk_thread_enter and gdk_thread_leave) int w = get_width(); int h = get_height(); + #if PROTECT_VECTORS + MYREADERLOCK(l, parent->entryRW); + #endif + for (size_t i=0; ifd.size(); i++) if (parent->fd[i]->drawable && parent->fd[i]->insideWindow (0, 0, w, h)) { - #ifdef WIN32 - //l.release(); // motionNotify calls the queue, which locks - #endif + /*#if PROTECT_VECTORS + MYREADERLOCK_RELEASE(l); // motionNotify calls the queue, which locks + #endif*/ parent->fd[i]->motionNotify ((int)event->x, (int)event->y); } return true; } bool ThumbBrowserBase::Internal::on_scroll_event (GdkEventScroll* event) { + // Gtk signals automatically acquire the GUI (i.e. this method is enclosed by gdk_thread_enter and gdk_thread_leave) parent->scroll (event->direction); return true; @@ -474,6 +559,7 @@ bool ThumbBrowserBase::Internal::on_scroll_event (GdkEventScroll* event) { void ThumbBrowserBase::redraw () { + GThreadLock lock; arrangeFiles (); queue_draw (); } @@ -481,30 +567,31 @@ void ThumbBrowserBase::redraw () { void ThumbBrowserBase::zoomChanged (bool zoomIn) { int newHeight=0; - int optThumbSize=getCurrentThumbSize(); + int optThumbSize=getThumbnailHeight(); if (zoomIn) - for (size_t i=0; i optThumbSize) break; } else - for (size_t i=options.thumbnailZoomRatios.size()-1; i>0; i--) { + for (size_t i=options.thumbnailZoomRatios.size()-1; i>0; i--) { newHeight = (int)(options.thumbnailZoomRatios[i] * getMaxThumbnailHeight()); if (newHeight < optThumbSize) break; } previewHeight = newHeight; - if (inTabMode) options.thumbSizeTab = newHeight; else options.thumbSize = newHeight; - { - // TODO: Check for Linux - #ifdef WIN32 - Glib::RWLock::WriterLock l(entryRW); - #endif + saveThumbnailHeight(newHeight); - for (size_t i=0; iresize (previewHeight); - } + { + #if PROTECT_VECTORS + MYWRITERLOCK(l, entryRW); + #endif + + for (size_t i=0; iresize (previewHeight); + } redraw (); #ifdef WIN32 @@ -512,44 +599,46 @@ void ThumbBrowserBase::zoomChanged (bool zoomIn) { #endif } -int ThumbBrowserBase::getCurrentThumbSize() { return inTabMode ? options.thumbSizeTab : options.thumbSize; } - void ThumbBrowserBase::refreshThumbImages () { - { - // TODO: Check for Linux - #ifdef WIN32 - Glib::RWLock::WriterLock l(entryRW); - #endif - int previewHeight = getCurrentThumbSize(); - for (size_t i=0; iresize (previewHeight); - } + int previewHeight = getThumbnailHeight(); + { + #if PROTECT_VECTORS + MYWRITERLOCK(l, entryRW); + #endif + + for (size_t i=0; iresize (previewHeight); + } redraw (); } void ThumbBrowserBase::refreshQuickThumbImages () { - // TODO: Check for Linux - #ifdef WIN32 - Glib::RWLock::WriterLock l(entryRW); - #endif + #if PROTECT_VECTORS + MYWRITERLOCK(l, entryRW); + #endif - for (size_t i=0; irefreshQuickThumbnailImage (); + for (size_t i=0; irefreshQuickThumbnailImage (); } void ThumbBrowserBase::refreshEditedState (const std::set& efiles) { editedFiles = efiles; + { + #if PROTECT_VECTORS + MYREADERLOCK(l, entryRW); + #endif + for (size_t i=0; iframed = editedFiles.find (fd[i]->filename)!=editedFiles.end(); + } queue_draw (); } - void ThumbBrowserBase::setArrangement (Arrangement a) { - arrangement = a; + arrangement = a; redraw (); } @@ -557,31 +646,40 @@ void ThumbBrowserBase::enableTabMode(bool enable) { inTabMode = enable; arrangement = inTabMode ? ThumbBrowserBase::TB_Horizontal : ThumbBrowserBase::TB_Vertical; - if (options.thumbSizeTab!=options.thumbSize) { - // TODO: Check for Linux - #ifdef WIN32 - Glib::RWLock::WriterLock l(entryRW); - #endif + if (!options.sameThumbSize && (options.thumbSizeTab!=options.thumbSize)) { + #if PROTECT_VECTORS + MYWRITERLOCK(l, entryRW); + #endif - for (size_t i=0; iresize (getCurrentThumbSize()); + for (size_t i=0; iresize (getThumbnailHeight()); } redraw (); // Scroll to selected position if going into ribbon mode or back // Tab mode is horizontal, file browser is vertical + { + #if PROTECT_VECTORS + MYREADERLOCK(l, entryRW); + #endif + if (!selected.empty()) { if (inTabMode) { - double h=selected[0]->getStartX(); - hscroll.set_value (min(h, hscroll.get_adjustment()->get_upper())); + double h=selected[0]->getStartX(); + #if PROTECT_VECTORS + MYREADERLOCK_RELEASE(l); + #endif + hscroll.set_value (min(h, hscroll.get_adjustment()->get_upper())); } else { double v=selected[0]->getStartY(); + #if PROTECT_VECTORS + MYREADERLOCK_RELEASE(l); + #endif vscroll.set_value (min(v, vscroll.get_adjustment()->get_upper())); } } - - + } } void ThumbBrowserBase::initEntry (ThumbBrowserEntryBase* entry) { @@ -602,6 +700,10 @@ void ThumbBrowserBase::setScrollPosition (double h, double v) { int ThumbBrowserBase::getEffectiveHeight() { int h=hscroll.get_height() + 2; // have 2 pixels rounding error for scroll bars to appear + #if PROTECT_VECTORS + MYREADERLOCK(l, entryRW); + #endif + // Filtered items do not change in size, so take a non-filtered for (size_t i=0;ifiltered) { @@ -610,10 +712,13 @@ int ThumbBrowserBase::getEffectiveHeight() { } return h; -} +} void ThumbBrowserBase::redrawNeeded (ThumbBrowserEntryBase* entry) { + // HOMBRE:DELETE ME? + GThreadLock tLock; // Acquire the GUI + if (entry->insideWindow (0, 0, internal.get_width(), internal.get_height())) { if (!internal.isDirty ()) { internal.setDirty (); diff --git a/rtgui/thumbbrowserbase.h b/rtgui/thumbbrowserbase.h index 90164596a..fd1b84712 100644 --- a/rtgui/thumbbrowserbase.h +++ b/rtgui/thumbbrowserbase.h @@ -23,6 +23,7 @@ #include "thumbbrowserentrybase.h" #include #include "options.h" +#include "guiutils.h" /* * Class handling the list of ThumbBrowserEntry objects and their position in it's allocated space @@ -54,6 +55,8 @@ class ThumbBrowserBase : public Gtk::VBox { protected: virtual int getMaxThumbnailHeight() const { return options.maxThumbnailHeight; } // Differs between batch and file + virtual void saveThumbnailHeight (int height)=0; + virtual int getThumbnailHeight ()=0; Internal internal; Gtk::HScrollbar hscroll; @@ -62,7 +65,6 @@ class ThumbBrowserBase : public Gtk::VBox { int inW, inH; bool inTabMode; // Tab mode has e.g. different preview heights - int getCurrentThumbSize(); // depending on filmstrip/file browser mode void resizeThumbnailArea (int w, int h); void internalAreaResized (Gtk::Allocation& req); @@ -80,7 +82,7 @@ class ThumbBrowserBase : public Gtk::VBox { int eventTime; - Glib::RWLock entryRW; // Locks access to following vectors + MyRWMutex entryRW; // Locks access to following 'fd' AND 'selected' std::vector fd; std::vector selected; ThumbBrowserEntryBase* lastClicked; diff --git a/rtgui/thumbbrowserentrybase.cc b/rtgui/thumbbrowserentrybase.cc index cfb2e7acf..865291228 100644 --- a/rtgui/thumbbrowserentrybase.cc +++ b/rtgui/thumbbrowserentrybase.cc @@ -22,26 +22,18 @@ #include "../rtengine/mytime.h" ThumbBrowserEntryBase::ThumbBrowserEntryBase (const Glib::ustring& fname) - : preh(0), preview(NULL), buttonSet(NULL), exp_width(0), exp_height(0), redrawRequests(0), - parent(NULL), filename(fname), exifline(""), datetimeline(""), selected(false), - drawable(false),framed(false), processing(false), italicstyle(false), - updatepriority(false) { - - shortname = Glib::path_get_basename (fname); - dispname = shortname; - - upperMargin = 6; - borderWidth = 1; - sideMargin = 8; - lowerMargin = 8; - textGap = 6; - - ofsX = ofsY = 0; -} + : fnlabw(0), fnlabh(0), dtlabw(0), dtlabh(0), exlabw(0), exlabh(0), prew(0), preh(0), + prex(0), prey(0), upperMargin(6), borderWidth(1), textGap(6), sideMargin(8), lowerMargin(8), + preview(NULL), dispname(Glib::path_get_basename (fname)), buttonSet(NULL), width(0), height(0), + exp_width(0), exp_height(0), startx(0), starty(0), ofsX(0), ofsY(0), redrawRequests(0), + parent(NULL), bbSelected(false), bbFramed(false), bbPreview(NULL), + thumbnail(NULL), filename(fname), shortname(dispname), exifline(""), datetimeline(""), + selected(false), drawable(false), filtered(false), framed(false), processing(false), italicstyle(false), + edited(false), recentlysaved(false), updatepriority(false) {} ThumbBrowserEntryBase::~ThumbBrowserEntryBase () { - delete [] preview; + if (preview) delete [] preview; delete buttonSet; } @@ -259,7 +251,10 @@ void ThumbBrowserEntryBase::getTextSizes (int& infow, int& infoh) { } void ThumbBrowserEntryBase::resize (int h) { - Glib::RWLock::WriterLock l(lockRW); + + #if PROTECT_VECTORS + MYWRITERLOCK(l, lockRW); + #endif height = h; int old_preh = preh, old_width = width; @@ -348,7 +343,9 @@ void ThumbBrowserEntryBase::draw () { if (!drawable || !parent) return; - Glib::RWLock::ReaderLock l(lockRW); // No resizes, position moves etc. inbetween + #if PROTECT_VECTORS + MYREADERLOCK(l, lockRW); // No resizes, position moves etc. inbetween + #endif int bbWidth, bbHeight; if (backBuffer) @@ -376,12 +373,16 @@ void ThumbBrowserEntryBase::draw () { // redraw button set above the thumbnail if (buttonSet) { buttonSet->setColors (selected ? bgs : bgn, selected ? bgn : bgs); - buttonSet->redraw (w->get_window()->create_cairo_context()); + Cairo::RefPtr cc = w->get_window()->create_cairo_context(); + buttonSet->redraw (cc); } } void ThumbBrowserEntryBase::setPosition (int x, int y, int w, int h) { - Glib::RWLock::WriterLock l(lockRW); + + #if PROTECT_VECTORS + MYWRITERLOCK(l, lockRW); + #endif exp_width = w; exp_height = h; @@ -393,7 +394,10 @@ void ThumbBrowserEntryBase::setPosition (int x, int y, int w, int h) { } void ThumbBrowserEntryBase::setOffset (int x, int y) { - Glib::RWLock::WriterLock l(lockRW); + + #if PROTECT_VECTORS + MYWRITERLOCK(l, lockRW); + #endif ofsX = -x; ofsY = -y; diff --git a/rtgui/thumbbrowserentrybase.h b/rtgui/thumbbrowserentrybase.h index 20337eac0..a49860c70 100644 --- a/rtgui/thumbbrowserentrybase.h +++ b/rtgui/thumbbrowserentrybase.h @@ -22,6 +22,7 @@ #include #include "lwbuttonset.h" #include "thumbnail.h" +#include "guiutils.h" class ThumbBrowserBase; class ThumbBrowserEntryBase { @@ -42,7 +43,7 @@ protected: int lowerMargin; - Glib::RWLock lockRW; // Locks access to all image thumb changing actions + MyRWMutex lockRW; // Locks access to all image thumb changing actions guint8* preview; // holds the preview image. used in updateBackBuffer. TODO Olli: Make a cache to reduce mem significantly diff --git a/rtgui/thumbimageupdater.cc b/rtgui/thumbimageupdater.cc index da76e3665..4237ad5e9 100644 --- a/rtgui/thumbimageupdater.cc +++ b/rtgui/thumbimageupdater.cc @@ -48,6 +48,8 @@ public: Job(): tbe_(0), + priority_(NULL), + upgrade_(false), listener_(0) {} @@ -86,7 +88,7 @@ public: Glib::Cond inactive_; void - processNextJob(void) + processNextJob() { Job j; diff --git a/rtgui/thumbnail.cc b/rtgui/thumbnail.cc index e3b5596fc..dae668cdc 100644 --- a/rtgui/thumbnail.cc +++ b/rtgui/thumbnail.cc @@ -33,13 +33,18 @@ using namespace rtengine::procparams; -Thumbnail::Thumbnail (CacheManager* cm, const Glib::ustring& fname, CacheImageData* cf) +Thumbnail::Thumbnail (CacheManager* cm, const Glib::ustring& fname, CacheImageData* cf, const rtengine::procparams::ProcParams *pparams) : fname(fname), cfs(*cf), cachemgr(cm), ref(1), enqueueNumber(0), tpp(NULL), pparamsValid(false), needsReProcessing(true),imageLoading(false), lastImg(NULL), - lastW(0), lastH(0), lastScale(0), initial_(false) { + lastW(0), lastH(0), lastScale(0), initial_(false) +{ - cfs.load (getCacheFileName ("data")+".txt"); - loadProcParams (); + if (pparams) { + this->pparams = *pparams; + pparamsValid = true; + } + else + loadProcParams (); _loadThumbnail (); generateExifDateTimeStrings (); @@ -55,25 +60,31 @@ Thumbnail::Thumbnail (CacheManager* cm, const Glib::ustring& fname, CacheImageDa setStage(cfs.inTrashOld); } - delete tpp; - tpp = 0; + delete tpp; + tpp = 0; } -Thumbnail::Thumbnail (CacheManager* cm, const Glib::ustring& fname, const std::string& md5) +Thumbnail::Thumbnail (CacheManager* cm, const Glib::ustring& fname, const std::string& md5, const rtengine::procparams::ProcParams *pparams) : fname(fname), cachemgr(cm), ref(1), enqueueNumber(0), tpp(NULL), pparamsValid(false), needsReProcessing(true),imageLoading(false), lastImg(NULL), - initial_(true) { + initial_(true) +{ cfs.md5 = md5; _generateThumbnailImage (); - loadProcParams (); + if (pparams) { + this->pparams = *pparams; + pparamsValid = true; + } + else + loadProcParams (); cfs.recentlySaved = false; - initial_ = false; - - delete tpp; - tpp = 0; + initial_ = false; + + delete tpp; + tpp = 0; } void Thumbnail::_generateThumbnailImage () { @@ -96,7 +107,7 @@ void Thumbnail::_generateThumbnailImage () { cfs.timeValid = false; if (ext.lowercase()=="jpg" || ext.lowercase()=="jpeg") { - tpp = rtengine::Thumbnail::loadFromImage (fname, tw, th, 1, infoFromImage (fname)); + tpp = rtengine::Thumbnail::loadFromImage (fname, tw, th, 1); if (tpp) cfs.format = FT_Jpeg; } @@ -106,8 +117,7 @@ void Thumbnail::_generateThumbnailImage () { cfs.format = FT_Png; } else if (ext.lowercase()=="tif" || ext.lowercase()=="tiff") { - int deg = infoFromImage (fname); - tpp = rtengine::Thumbnail::loadFromImage (fname, tw, th, 1, deg); + tpp = rtengine::Thumbnail::loadFromImage (fname, tw, th, 1); if (tpp) cfs.format = FT_Tiff; } diff --git a/rtgui/thumbnail.h b/rtgui/thumbnail.h index c88dbe7ef..37286306a 100644 --- a/rtgui/thumbnail.h +++ b/rtgui/thumbnail.h @@ -76,8 +76,8 @@ class Thumbnail { Glib::ustring getCacheFileName (Glib::ustring subdir); public: - Thumbnail (CacheManager* cm, const Glib::ustring& fname, CacheImageData* cf); - Thumbnail (CacheManager* cm, const Glib::ustring& fname, const std::string& md5); + Thumbnail (CacheManager* cm, const Glib::ustring& fname, CacheImageData* cf, const rtengine::procparams::ProcParams *pparams=NULL); + Thumbnail (CacheManager* cm, const Glib::ustring& fname, const std::string& md5, const rtengine::procparams::ProcParams *pparams=NULL); ~Thumbnail (); bool hasProcParams (); diff --git a/rtgui/tonecurve.cc b/rtgui/tonecurve.cc index 69a819f77..a055813ee 100644 --- a/rtgui/tonecurve.cc +++ b/rtgui/tonecurve.cc @@ -494,8 +494,6 @@ void ToneCurve::autoExpChanged (double expcomp, int bright, int contr, int black nextHlcompr = hlcompr; nextHlcomprthresh = hlcomprthresh; g_idle_add (autoExpChangedUI, this); - -// Glib::signal_idle().connect (sigc::mem_fun(*this, &ToneCurve::autoExpComputed_)); } void ToneCurve::enableAll () { @@ -515,6 +513,7 @@ void ToneCurve::enableAll () { bool ToneCurve::autoExpComputed_ () { + GThreadLock lock; // All GUI acces from idle_add callbacks or separate thread HAVE to be protected disableListener (); enableAll (); expcomp->setValue (nextExpcomp); diff --git a/rtgui/toolpanelcoord.cc b/rtgui/toolpanelcoord.cc index 83c44c167..4673d5c31 100644 --- a/rtgui/toolpanelcoord.cc +++ b/rtgui/toolpanelcoord.cc @@ -580,6 +580,7 @@ bool ToolPanelCoordinator::handleShortcutKey (GdkEventKey* event) { } void ToolPanelCoordinator::updateVScrollbars (bool hide) { + GThreadLock lock; // All GUI acces from idle_add callbacks or separate thread HAVE to be protected Gtk::PolicyType policy = hide ? Gtk::POLICY_NEVER : Gtk::POLICY_AUTOMATIC; exposurePanelSW->set_policy (Gtk::POLICY_AUTOMATIC, policy); detailsPanelSW->set_policy (Gtk::POLICY_AUTOMATIC, policy); @@ -589,9 +590,10 @@ void ToolPanelCoordinator::updateVScrollbars (bool hide) { } void ToolPanelCoordinator::updateTabsHeader (bool useIcons) { - TOITypes type = useIcons ? TOI_ICON : TOI_TEXT; + GThreadLock lock; // All GUI acces from idle_add callbacks or separate thread HAVE to be protected + TOITypes type = useIcons ? TOI_ICON : TOI_TEXT; - toiE->switchTo(type); + toiE->switchTo(type); toiD->switchTo(type); toiC->switchTo(type); toiT->switchTo(type); @@ -609,6 +611,7 @@ void ToolPanelCoordinator::updateTabsUsesIcons (bool useIcons) { } void ToolPanelCoordinator::toolSelected (ToolMode tool) { + GThreadLock lock; // All GUI acces from idle_add callbacks or separate thread HAVE to be protected switch (tool) { case TMCropSelect: crop->exp->set_expanded(true);