diff --git a/AUTHORS.txt b/AUTHORS.txt
index 374a7935b..3afd09dc2 100644
--- a/AUTHORS.txt
+++ b/AUTHORS.txt
@@ -20,6 +20,7 @@ Development contributors, in last name alphabetical order:
Rüdiger Franke
Jean-Christophe Frisch
Ilias Giarimis
+ Scott Gilbertson
Alberto Griggio
Steve Herrell
Philippe Hupé
diff --git a/rtdata/languages/default b/rtdata/languages/default
index 403fccdf0..bf43596dc 100644
--- a/rtdata/languages/default
+++ b/rtdata/languages/default
@@ -2101,10 +2101,12 @@ QINFO_PIXELSHIFT;Pixel Shift / %2 frame(s)
QUEUE_AUTOSTART;Auto-start
QUEUE_AUTOSTART_TOOLTIP;Start processing automatically when a new job arrives.
QUEUE_DESTFILENAME;Path and file name
+QUEUE_DESTPREVIEW_TITLE;Select a thumbnail to preview its destination path here
+QUEUE_DESTPREVIEW_TOOLTIP;Destination path for the first selected image appears here
QUEUE_FORMAT_TITLE;File Format
QUEUE_LOCATION_FOLDER;Save to folder
QUEUE_LOCATION_TEMPLATE;Use template
-QUEUE_LOCATION_TEMPLATE_TOOLTIP;Specify the output location based on the source photo's location, rank, trash status or position in the queue.\n\nUsing the following pathname as an example:\n/home/tom/photos/2010-10-31/photo1.raw\nthe meaning of the formatting strings follows:\n%d4 = home\n%d3 = tom\n%d2 = photos\n%d1 = 2010-10-31\n%f = photo1\n%p1 = /home/tom/photos/2010-10-31/\n%p2 = /home/tom/photos/\n%p3 = /home/tom/\n%p4 = /home/\n\n%r will be replaced by the photo's rank. If the photo is unranked, '0' is used. If the photo is in the trash, 'x' is used.\n\n%s1, ..., %s9 will be replaced by the photo's initial position in the queue at the time the queue is started. The number specifies the padding, e.g. %s3 results in '001'.\n\nIf you want to save the output image alongside the source image, write:\n%p1/%f\n\nIf you want to save the output image in a folder named 'converted' located in the source photo's folder, write:\n%p1/converted/%f\n\nIf you want to save the output image in\n'/home/tom/photos/converted/2010-10-31', write:\n%p2/converted/%d1/%f
+QUEUE_LOCATION_TEMPLATE_TOOLTIP;Specify the output location based on the source photo's location, rank, trash status or position in the queue.\n\n%dN, %d-N, %pN, %p-N, %PN and %P-N (N = 1..9) will be replaced by elements of the image file's directory path (not including the file name):\n%dN = Nth directory from the end of the path\n%d-N = Nth directory from the start of the path\n%pN = all directories up to the Nth from the end of the path\n%p-N = the first N directories in the path\n%PN = the last N directories in the path\n%P-N = all directories from the Nth to the end of the path\n%f = base filename (no extension)\nFor Windows paths, %d-1 is the drive letter and colon, and %d-2 is the base directory on that drive.\n\nUsing the following pathname as an example:\n/home/tom/photos/2010-10-31/photo1.raw\nthe meaning of the formatting strings follows:\n%d4 = %d-1 = home\n%d3 = %d-2 = tom\n%d2 = %d-3 = photos\n%d1 = %d-4 = 2010-10-31\n%p1 = %p-4 = /home/tom/photos/2010-10-31/\n%p2 = %p-3 = /home/tom/photos/\n%p3 = %p-2 = /home/tom/\n%p4 = %p-1 = /home/\n%P1 = %P-4 = 2010-10-31/\n%P2 = %P-3 = photos/2010-10-31/\n%P3 = %P-2 = tom/photos/2010-10-31/\n%P4 = %P-1 = /home/tom/photos/2010-10-31/\n%f = photo1\n\n%r will be replaced by the photo's rank. If the photo is unranked, '0' is used. If the photo is in the trash, 'x' is used.\n\n%s1, ..., %s9 will be replaced by the photo's initial position in the queue at the time the queue is started. The number specifies the padding, e.g. %s3 results in '001'.\n\nIf you want to save the output image alongside the source image, write:\n%p1/%f\n\nIf you want to save the output image in a folder named 'converted' located in the source photo's folder, write:\n%p1/converted/%f\n\nIf you want to save the output image in\n'/home/tom/photos/converted/2010-10-31', write:\n%p-3/converted/%P-4/%f
QUEUE_LOCATION_TITLE;Output Location
QUEUE_STARTSTOP_TOOLTIP;Start or stop processing the images in the queue.\n\nShortcut: Ctrl+s
SAMPLEFORMAT_0;Unknown data format
diff --git a/rtgui/batchqueue.cc b/rtgui/batchqueue.cc
index 6ab75bd47..2811e3445 100644
--- a/rtgui/batchqueue.cc
+++ b/rtgui/batchqueue.cc
@@ -43,6 +43,59 @@
using namespace std;
using namespace rtengine;
+#ifdef _WIN32
+#define PATH_SEPARATOR '\\';
+#else
+#define PATH_SEPARATOR '/';
+#endif
+
+namespace // local helper functions
+{
+ // Look for N or -N in templateText at position ix, meaning "index from end" and "index from start".
+ // For N, return Nth index from the end, and for -N return the Nth index from the start.
+ // N is a digit 1 through 9. The returned value is not range-checked, so it may be >=numPathElements.
+ // or negative. The caller performs any required range-checking.
+ int decodePathIndex(unsigned int& ix, Glib::ustring& templateText, size_t numPathElements)
+ {
+ int pathIndex = static_cast(numPathElements); // a value that means input was invalid
+ bool fromStart = false;
+ if (ix < templateText.size()) {
+ if (templateText[ix] == '-') {
+ fromStart = true; // minus sign means N is from the start rather than the end of the path
+ ix++;
+ }
+ }
+ if (ix < templateText.size()) {
+ pathIndex = templateText[ix] - '1';
+ if (!fromStart) {
+ pathIndex = numPathElements - pathIndex - 1;
+ }
+ }
+ return pathIndex;
+ }
+
+ // Extract the initial characters from a canonical absolute path, and append
+ // those to a path string. Initial characters are '/' for Unix/Linux paths and
+ // '\\' or '//' for UNC paths. A single backslash is also accepted, for driveless
+ // Windows paths.
+ void appendAbsolutePathPrefix(Glib::ustring& path, const Glib::ustring& absolutePath)
+ {
+ if (absolutePath[0] == '/') {
+ if (absolutePath.size() > 1 && absolutePath[1] == '/') {
+ path += "//"; // Start of a Samba UNC path
+ } else {
+ path += '/'; // Start of a Unix/Linux path
+ }
+ } else if (absolutePath[0] == '\\') {
+ if (absolutePath.size() > 1 && absolutePath[1] == '\\') {
+ path += "\\\\"; // Start of a UNC path
+ } else {
+ path += '\\'; // Start of a Windows path that does not include a drive letter
+ }
+ }
+ }
+}
+
BatchQueue::BatchQueue (FileCatalog* aFileCatalog) : processing(nullptr), fileCatalog(aFileCatalog), sequence(0), listener(nullptr)
{
@@ -394,7 +447,7 @@ Glib::ustring BatchQueue::getTempFilenameForParams( const Glib::ustring &filenam
timeval tv;
gettimeofday(&tv, nullptr);
char mseconds[11];
- snprintf(mseconds, sizeof(mseconds), "%d", (int)(tv.tv_usec / 1000));
+ snprintf(mseconds, sizeof(mseconds), "%d", static_cast((tv.tv_usec / 1000)));
time_t rawtime;
struct tm *timeinfo;
char stringTimestamp [80];
@@ -561,12 +614,28 @@ void BatchQueue::openLastSelectedItemInEditor()
{
MYREADERLOCK(l, entryRW);
- if (selected.size() > 0) {
+ if (!selected.empty()) {
openItemInEditor(selected.back());
}
}
}
+void BatchQueue::updateDestinationPathPreview()
+{
+ MYWRITERLOCK(l, entryRW);
+
+ if (!selected.empty()) {
+ auto& entry = *selected.at(0);
+ int sequence = 0; // Sequence during subsequent queue processing can't be determined here
+ Glib::ustring baseDestination = calcAutoFileNameBase(entry.filename, sequence);
+ Glib::ustring destination = Glib::ustring::compose ("%1.%2", baseDestination, options.saveFormatBatch.format);
+
+ if (listener) {
+ listener->setDestinationPreviewText(destination);
+ }
+ }
+}
+
void BatchQueue::openItemInEditor(ThumbBrowserEntryBase* item)
{
if (item) {
@@ -811,7 +880,6 @@ rtengine::ProcessingJob* BatchQueue::imageReady(rtengine::IImagefloat* img)
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++) {
@@ -829,29 +897,13 @@ Glib::ustring BatchQueue::calcAutoFileNameBase (const Glib::ustring& origFileNam
tok = tok + origFileName[i++];
}
- da.push_back (tok);
- }
-
- if (origFileName[0] == '/') {
- pa.push_back ("/" + da[0]);
- } else if (origFileName[0] == '\\') {
- if (origFileName.size() > 1 && origFileName[1] == '\\') {
- pa.push_back ("\\\\" + da[0]);
- } else {
- pa.push_back ("/" + da[0]);
+ if (i < origFileName.size()) { // omit the last token, which is the file name
+ da.push_back (tok);
}
- } 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())) {
+ if (n == 0) {
+ appendAbsolutePathPrefix(path, origFileName);
+ }
+ for (unsigned int i = static_cast(n); i < da.size(); i++) {
+ path += da[i] + PATH_SEPARATOR;
+ }
+ }
+ // If the next template character is a separator, skip it, because path already has one
ix++;
+ if (ix < options.savePathTemplate.size() && options.savePathTemplate[ix] != '/' && options.savePathTemplate[ix] != '\\') {
+ ix--;
+ }
+ } else if (options.savePathTemplate[ix] == 'p') {
+ // insert path elements from the start of the path up to the given index
+ ix++;
+ int n = decodePathIndex(ix, options.savePathTemplate, da.size());
+ if (n >= 0) {
+ appendAbsolutePathPrefix(path, origFileName);
+ }
+ for (unsigned int i=0; static_cast(i) <= n && i < da.size(); i++) {
+ path += da[i] + PATH_SEPARATOR;
+ }
+ // If the next template character is a separator, skip it, because path already has one
+ ix++;
+ if (ix < options.savePathTemplate.size() && options.savePathTemplate[ix] != '/' && options.savePathTemplate[ix] != '\\') {
+ ix--;
+ }
} else if (options.savePathTemplate[ix] == 'd') {
+ // insert a single directory name from the file's path
ix++;
- unsigned i = options.savePathTemplate[ix] - '0';
-
- if (i < da.size()) {
- path = path + da[da.size() - i - 1];
+ int n = decodePathIndex(ix, options.savePathTemplate, da.size());
+ if (n >= 0 && n < static_cast(da.size())) {
+ path += da[n];
}
} else if (options.savePathTemplate[ix] == 'f') {
- path = path + filename;
+ path += filename;
} else if (options.savePathTemplate[ix] == 'r') { // rank from pparams
char rank;
rtengine::procparams::ProcParams pparams;
@@ -927,7 +1005,7 @@ Glib::ustring BatchQueue::calcAutoFileNameBase (const Glib::ustring& origFileNam
}
else {
- path = path + options.savePathTemplate[ix];
+ path += options.savePathTemplate[ix];
}
ix++;
@@ -1021,3 +1099,8 @@ void BatchQueue::redrawNeeded (LWButton* button)
GThreadLock lock;
queue_draw ();
}
+
+void BatchQueue::selectionChanged()
+{
+ updateDestinationPathPreview();
+}
diff --git a/rtgui/batchqueue.h b/rtgui/batchqueue.h
index 5cde37748..f8f1cf255 100644
--- a/rtgui/batchqueue.h
+++ b/rtgui/batchqueue.h
@@ -38,6 +38,7 @@ public:
virtual ~BatchQueueListener() = default;
virtual void queueSizeChanged(int qsize, bool queueRunning, bool queueError, const Glib::ustring& queueErrorMessage) = 0;
virtual bool canStartNext() = 0;
+ virtual void setDestinationPreviewText(const Glib::ustring& destinationPath) = 0;
};
class FileCatalog;
@@ -59,6 +60,7 @@ public:
void selectAll ();
void openItemInEditor(ThumbBrowserEntryBase* item);
void openLastSelectedItemInEditor();
+ void updateDestinationPathPreview();
void startProcessing ();
@@ -79,6 +81,7 @@ public:
bool keyPressed (GdkEventKey* event) override;
void buttonPressed (LWButton* button, int actionCode, void* actionData) override;
void redrawNeeded (LWButton* button) override;
+ void selectionChanged () override;
void setBatchQueueListener (BatchQueueListener* l)
{
diff --git a/rtgui/batchqueuepanel.cc b/rtgui/batchqueuepanel.cc
index 8a6dd25b4..e35149326 100644
--- a/rtgui/batchqueuepanel.cc
+++ b/rtgui/batchqueuepanel.cc
@@ -108,6 +108,14 @@ BatchQueuePanel::BatchQueuePanel (FileCatalog* aFileCatalog) : parent(nullptr)
#endif
odvb->pack_start (*hb3, Gtk::PACK_SHRINK, 4);
+ destinationPreviewLabel = Gtk::manage (new Gtk::Label ());
+ destinationPreviewLabel->set_tooltip_markup (M("QUEUE_DESTPREVIEW_TOOLTIP"));
+ destinationPreviewLabel->set_selectable (true); // so users can copy the path to the clipboard
+ destinationPreviewLabel->set_halign (Gtk::ALIGN_START);
+ auto destinationPreviewScrolledWindow = Gtk::manage(new Gtk::ScrolledWindow ());
+ destinationPreviewScrolledWindow->set_policy (Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
+ destinationPreviewScrolledWindow->add (*destinationPreviewLabel);
+ odvb->pack_start (*destinationPreviewScrolledWindow, Gtk::PACK_SHRINK);
Gtk::RadioButton::Group g = useTemplate->get_group();
useFolder->set_group (g);
fdir->add (*odvb);
@@ -122,6 +130,7 @@ BatchQueuePanel::BatchQueuePanel (FileCatalog* aFileCatalog) : parent(nullptr)
outdirTemplate->set_text (options.savePathTemplate);
useTemplate->set_active (options.saveUsePathTemplate);
useFolder->set_active (!options.saveUsePathTemplate);
+ destinationPreviewLabel->set_text (M("QUEUE_DESTPREVIEW_TITLE"));
// setup signal handlers
outdirTemplate->signal_changed().connect (sigc::mem_fun(*this, &BatchQueuePanel::saveOptions));
@@ -329,6 +338,7 @@ void BatchQueuePanel::saveOptions ()
options.savePathTemplate = outdirTemplate->get_text();
options.saveUsePathTemplate = useTemplate->get_active();
options.procQueueEnabled = qAutoStart->get_active();
+ batchQueue->updateDestinationPathPreview();
}
bool BatchQueuePanel::handleShortcutKey (GdkEventKey* event)
@@ -358,6 +368,11 @@ bool BatchQueuePanel::canStartNext ()
return queueShouldRun;
}
+void BatchQueuePanel::setDestinationPreviewText(const Glib::ustring &destinationPath)
+{
+ destinationPreviewLabel->set_text(destinationPath);
+}
+
void BatchQueuePanel::pathFolderButtonPressed ()
{
@@ -381,9 +396,11 @@ void BatchQueuePanel::pathFolderButtonPressed ()
void BatchQueuePanel::pathFolderChanged ()
{
options.savePathFolder = outdirFolder->get_filename();
+ batchQueue->updateDestinationPathPreview();
}
void BatchQueuePanel::formatChanged(const Glib::ustring& format)
{
options.saveFormatBatch = saveFormatPanel->getFormat();
+ batchQueue->updateDestinationPathPreview();
}
diff --git a/rtgui/batchqueuepanel.h b/rtgui/batchqueuepanel.h
index db4e243e9..d73f8b893 100644
--- a/rtgui/batchqueuepanel.h
+++ b/rtgui/batchqueuepanel.h
@@ -42,6 +42,7 @@ class BatchQueuePanel : public Gtk::Box,
Gtk::CheckButton* qAutoStart;
Gtk::Entry* outdirTemplate;
+ Gtk::Label* destinationPreviewLabel;
MyFileChooserButton* outdirFolder;
Gtk::Button* outdirFolderButton;
Gtk::RadioButton* useTemplate;
@@ -72,6 +73,7 @@ public:
// batchqueuelistener interface
void queueSizeChanged(int qsize, bool queueRunning, bool queueError, const Glib::ustring& queueErrorMessage) override;
bool canStartNext() override;
+ void setDestinationPreviewText(const Glib::ustring& destinationPath) override;
private:
void startBatchProc ();