/* * This file is part of RawTherapee. * * Copyright (c) 2004-2010 Gabor Horvath * * RawTherapee is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * RawTherapee is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with RawTherapee. If not, see . */ #include #include #include #include "../rtengine/rt_math.h" #include #include #include #include "thumbnail.h" #include "batchqueue.h" #include "multilangmgr.h" #include "filecatalog.h" #include "batchqueuebuttonset.h" #include "guiutils.h" #include "../rtengine/safegtk.h" #include "rtimage.h" using namespace std; using namespace rtengine; BatchQueue::BatchQueue (FileCatalog* aFileCatalog) : processing(NULL), fileCatalog(aFileCatalog), sequence(0), listener(NULL) { location = THLOC_BATCHQUEUE; int p = 0; pmenu = new Gtk::Menu (); pmenu->attach (*Gtk::manage(open = new Gtk::MenuItem (M("FILEBROWSER_POPUPOPENINEDITOR"))), 0, 1, p, p + 1); p++; pmenu->attach (*Gtk::manage(selall = new Gtk::MenuItem (M("FILEBROWSER_POPUPSELECTALL"))), 0, 1, p, p + 1); p++; pmenu->attach (*Gtk::manage(new Gtk::SeparatorMenuItem ()), 0, 1, p, p + 1); p++; pmenu->attach (*Gtk::manage(head = new MyImageMenuItem (M("FILEBROWSER_POPUPMOVEHEAD"), "toleftend.png")), 0, 1, p, p + 1); p++; pmenu->attach (*Gtk::manage(tail = new MyImageMenuItem (M("FILEBROWSER_POPUPMOVEEND"), "torightend.png")), 0, 1, p, p + 1); p++; pmenu->attach (*Gtk::manage(new Gtk::SeparatorMenuItem ()), 0, 1, p, p + 1); p++; pmenu->attach (*Gtk::manage(cancel = new MyImageMenuItem (M("FILEBROWSER_POPUPCANCELJOB"), "gtk-close.png")), 0, 1, p, p + 1); p++; pmenu->show_all (); // Accelerators pmaccelgroup = Gtk::AccelGroup::create (); pmenu->set_accel_group (pmaccelgroup); open->add_accelerator ("activate", pmenu->get_accel_group(), GDK_KEY_e, Gdk::CONTROL_MASK, Gtk::ACCEL_VISIBLE); selall->add_accelerator ("activate", pmenu->get_accel_group(), GDK_KEY_a, Gdk::CONTROL_MASK, Gtk::ACCEL_VISIBLE); head->add_accelerator ("activate", pmenu->get_accel_group(), GDK_KEY_Home, (Gdk::ModifierType)0, Gtk::ACCEL_VISIBLE); tail->add_accelerator ("activate", pmenu->get_accel_group(), GDK_KEY_End, (Gdk::ModifierType)0, Gtk::ACCEL_VISIBLE); cancel->add_accelerator ("activate", pmenu->get_accel_group(), GDK_KEY_Delete, (Gdk::ModifierType)0, Gtk::ACCEL_VISIBLE); open->signal_activate().connect(sigc::mem_fun(*this, &BatchQueue::openLastSelectedItemInEditor)); cancel->signal_activate().connect (sigc::bind(sigc::mem_fun(*this, &BatchQueue::cancelItems), &selected)); head->signal_activate().connect (sigc::bind(sigc::mem_fun(*this, &BatchQueue::headItems), &selected)); tail->signal_activate().connect (sigc::bind(sigc::mem_fun(*this, &BatchQueue::tailItems), &selected)); selall->signal_activate().connect (sigc::mem_fun(*this, &BatchQueue::selectAll)); setArrangement (ThumbBrowserBase::TB_Vertical); } BatchQueue::~BatchQueue () { #if PROTECT_VECTORS MYWRITERLOCK(l, entryRW); #endif // The listener merges parameters with old values, so delete afterwards for (size_t i = 0; i < fd.size(); i++) { delete fd.at(i); } fd.clear (); delete pmenu; } void BatchQueue::resizeLoadedQueue() { // TODO: Check for Linux #if PROTECT_VECTORS MYWRITERLOCK(l, entryRW); #endif for (size_t i = 0; i < fd.size(); i++) { fd.at(i)->resize(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() { return std::min(options.maxThumbnailHeight, 200); } // Function for virtual override in thumbbrowser base 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::doubleClicked(ThumbBrowserEntryBase* entry) { openItemInEditor(entry); } bool BatchQueue::keyPressed (GdkEventKey* event) { bool ctrl = event->state & GDK_CONTROL_MASK; if ((event->keyval == GDK_KEY_A || event->keyval == GDK_KEY_a) && ctrl) { selectAll (); return true; } else if ((event->keyval == GDK_KEY_E || event->keyval == GDK_KEY_e) && ctrl) { openLastSelectedItemInEditor(); return true; } else if (event->keyval == GDK_KEY_Home) { headItems (&selected); return true; } else if (event->keyval == GDK_KEY_End) { tailItems (&selected); return true; } else if (event->keyval == GDK_KEY_Delete) { cancelItems (&selected); return true; } return false; } void BatchQueue::addEntries ( std::vector &entries, bool head, bool save) { { // 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); // 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 if( !(*entry)->params.save( tempFile ) ) { (*entry)->savedParamsFile = tempFile; } (*entry)->selected = false; if (!head) { fd.push_back (*entry); } else { std::vector::iterator pos; for (pos = fd.begin(); pos != fd.end(); pos++) if (!(*pos)->processing) { fd.insert (pos, *entry); break; } if (pos == fd.end()) { fd.push_back (*entry); } } if ((*entry)->thumbnail) { (*entry)->thumbnail->imageEnqueued (); } } } if (save) { saveBatchQueue( ); } redraw(); notifyListener (false); } bool BatchQueue::saveBatchQueue( ) { Glib::ustring savedQueueFile; savedQueueFile = options.rtdir + "/batch/queue.csv"; FILE *f = safe_g_fopen (savedQueueFile, "wt"); 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|" "png bit depth|png compression|tiff bit depth|uncompressed tiff|save output params|force format options|\n"); // method is already running with entryLock, so no need to lock again for (std::vector::iterator pos = fd.begin(); pos != fd.end(); pos++) { BatchQueueEntry* bqe = reinterpret_cast(*pos); // Warning: for code's simplicity in loadBatchQueue, each field must end by the '|' character, safer than ';' or ',' since it can't be used in paths fprintf(f, "%s|%s|%s|%s|%d|%d|%d|%d|%d|%d|%d|%d|\n", bqe->filename.c_str(), bqe->savedParamsFile.c_str(), bqe->outFileName.c_str(), bqe->saveFormat.format.c_str(), bqe->saveFormat.jpegQuality, bqe->saveFormat.jpegSubSamp, bqe->saveFormat.pngBits, bqe->saveFormat.pngCompression, bqe->saveFormat.tiffBits, bqe->saveFormat.tiffUncompressed, bqe->saveFormat.saveParams, bqe->forceFormatOpts ); } } fclose (f); return true; } 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 #if PROTECT_VECTORS MYWRITERLOCK(l, entryRW); #endif while (fgets (buffer, 1024, f)) { 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; 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 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); // No need to bother reading the last options, they will be ignored if outputFile is empty! if (!outputFile.empty()) { // 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 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 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 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 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 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 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 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 ); if( thumb ) { rtengine::ProcessingJob* job = rtengine::ProcessingJob::create(source, thumb->getType() == FT_Raw, pparams); int prevh = getMaxThumbnailHeight(); int prevw = prevh; thumb->getThumbnailSize (prevw, prevh, &pparams); BatchQueueEntry *entry = new BatchQueueEntry(job, pparams, source, prevw, prevh, thumb); thumb->decreaseRef(); // Removing the refCount acquired by cacheMgr->getEntry entry->setParent(this); // 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()); 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); } redraw(); notifyListener(false); return !fd.empty(); } Glib::ustring BatchQueue::getTempFilenameForParams( const Glib::ustring filename ) { time_t rawtime; struct tm *timeinfo; char stringTimestamp [80]; time ( &rawtime ); timeinfo = localtime ( &rawtime ); strftime (stringTimestamp, sizeof(stringTimestamp), "_%Y%m%d%H%M%S_", timeinfo); Glib::ustring savedParamPath; savedParamPath = options.rtdir + "/batch/"; safe_g_mkdir_with_parents (savedParamPath, 0755); savedParamPath += Glib::path_get_basename (filename); savedParamPath += stringTimestamp; savedParamPath += paramFileExtension; return savedParamPath; } int cancelItemUI (void* data) { safe_g_remove( (static_cast(data))->savedParamsFile ); delete static_cast(data); return 0; } void BatchQueue::cancelItems (std::vector* items) { { // TODO: Check for Linux #if PROTECT_VECTORS MYWRITERLOCK(l, entryRW); #endif for (size_t i = 0; i < items->size(); 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); rtengine::ProcessingJob::destroy (entry->job); if (entry->thumbnail) { entry->thumbnail->imageRemovedFromQueue (); } g_idle_add (cancelItemUI, entry); } } for (size_t i = 0; i < fd.size(); i++) { fd[i]->selected = false; } lastClicked = NULL; selected.clear (); } saveBatchQueue( ); redraw (); notifyListener (false); } void BatchQueue::headItems (std::vector* items) { { // 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( ); redraw (); } void BatchQueue::tailItems (std::vector* items) { { // TODO: Check for Linux #if PROTECT_VECTORS MYWRITERLOCK(l, entryRW); #endif for (size_t i = 0; i < items->size(); 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( ); redraw (); } void BatchQueue::selectAll () { { // TODO: Check for Linux #if PROTECT_VECTORS MYWRITERLOCK(l, entryRW); #endif lastClicked = NULL; selected.clear (); for (size_t i = 0; i < fd.size(); i++) { if (fd[i]->processing) { continue; } fd[i]->selected = true; selected.push_back (fd[i]); } } queue_draw (); } void BatchQueue::openLastSelectedItemInEditor() { { // TODO: Check for Linux #if PROTECT_VECTORS MYREADERLOCK(l, entryRW); #endif if (selected.size() > 0) { openItemInEditor(selected.back()); } } } void BatchQueue::openItemInEditor(ThumbBrowserEntryBase* item) { if (item) { std::vector< ::Thumbnail*> requestedItem; requestedItem.push_back(item->thumbnail); fileCatalog->openRequested(requestedItem); } } void BatchQueue::startProcessing () { if (!processing) { // TODO: Check for Linux #if PROTECT_VECTORS MYWRITERLOCK(l, entryRW); #endif if (!fd.empty()) { BatchQueueEntry* next; next = static_cast(fd[0]); // tag it as processing and set sequence next->processing = true; next->sequence = sequence = 1; processing = next; // remove from selection if (processing->selected) { std::vector::iterator pos = std::find (selected.begin(), selected.end(), processing); if (pos != selected.end()) { selected.erase (pos); } 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 (); } } } rtengine::ProcessingJob* BatchQueue::imageReady (rtengine::IImage16* img) { // save image img Glib::ustring fname; SaveFormat saveFormat; if (processing->outFileName == "") { // auto file name Glib::ustring s = calcAutoFileNameBase (processing->filename, processing->sequence); saveFormat = options.saveFormatBatch; fname = autoCompleteFileName (s, saveFormat.format); } else { // use the save-as filename with automatic completion for uniqueness if (processing->forceFormatOpts) { saveFormat = processing->saveFormat; } else { saveFormat = options.saveFormatBatch; } // The output filename's extension is forced to the current or selected output format, // despite what the user have set in the fielneame's field of the "Save as" dialgo box fname = autoCompleteFileName (removeExtension(processing->outFileName), saveFormat.format); //fname = autoCompleteFileName (removeExtension(processing->outFileName), getExtension(processing->outFileName)); } //printf ("fname=%s, %s\n", fname.c_str(), removeExtension(fname).c_str()); if (img && fname != "") { int err = 0; if (saveFormat.format == "tif") { err = img->saveAsTIFF (fname, saveFormat.tiffBits, saveFormat.tiffUncompressed); } else if (saveFormat.format == "png") { err = img->saveAsPNG (fname, saveFormat.pngCompression, saveFormat.pngBits); } else if (saveFormat.format == "jpg") { err = img->saveAsJPEG (fname, saveFormat.jpegQuality, saveFormat.jpegSubSamp); } img->free (); if (err) { throw Glib::FileError(Glib::FileError::FAILED, M("MAIN_MSG_CANNOTSAVE") + "\n" + fname); } if (saveFormat.saveParams) { // 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); } if (processing->thumbnail) { processing->thumbnail->imageDeveloped (); processing->thumbnail->imageRemovedFromQueue (); } } // save temporary params file name: delete as last thing Glib::ustring processedParams = processing->savedParamsFile; // delete from the queue bool queueEmptied = false; bool remove_button_set = false; { // TODO: Check for Linux #if PROTECT_VECTORS MYWRITERLOCK(l, entryRW); #endif delete processing; processing = NULL; fd.erase (fd.begin()); // return next job if (fd.empty()) { queueEmptied = true; } else if (listener && listener->canStartNext ()) { BatchQueueEntry* next = static_cast(fd[0]); // tag it as selected and set sequence next->processing = true; next->sequence = ++sequence; processing = next; // remove from selection if (processing->selected) { std::vector::iterator pos = std::find (selected.begin(), selected.end(), processing); if (pos != selected.end()) { selected.erase (pos); } processing->selected = false; } // remove button set remove_button_set = true; } } if (remove_button_set) { // ButtonSet have Cairo::Surface which might be rendered while we're trying to delete them GThreadLock lock; processing->removeButtonSet (); } 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 = Glib::build_filename(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); return processing ? processing->job : NULL; } // Calculates automatic filename of processed batch entry, but just the base name // example output: "c:\out\converted\dsc0121" Glib::ustring BatchQueue::calcAutoFileNameBase (const Glib::ustring& origFileName, int sequence) { std::vector pa; std::vector da; for (size_t i = 0; i < origFileName.size(); i++) { while ((i < origFileName.size()) && (origFileName[i] == '\\' || origFileName[i] == '/')) { i++; } if (i >= origFileName.size()) { break; } Glib::ustring tok = ""; while ((i < origFileName.size()) && !(origFileName[i] == '\\' || origFileName[i] == '/')) { tok = tok + origFileName[i++]; } da.push_back (tok); } if (origFileName[0] == '/' || origFileName[0] == '\\') { pa.push_back ("/" + da[0]); } else { pa.push_back (da[0]); } for (size_t i = 1; i < da.size(); i++) { pa.push_back (pa[i - 1] + "/" + da[i]); } // for (int i=0; i= 0 && origFileName[extpos] != '.'; extpos--); for (int k = extpos - 1; k >= 0 && origFileName[k] != '/' && origFileName[k] != '\\'; k--) { filename = origFileName[k] + filename; } // printf ("%d, |%s|\n", extpos, filename.c_str()); // constructing full output path // printf ("path=|%s|\n", options.savePath.c_str()); Glib::ustring path = ""; if (options.saveUsePathTemplate) { int ix = 0; while (options.savePathTemplate[ix] != 0) { if (options.savePathTemplate[ix] == '%') { ix++; if (options.savePathTemplate[ix] == 'p') { ix++; int i = options.savePathTemplate[ix] - '0'; if (i < pa.size()) { path = path + pa[pa.size() - i - 1] + '/'; } ix++; } else if (options.savePathTemplate[ix] == 'd') { ix++; int i = options.savePathTemplate[ix] - '0'; if (i < da.size()) { path = path + da[da.size() - i - 1]; } } else if (options.savePathTemplate[ix] == 'f') { path = path + filename; } else if (options.savePathTemplate[ix] == 'r') { // rank from pparams char rank; rtengine::procparams::ProcParams pparams; if( pparams.load(origFileName + paramFileExtension) == 0 ) { if (!pparams.inTrash) { rank = pparams.rank + '0'; } else { rank = 'x'; } } else { rank = '0'; // if param file not loaded (e.g. does not exist), default to rank=0 } path += rank; } else if (options.savePathTemplate[ix] == 's') { // sequence std::ostringstream seqstr; int w = options.savePathTemplate[ix + 1] - '0'; if (w >= 1 && w <= 9) { ix++; seqstr << std::setw (w) << std::setfill ('0'); } seqstr << sequence; path += seqstr.str (); } } else { path = path + options.savePathTemplate[ix]; } ix++; } } else { path = Glib::build_filename (options.savePathFolder, filename); } return path; } Glib::ustring BatchQueue::autoCompleteFileName (const Glib::ustring& fileName, const Glib::ustring& format) { // separate filename and the path to the destination directory Glib::ustring dstdir = Glib::path_get_dirname (fileName); Glib::ustring dstfname = Glib::path_get_basename (fileName); Glib::ustring fname; // create directory, if does not exist if (safe_g_mkdir_with_parents (dstdir, 0755) ) { return ""; } // In overwrite mode we TRY to delete the old file first. // if that's not possible (e.g. locked by viewer, R/O), we revert to the standard naming scheme bool inOverwriteMode = options.overwriteOutputFile; for (int tries = 0; tries < 100; tries++) { if (tries == 0) { fname = Glib::ustring::compose ("%1.%2", Glib::build_filename (dstdir, dstfname), format); } else { fname = Glib::ustring::compose ("%1-%2.%3", Glib::build_filename (dstdir, dstfname), tries, format); } int fileExists = safe_file_test (fname, Glib::FILE_TEST_EXISTS); if (inOverwriteMode && fileExists) { if (safe_g_remove(fname) == -1) { inOverwriteMode = false; // failed to delete- revert to old naming scheme } else { fileExists = false; // deleted now } } if (!fileExists) { return fname; } } return ""; } int setProgressUI (void* p) { (static_cast(p))->redraw(); return 0; } 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); } void BatchQueue::buttonPressed (LWButton* button, int actionCode, void* actionData) { std::vector bqe; bqe.push_back (static_cast(actionData)); if (actionCode == 10) { // cancel cancelItems (&bqe); } else if (actionCode == 8) { // to head headItems (&bqe); } else if (actionCode == 9) { // to tail tailItems (&bqe); } } struct NLParams { BatchQueueListener* listener; int qsize; bool queueEmptied; bool queueError; Glib::ustring queueErrorMessage; }; int bqnotifylistenerUI (void* data) { NLParams* params = static_cast(data); params->listener->queueSizeChanged (params->qsize, params->queueEmptied, params->queueError, params->queueErrorMessage); delete params; return 0; } 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; params->queueError = false; add_idle (bqnotifylistenerUI, params); } } void BatchQueue::redrawNeeded (LWButton* button) { GThreadLock lock; queue_draw (); } void BatchQueue::error (Glib::ustring msg) { if (processing && processing->processing) { // restore failed thumb BatchQueueButtonSet* bqbs = new BatchQueueButtonSet (processing); bqbs->setButtonListener (this); processing->addButtonSet (bqbs); processing->processing = false; processing->job = rtengine::ProcessingJob::create(processing->filename, processing->thumbnail->getType() == FT_Raw, processing->params); processing = NULL; redraw (); } if (listener) { NLParams* params = new NLParams; params->listener = listener; params->queueEmptied = false; params->queueError = true; params->queueErrorMessage = msg; add_idle (bqnotifylistenerUI, params); } }