Pipette and "On Preview Widgets" branch. See issue 227
The pipette part is already working quite nice but need to be finished. The widgets part needs more work...
This commit is contained in:
918
rtgui/batchqueue.cc
Normal file
918
rtgui/batchqueue.cc
Normal file
@@ -0,0 +1,918 @@
|
||||
/*
|
||||
* This file is part of RawTherapee.
|
||||
*
|
||||
* Copyright (c) 2004-2010 Gabor Horvath <hgabor@rawtherapee.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <glibmm.h>
|
||||
#include <glib/gstdio.h>
|
||||
#include <cstring>
|
||||
#include "../rtengine/rt_math.h"
|
||||
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
#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 () : processing(NULL), sequence(0), listener(NULL) {
|
||||
|
||||
int p = 0;
|
||||
pmenu = new Gtk::Menu ();
|
||||
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 Gtk::ImageMenuItem (M("FILEBROWSER_POPUPMOVEHEAD"))), 0, 1, p, p+1); p++;
|
||||
head->set_image(*Gtk::manage(new RTImage ("toleftend.png")));
|
||||
|
||||
pmenu->attach (*Gtk::manage(tail = new Gtk::ImageMenuItem (M("FILEBROWSER_POPUPMOVEEND"))), 0, 1, p, p+1); p++;
|
||||
tail->set_image(*Gtk::manage(new RTImage ("torightend.png")));
|
||||
|
||||
pmenu->attach (*Gtk::manage(new Gtk::SeparatorMenuItem ()), 0, 1, p, p+1); p++;
|
||||
|
||||
pmenu->attach (*Gtk::manage(cancel = new Gtk::ImageMenuItem (M("FILEBROWSER_POPUPCANCELJOB"))), 0, 1, p, p+1); p++;
|
||||
cancel->set_image(*Gtk::manage(new RTImage ("gtk-close.png")));
|
||||
|
||||
pmenu->show_all ();
|
||||
|
||||
// Accelerators
|
||||
pmaccelgroup = Gtk::AccelGroup::create ();
|
||||
pmenu->set_accel_group (pmaccelgroup);
|
||||
selall->add_accelerator ("activate", pmenu->get_accel_group(), GDK_a, Gdk::CONTROL_MASK, Gtk::ACCEL_VISIBLE);
|
||||
head->add_accelerator ("activate", pmenu->get_accel_group(), GDK_Home, (Gdk::ModifierType)0, Gtk::ACCEL_VISIBLE);
|
||||
tail->add_accelerator ("activate", pmenu->get_accel_group(), GDK_End, (Gdk::ModifierType)0, Gtk::ACCEL_VISIBLE);
|
||||
cancel->add_accelerator ("activate", pmenu->get_accel_group(), GDK_Delete, (Gdk::ModifierType)0, Gtk::ACCEL_VISIBLE);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
bool BatchQueue::keyPressed (GdkEventKey* event) {
|
||||
bool ctrl = event->state & GDK_CONTROL_MASK;
|
||||
|
||||
if ((event->keyval==GDK_A || event->keyval==GDK_a) && ctrl) {
|
||||
selectAll ();
|
||||
return true;
|
||||
}
|
||||
else if (event->keyval==GDK_Home) {
|
||||
headItems (&selected);
|
||||
return true;
|
||||
}
|
||||
else if (event->keyval==GDK_End) {
|
||||
tailItems (&selected);
|
||||
return true;
|
||||
}
|
||||
else if (event->keyval==GDK_Delete) {
|
||||
cancelItems (&selected);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void BatchQueue::addEntries ( std::vector<BatchQueueEntry*> &entries, bool head, bool save)
|
||||
{
|
||||
{
|
||||
// TODO: Check for Linux
|
||||
#if PROTECT_VECTORS
|
||||
MYWRITERLOCK(l, entryRW);
|
||||
#endif
|
||||
|
||||
for( std::vector<BatchQueueEntry*>::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<ThumbBrowserEntryBase*>::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|<end of line>\n");
|
||||
|
||||
// method is already running with entryLock, so no need to lock again
|
||||
for (std::vector<ThumbBrowserEntryBase*>::iterator pos=fd.begin(); pos!=fd.end(); pos++){
|
||||
BatchQueueEntry* bqe = reinterpret_cast<BatchQueueEntry*>(*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<BatchQueueEntry*>(data))->savedParamsFile );
|
||||
delete static_cast<BatchQueueEntry*>(data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void BatchQueue::cancelItems (std::vector<ThumbBrowserEntryBase*>* 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<ThumbBrowserEntryBase*>::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<ThumbBrowserEntryBase*>* 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<ThumbBrowserEntryBase*>::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<ThumbBrowserEntryBase*>* 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<ThumbBrowserEntryBase*>::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::startProcessing () {
|
||||
|
||||
if (!processing) {
|
||||
// TODO: Check for Linux
|
||||
#if PROTECT_VECTORS
|
||||
MYWRITERLOCK(l, entryRW);
|
||||
#endif
|
||||
|
||||
if (!fd.empty()) {
|
||||
BatchQueueEntry* next;
|
||||
|
||||
next = static_cast<BatchQueueEntry*>(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<ThumbBrowserEntryBase*>::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"));
|
||||
|
||||
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
|
||||
delete processing; processing = NULL;
|
||||
bool queueEmptied=false;
|
||||
{
|
||||
// TODO: Check for Linux
|
||||
#if PROTECT_VECTORS
|
||||
MYWRITERLOCK(l, entryRW);
|
||||
#endif
|
||||
|
||||
fd.erase (fd.begin());
|
||||
|
||||
// return next job
|
||||
if (fd.empty()) {
|
||||
queueEmptied=true;
|
||||
}
|
||||
else if (listener && listener->canStartNext ()) {
|
||||
BatchQueueEntry* next = static_cast<BatchQueueEntry*>(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<ThumbBrowserEntryBase*>::iterator pos = std::find (selected.begin(), selected.end(), processing);
|
||||
if (pos!=selected.end())
|
||||
selected.erase (pos);
|
||||
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
|
||||
|
||||
// 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<Glib::ustring> names;
|
||||
Glib::ustring batchdir = Glib::build_filename(options.rtdir, "batch");
|
||||
Glib::RefPtr<Gio::File> dir = Gio::File::create_for_path (batchdir);
|
||||
safe_build_file_list (dir, names, batchdir);
|
||||
for(std::vector<Glib::ustring>::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<Glib::ustring> pa;
|
||||
std::vector<Glib::ustring> 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<da.size(); i++)
|
||||
// printf ("da: %s\n", da[i].c_str());
|
||||
// for (int i=0; i<pa.size(); i++)
|
||||
// printf ("pa: %s\n", pa[i].c_str());
|
||||
|
||||
// extracting filebase
|
||||
Glib::ustring filename;
|
||||
|
||||
int extpos = origFileName.size()-1;
|
||||
for (; extpos>=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<BatchQueue*>(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<ThumbBrowserEntryBase*> bqe;
|
||||
bqe.push_back (static_cast<BatchQueueEntry*>(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) {
|
||||
GThreadLock lock; // All GUI acces from idle_add callbacks or separate thread HAVE to be protected
|
||||
NLParams* params = static_cast<NLParams*>(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;
|
||||
g_idle_add (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;
|
||||
g_idle_add (bqnotifylistenerUI, params);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user