diff --git a/rtdata/images/svg/folder-subfolder.svg b/rtdata/images/svg/folder-subfolder.svg
new file mode 100644
index 000000000..f22d0da0f
--- /dev/null
+++ b/rtdata/images/svg/folder-subfolder.svg
@@ -0,0 +1,98 @@
+
+
+
+
diff --git a/rtdata/languages/default b/rtdata/languages/default
index 050836d21..c23410661 100644
--- a/rtdata/languages/default
+++ b/rtdata/languages/default
@@ -200,6 +200,7 @@ FILEBROWSER_SHOWRANK4HINT;Show images ranked as 4-star.\nShortcut: 4
FILEBROWSER_SHOWRANK5HINT;Show images ranked as 5-star.\nShortcut: 5
FILEBROWSER_SHOWRECENTLYSAVEDHINT;Show saved images.\nShortcut: Alt-7
FILEBROWSER_SHOWRECENTLYSAVEDNOTHINT;Show unsaved images.\nShortcut: Alt-6
+FILEBROWSER_SHOWRECURSIVE;Show images in sub-folders recursively.
FILEBROWSER_SHOWTRASHHINT;Show contents of trash.\nShortcut: Ctrl-t
FILEBROWSER_SHOWUNCOLORHINT;Show images without a color label.\nShortcut: Alt-0
FILEBROWSER_SHOWUNRANKHINT;Show unranked images.\nShortcut: 0
diff --git a/rtgui/filecatalog.cc b/rtgui/filecatalog.cc
index d6c440570..5c20aa5fe 100644
--- a/rtgui/filecatalog.cc
+++ b/rtgui/filecatalog.cc
@@ -19,6 +19,8 @@
*/
#include "filecatalog.h"
+#include
+#include
#include
#include
@@ -347,9 +349,17 @@ FileCatalog::FileCatalog (CoarsePanel* cp, ToolBar* tb, FilePanel* filepanel) :
bCateg[19] = bOriginal->signal_toggled().connect (sigc::bind(sigc::mem_fun(*this, &FileCatalog::categoryButtonToggled), bOriginal, true));
bOriginal->signal_button_press_event().connect (sigc::mem_fun(*this, &FileCatalog::capture_event), false);
+ bRecursive = Gtk::manage(new Gtk::ToggleButton());
+ bRecursive->set_image(*Gtk::manage(new RTImage("folder-subfolder.png")));
+ bRecursive->set_tooltip_text(M("FILEBROWSER_SHOWRECURSIVE"));
+ bRecursive->set_relief(Gtk::RELIEF_NONE);
+ bRecursive->set_active(options.browseRecursive);
+ bRecursive->signal_toggled().connect(sigc::mem_fun(*this, &FileCatalog::showRecursiveToggled));
+
buttonBar->pack_start (*bTrash, Gtk::PACK_SHRINK);
buttonBar->pack_start (*bNotTrash, Gtk::PACK_SHRINK);
buttonBar->pack_start (*bOriginal, Gtk::PACK_SHRINK);
+ buttonBar->pack_start(*bRecursive, Gtk::PACK_SHRINK);
buttonBar->pack_start (*Gtk::manage(new Gtk::Separator(Gtk::ORIENTATION_VERTICAL)), Gtk::PACK_SHRINK);
fileBrowser->trash_changed().connect( sigc::mem_fun(*this, &FileCatalog::trashChanged) );
@@ -544,9 +554,7 @@ void FileCatalog::closeDir ()
exportPanel->set_sensitive (false);
}
- if (dirMonitor) {
- dirMonitor->cancel ();
- }
+ dirMonitors.clear();
// ignore old requests
++selectedDirectoryId;
@@ -570,60 +578,73 @@ void FileCatalog::closeDir ()
redrawAll ();
}
-std::vector FileCatalog::getFileList()
+std::vector FileCatalog::getFileList(std::vector> *dirs_explored)
{
std::vector names;
const std::set& extensions = options.parsedExtensionsSet;
- try {
+ static void (*getFilesRecursively)(const Glib::ustring &, int, int &, std::vector &, std::vector> *) = [](const Glib::ustring &dir_path, int max_depth, int &dir_quota, std::vector &file_names, std::vector> * directories_explored) {
+ try {
- const auto dir = Gio::File::create_for_path(selectedDirectory);
+ const auto dir = Gio::File::create_for_path(dir_path);
- auto enumerator = dir->enumerate_children("standard::name,standard::type,standard::is-hidden");
+ auto enumerator = dir->enumerate_children("standard::name,standard::type,standard::is-hidden");
- while (true) {
- try {
- const auto file = enumerator->next_file();
- if (!file) {
- break;
- }
+ if (directories_explored) {
+ directories_explored->push_back(dir);
+ }
- if (file->get_file_type() == Gio::FILE_TYPE_DIRECTORY) {
- continue;
- }
+ while (true) {
+ try {
+ const auto file = enumerator->next_file();
+ if (!file) {
+ break;
+ }
- if (!options.fbShowHidden && file->is_hidden()) {
- continue;
- }
+ if (!options.fbShowHidden && file->is_hidden()) {
+ continue;
+ }
- const Glib::ustring fname = file->get_name();
- const auto lastdot = fname.find_last_of('.');
+ if (file->get_file_type() == Gio::FILE_TYPE_DIRECTORY) {
+ if (max_depth > 0 && dir_quota > 0) {
+ const Glib::ustring child_dir_path = Glib::build_filename(dir_path, file->get_name());
+ getFilesRecursively(child_dir_path, max_depth - 1, --dir_quota, file_names, directories_explored);
+ }
+ continue;
+ }
- if (lastdot >= fname.length() - 1) {
- continue;
- }
+ const Glib::ustring fname = file->get_name();
+ const auto lastdot = fname.find_last_of('.');
- if (extensions.find(fname.substr(lastdot + 1).lowercase()) == extensions.end()) {
- continue;
- }
+ if (lastdot >= fname.length() - 1) {
+ continue;
+ }
- names.push_back(Glib::build_filename(selectedDirectory, fname));
- } catch (Glib::Exception& exception) {
- if (rtengine::settings->verbose) {
- std::cerr << exception.what() << std::endl;
+ if (extensions.find(fname.substr(lastdot + 1).lowercase()) == extensions.end()) {
+ continue;
+ }
+
+ file_names.emplace_back(Glib::build_filename(dir_path, fname));
+ } catch (Glib::Exception& exception) {
+ if (rtengine::settings->verbose) {
+ std::cerr << exception.what() << std::endl;
+ }
}
}
+
+ } catch (Glib::Exception& exception) {
+
+ if (rtengine::settings->verbose) {
+ std::cerr << "Failed to list directory \"" << dir_path << "\": " << exception.what() << std::endl;
+ }
+
}
+ };
- } catch (Glib::Exception& exception) {
-
- if (rtengine::settings->verbose) {
- std::cerr << "Failed to list directory \"" << selectedDirectory << "\": " << exception.what() << std::endl;
- }
-
- }
+ int dirs_left = options.browseRecursive ? options.browseRecursiveMaxDirs : 0;
+ getFilesRecursively(selectedDirectory, options.browseRecursiveDepth, dirs_left, names, dirs_explored);
return names;
}
@@ -649,9 +670,10 @@ void FileCatalog::dirSelected (const Glib::ustring& dirname, const Glib::ustring
selectedDirectory = dir->get_parse_name();
+ std::vector> allDirs;
BrowsePath->set_text(selectedDirectory);
buttonBrowsePath->set_image(*iRefreshWhite);
- fileNameList = getFileList();
+ fileNameList = getFileList(&allDirs);
for (unsigned int i = 0; i < fileNameList.size(); i++) {
if (openfile.empty() || fileNameList[i] != openfile) { // if we opened a file at the beginning don't add it again
@@ -667,13 +689,45 @@ void FileCatalog::dirSelected (const Glib::ustring& dirname, const Glib::ustring
filepanel->loadingThumbs(M("PROGRESSBAR_LOADINGTHUMBS"), 0);
}
- dirMonitor = dir->monitor_directory ();
- dirMonitor->signal_changed().connect (sigc::bind(sigc::mem_fun(*this, &FileCatalog::on_dir_changed), false));
+ refreshDirectoryMonitors(allDirs);
} catch (Glib::Exception& ex) {
std::cout << ex.what();
}
}
+void FileCatalog::refreshDirectoryMonitors(const std::vector> &dirs_to_monitor)
+{
+ std::vector updated_dir_names;
+ std::transform(
+ dirs_to_monitor.cbegin(), dirs_to_monitor.cend(),
+ std::back_inserter(updated_dir_names),
+ [](const Glib::RefPtr &updated_dir) { return updated_dir->get_path(); });
+
+ // Remove monitors on directories that are no longer shown.
+ dirMonitors.erase(
+ std::remove_if(dirMonitors.begin(), dirMonitors.end(),
+ [&updated_dir_names](const FileMonitorInfo &fileMonitorInfo) {
+ return std::find(updated_dir_names.cbegin(), updated_dir_names.cend(), fileMonitorInfo.filePath) == updated_dir_names.cend();
+ }),
+ dirMonitors.end());
+
+ // Add monitors that do not exist yet.
+ std::vector monitored_dir_names;
+ std::transform(
+ dirMonitors.cbegin(), dirMonitors.cend(),
+ std::back_inserter(monitored_dir_names),
+ [](const FileMonitorInfo &dir_monitor) { return dir_monitor.filePath; });
+ for (const auto &dir_to_monitor : dirs_to_monitor) {
+ const auto dir_path = dir_to_monitor->get_path();
+ if (std::find(monitored_dir_names.cbegin(), monitored_dir_names.cend(), dir_path) != monitored_dir_names.cend()) {
+ continue; // A monitor exists already.
+ }
+ auto dir_monitor = dir_to_monitor->monitor_directory();
+ dir_monitor->signal_changed().connect(sigc::bind(sigc::mem_fun(*this, &FileCatalog::on_dir_changed), false));
+ dirMonitors.emplace_back(dir_monitor, dir_path);
+ }
+}
+
void FileCatalog::enableTabMode(bool enable)
{
inTabMode = enable;
@@ -1581,6 +1635,12 @@ void FileCatalog::categoryButtonToggled (Gtk::ToggleButton* b, bool isMouseClick
}
}
+void FileCatalog::showRecursiveToggled()
+{
+ options.browseRecursive = bRecursive->get_active();
+ reparseDirectory();
+}
+
BrowserFilter FileCatalog::getFilter ()
{
@@ -1717,18 +1777,25 @@ void FileCatalog::reparseDirectory ()
// check if a thumbnailed file has been deleted
const std::vector& t = fileBrowser->getEntries();
std::vector fileNamesToDel;
+ std::vector fileNamesToRemove;
for (const auto& entry : t) {
if (!Glib::file_test(entry->filename, Glib::FILE_TEST_EXISTS)) {
fileNamesToDel.push_back(entry->filename);
+ fileNamesToRemove.push_back(entry->filename);
+ }
+ else if (!options.browseRecursive && Glib::path_get_dirname(entry->filename) != selectedDirectory) {
+ fileNamesToRemove.push_back(entry->filename);
}
}
- for (const auto& toDelete : fileNamesToDel) {
- delete fileBrowser->delEntry(toDelete);
- cacheMgr->deleteEntry(toDelete);
+ for (const auto& toRemove : fileNamesToRemove) {
+ delete fileBrowser->delEntry(toRemove);
--previewsLoaded;
}
+ for (const auto& toDelete : fileNamesToDel) {
+ cacheMgr->deleteEntry(toDelete);
+ }
if (!fileNamesToDel.empty()) {
_refreshProgressBar();
@@ -1741,7 +1808,8 @@ void FileCatalog::reparseDirectory ()
oldNames.insert(oldName.collate_key());
}
- fileNameList = getFileList();
+ std::vector> allDirs;
+ fileNameList = getFileList(&allDirs);
for (const auto& newName : fileNameList) {
if (oldNames.find(newName.collate_key()) == oldNames.end()) {
addFile(newName);
@@ -1749,6 +1817,7 @@ void FileCatalog::reparseDirectory ()
}
}
+ refreshDirectoryMonitors(allDirs);
}
void FileCatalog::on_dir_changed (const Glib::RefPtr& file, const Glib::RefPtr& other_file, Gio::FileMonitorEvent event_type, bool internal)
diff --git a/rtgui/filecatalog.h b/rtgui/filecatalog.h
index 23d56af73..f41c42475 100644
--- a/rtgui/filecatalog.h
+++ b/rtgui/filecatalog.h
@@ -54,6 +54,13 @@ public:
typedef sigc::slot DirSelectionSlot;
private:
+ struct FileMonitorInfo {
+ FileMonitorInfo(const Glib::RefPtr &file_monitor, const Glib::ustring &file_path) :
+ fileMonitor(file_monitor), filePath(file_path) {}
+ Glib::RefPtr fileMonitor;
+ Glib::ustring filePath;
+ };
+
FilePanel* filepanel;
Gtk::Box* hBox;
Glib::ustring selectedDirectory;
@@ -95,6 +102,7 @@ private:
Gtk::ToggleButton* bTrash;
Gtk::ToggleButton* bNotTrash;
Gtk::ToggleButton* bOriginal;
+ Gtk::ToggleButton* bRecursive;
Gtk::ToggleButton* categoryButtons[20];
Gtk::ToggleButton* exifInfo;
sigc::connection bCateg[20];
@@ -143,14 +151,15 @@ private:
std::set editedFiles;
guint modifierKey; // any modifiers held when rank button was pressed
- Glib::RefPtr dirMonitor;
+ std::vector dirMonitors;
IdleRegister idle_register;
void addAndOpenFile (const Glib::ustring& fname);
void addFile (const Glib::ustring& fName);
- std::vector getFileList ();
+ std::vector getFileList(std::vector> *dirs_explored = nullptr);
BrowserFilter getFilter ();
+ void refreshDirectoryMonitors(const std::vector> &dirs_to_monitor);
void trashChanged ();
public:
@@ -240,6 +249,7 @@ public:
void setExportPanel (ExportPanel* expanel);
void exifInfoButtonToggled();
void categoryButtonToggled (Gtk::ToggleButton* b, bool isMouseClick);
+ void showRecursiveToggled();
bool capture_event(GdkEventButton* event);
void filterChanged ();
void runFilterDialog ();
diff --git a/rtgui/options.cc b/rtgui/options.cc
index e89fcd647..ed6f03eb7 100644
--- a/rtgui/options.cc
+++ b/rtgui/options.cc
@@ -435,6 +435,9 @@ void Options::setDefaults()
parseExtensionsEnabled.clear();
parsedExtensions.clear();
parsedExtensionsSet.clear();
+ browseRecursive = false;
+ browseRecursiveDepth = 10;
+ browseRecursiveMaxDirs = 100;
renameUseTemplates = false;
renameTemplates.clear();
thumbnailZoomRatios.clear();
@@ -1342,6 +1345,18 @@ void Options::readFromFile(Glib::ustring fname)
if (keyFile.has_key("File Browser", "SortDescending")) {
sortDescending = keyFile.get_boolean("File Browser", "SortDescending");
}
+
+ if (keyFile.has_key("File Browser", "BrowseRecursive")) {
+ browseRecursive = keyFile.get_boolean("File Browser", "BrowseRecursive");
+ }
+
+ if (keyFile.has_key("File Browser", "BrowseRecursiveDepth")) {
+ browseRecursiveDepth = keyFile.get_integer("File Browser", "BrowseRecursiveDepth");
+ }
+
+ if (keyFile.has_key("File Browser", "BrowseRecursiveMaxDirs")) {
+ browseRecursiveMaxDirs = keyFile.get_integer("File Browser", "BrowseRecursiveMaxDirs");
+ }
}
if (keyFile.has_group("Clipping Indication")) {
@@ -2410,6 +2425,9 @@ void Options::saveToFile(Glib::ustring fname)
}
keyFile.set_integer("File Browser", "SortMethod", sortMethod);
keyFile.set_boolean("File Browser", "SortDescending", sortDescending);
+ keyFile.set_boolean("File Browser", "BrowseRecursive", browseRecursive);
+ keyFile.set_integer("File Browser", "BrowseRecursiveDepth", browseRecursiveDepth);
+ keyFile.set_integer("File Browser", "BrowseRecursiveMaxDirs", browseRecursiveMaxDirs);
keyFile.set_integer("Clipping Indication", "HighlightThreshold", highlightThreshold);
keyFile.set_integer("Clipping Indication", "ShadowThreshold", shadowThreshold);
keyFile.set_boolean("Clipping Indication", "BlinkClipped", blinkClipped);
diff --git a/rtgui/options.h b/rtgui/options.h
index 78059357e..6380cdb56 100644
--- a/rtgui/options.h
+++ b/rtgui/options.h
@@ -315,6 +315,9 @@ public:
std::vector parseExtensionsEnabled; // List of bool to retain extension or not
std::vector parsedExtensions; // List containing all retained extensions (lowercase)
std::set parsedExtensionsSet; // Set containing all retained extensions (lowercase)
+ bool browseRecursive;
+ int browseRecursiveDepth;
+ int browseRecursiveMaxDirs;
std::vector tpOpen;
bool autoSaveTpOpen;
//std::vector crvOpen;