"Fossies" - the Fresh Open Source Software Archive

Member "rawtherapee-5.7/rtgui/filebrowser.cc" (10 Sep 2019, 77017 Bytes) of package /linux/misc/rawtherapee-5.7.tar.xz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "filebrowser.cc" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 5.6_vs_5.7.

    1 /*
    2  *  This file is part of RawTherapee.
    3  *
    4  *  Copyright (c) 2004-2010 Gabor Horvath <hgabor@rawtherapee.com>
    5  *  Copyright (c) 2011 Oliver Duis <www.oliverduis.de>
    6  *  Copyright (c) 2011 Michael Ezra <www.michaelezra.com>
    7  *
    8  *  RawTherapee is free software: you can redistribute it and/or modify
    9  *  it under the terms of the GNU General Public License as published by
   10  *  the Free Software Foundation, either version 3 of the License, or
   11  *  (at your option) any later version.
   12  *
   13  *  RawTherapee is distributed in the hope that it will be useful,
   14  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
   15  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   16  *  GNU General Public License for more details.
   17  *
   18  *  You should have received a copy of the GNU General Public License
   19  *  along with RawTherapee.  If not, see <https://www.gnu.org/licenses/>.
   20  */
   21 #include <algorithm>
   22 #include <map>
   23 
   24 #include <glibmm.h>
   25 
   26 #include "filebrowser.h"
   27 
   28 #include "batchqueue.h"
   29 #include "clipboard.h"
   30 #include "multilangmgr.h"
   31 #include "options.h"
   32 #include "procparamchangers.h"
   33 #include "rtimage.h"
   34 #include "threadutils.h"
   35 
   36 #include "../rtengine/dfmanager.h"
   37 #include "../rtengine/ffmanager.h"
   38 #include "../rtengine/procparams.h"
   39 
   40 extern Options options;
   41 
   42 namespace
   43 {
   44 
   45 const Glib::ustring* getOriginalExtension (const ThumbBrowserEntryBase* entry)
   46 {
   47     // We use the parsed extensions as a priority list,
   48     // i.e. what comes earlier in the list is considered an original of what comes later.
   49     typedef std::vector<Glib::ustring> ExtensionVector;
   50     typedef ExtensionVector::const_iterator ExtensionIterator;
   51 
   52     const ExtensionVector& originalExtensions = options.parsedExtensions;
   53 
   54     // Extract extension from basename
   55     const Glib::ustring basename = Glib::path_get_basename (entry->filename.lowercase());
   56 
   57     const Glib::ustring::size_type pos = basename.find_last_of ('.');
   58     if (pos >= basename.length () - 1) {
   59         return nullptr;
   60     }
   61 
   62     const Glib::ustring extension = basename.substr (pos + 1);
   63 
   64     // Try to find a matching original extension
   65     for (ExtensionIterator originalExtension = originalExtensions.begin(); originalExtension != originalExtensions.end(); ++originalExtension) {
   66         if (*originalExtension == extension) {
   67             return &*originalExtension;
   68         }
   69     }
   70 
   71     return nullptr;
   72 }
   73 
   74 ThumbBrowserEntryBase* selectOriginalEntry (ThumbBrowserEntryBase* original, ThumbBrowserEntryBase* candidate)
   75 {
   76     if (original == nullptr) {
   77         return candidate;
   78     }
   79 
   80     // The candidate will become the new original, if it has an original extension
   81     // and if its extension is higher in the list than the old original.
   82     if (const Glib::ustring* candidateExtension = getOriginalExtension (candidate)) {
   83         if (const Glib::ustring* originalExtension = getOriginalExtension (original)) {
   84             return candidateExtension < originalExtension ? candidate : original;
   85         }
   86     }
   87 
   88     return original;
   89 }
   90 
   91 void findOriginalEntries (const std::vector<ThumbBrowserEntryBase*>& entries)
   92 {
   93     typedef std::vector<ThumbBrowserEntryBase*> EntryVector;
   94     typedef EntryVector::const_iterator EntryIterator;
   95     typedef std::map<Glib::ustring, EntryVector> BasenameMap;
   96     typedef BasenameMap::const_iterator BasenameIterator;
   97 
   98     // Sort all entries into buckets by basename without extension
   99     BasenameMap byBasename;
  100 
  101     for (EntryIterator entry = entries.begin (); entry != entries.end (); ++entry) {
  102         const Glib::ustring basename = Glib::path_get_basename ((*entry)->filename.lowercase());
  103 
  104         const Glib::ustring::size_type pos = basename.find_last_of ('.');
  105         if (pos >= basename.length () - 1) {
  106             (*entry)->setOriginal (nullptr);
  107             continue;
  108         }
  109 
  110         const Glib::ustring withoutExtension = basename.substr (0, pos);
  111 
  112         byBasename[withoutExtension].push_back (*entry);
  113     }
  114 
  115     // Find the original image for each bucket
  116     for (BasenameIterator bucket = byBasename.begin (); bucket != byBasename.end (); ++bucket) {
  117         const EntryVector& entries = bucket->second;
  118         ThumbBrowserEntryBase* original = nullptr;
  119 
  120         // Select the most likely original in a first pass...
  121         for (EntryIterator entry = entries.begin (); entry != entries.end (); ++entry) {
  122             original = selectOriginalEntry (original, *entry);
  123         }
  124 
  125         // ...and link all other images to it in a second pass.
  126         for (EntryIterator entry = entries.begin (); entry != entries.end (); ++entry) {
  127             (*entry)->setOriginal (*entry != original ? original : nullptr);
  128         }
  129     }
  130 }
  131 
  132 }
  133 
  134 FileBrowser::FileBrowser () :
  135     menuLabel(nullptr),
  136     miOpenDefaultViewer(nullptr),
  137     selectDF(nullptr),
  138     thisIsDF(nullptr),
  139     autoDF(nullptr),
  140     selectFF(nullptr),
  141     thisIsFF(nullptr),
  142     autoFF(nullptr),
  143     clearFromCache(nullptr),
  144     clearFromCacheFull(nullptr),
  145     colorLabel_actionData(nullptr),
  146     bppcl(nullptr),
  147     tbl(nullptr),
  148     numFiltered(0),
  149     exportPanel(nullptr)
  150 {
  151     session_id_ = 0;
  152 
  153     ProfileStore::getInstance()->addListener(this);
  154 
  155     int p = 0;
  156     pmenu = new Gtk::Menu ();
  157     pmenu->attach (*Gtk::manage(open = new Gtk::MenuItem (M("FILEBROWSER_POPUPOPEN"))), 0, 1, p, p + 1);
  158     p++;
  159     pmenu->attach (*Gtk::manage(develop = new MyImageMenuItem (M("FILEBROWSER_POPUPPROCESS"), "gears.png")), 0, 1, p, p + 1);
  160     p++;
  161     pmenu->attach (*Gtk::manage(developfast = new Gtk::MenuItem (M("FILEBROWSER_POPUPPROCESSFAST"))), 0, 1, p, p + 1);
  162     p++;
  163 
  164     pmenu->attach (*Gtk::manage(new Gtk::SeparatorMenuItem ()), 0, 1, p, p + 1);
  165     p++;
  166     pmenu->attach (*Gtk::manage(selall = new Gtk::MenuItem (M("FILEBROWSER_POPUPSELECTALL"))), 0, 1, p, p + 1);
  167     p++;
  168 
  169     /***********************
  170      * rank
  171      ***********************/
  172     if (options.menuGroupRank) {
  173         pmenu->attach (*Gtk::manage(menuRank = new Gtk::MenuItem (M("FILEBROWSER_POPUPRANK"))), 0, 1, p, p + 1);
  174         p++;
  175         Gtk::Menu* submenuRank = Gtk::manage (new Gtk::Menu ());
  176         submenuRank->attach (*Gtk::manage(rank[0] = new Gtk::MenuItem (M("FILEBROWSER_POPUPUNRANK"))), 0, 1, p, p + 1);
  177         p++;
  178 
  179         for (int i = 1; i <= 5; i++) {
  180             submenuRank->attach (*Gtk::manage(rank[i] = new Gtk::MenuItem (M(Glib::ustring::compose("%1%2", "FILEBROWSER_POPUPRANK", i)))), 0, 1, p, p + 1);
  181             p++;
  182         }
  183 
  184         submenuRank->show_all ();
  185         menuRank->set_submenu (*submenuRank);
  186     } else {
  187         pmenu->attach (*Gtk::manage(rank[0] = new Gtk::MenuItem (M("FILEBROWSER_POPUPUNRANK"))), 0, 1, p, p + 1);
  188         p++;
  189 
  190         for (int i = 1; i <= 5; i++) {
  191             pmenu->attach (*Gtk::manage(rank[i] = new Gtk::MenuItem (M(Glib::ustring::compose("%1%2", "FILEBROWSER_POPUPRANK", i)))), 0, 1, p, p + 1);
  192             p++;
  193         }
  194 
  195         pmenu->attach (*Gtk::manage(new Gtk::SeparatorMenuItem ()), 0, 1, p, p + 1);
  196         p++;
  197     }
  198 
  199     if (!options.menuGroupRank || !options.menuGroupLabel) { // separate Rank and Color Labels if either is not grouped
  200         pmenu->attach (*Gtk::manage(new Gtk::SeparatorMenuItem ()), 0, 1, p, p + 1);
  201     }
  202 
  203     p++;
  204 
  205     /***********************
  206      * color labels
  207      ***********************/
  208 
  209     // Thumbnail context menu
  210     // Similar image arrays in filecatalog.cc
  211     std::array<std::string, 6> clabelActiveIcons = {"circle-empty-gray-small.png", "circle-red-small.png", "circle-yellow-small.png", "circle-green-small.png", "circle-blue-small.png", "circle-purple-small.png"};
  212     std::array<std::string, 6> clabelInactiveIcons = {"circle-empty-darkgray-small.png", "circle-empty-red-small.png", "circle-empty-yellow-small.png", "circle-empty-green-small.png", "circle-empty-blue-small.png", "circle-empty-purple-small.png"};
  213 
  214     if (options.menuGroupLabel) {
  215         pmenu->attach (*Gtk::manage(menuLabel = new Gtk::MenuItem (M("FILEBROWSER_POPUPCOLORLABEL"))), 0, 1, p, p + 1);
  216         p++;
  217         Gtk::Menu* submenuLabel = Gtk::manage (new Gtk::Menu ());
  218 
  219         for (int i = 0; i <= 5; i++) {
  220             submenuLabel->attach(*Gtk::manage(colorlabel[i] = new MyImageMenuItem(M(Glib::ustring::compose("%1%2", "FILEBROWSER_POPUPCOLORLABEL", i)), clabelActiveIcons[i])), 0, 1, p, p + 1);
  221             p++;
  222         }
  223 
  224         submenuLabel->show_all ();
  225         menuLabel->set_submenu (*submenuLabel);
  226     } else {
  227         for (int i = 0; i <= 5; i++) {
  228             pmenu->attach(*Gtk::manage(colorlabel[i] = new MyImageMenuItem(M(Glib::ustring::compose("%1%2", "FILEBROWSER_POPUPCOLORLABEL", i)), clabelInactiveIcons[i])), 0, 1, p, p + 1);
  229             p++;
  230         }
  231     }
  232 
  233     pmenu->attach (*Gtk::manage(new Gtk::SeparatorMenuItem ()), 0, 1, p, p + 1);
  234     p++;
  235 
  236     /***********************
  237      * external programs
  238      * *********************/
  239 #if defined(WIN32)
  240     Gtk::manage(miOpenDefaultViewer = new Gtk::MenuItem (M("FILEBROWSER_OPENDEFAULTVIEWER")));
  241 #endif
  242 
  243     // Build a list of menu items
  244     mMenuExtProgs.clear();
  245     amiExtProg = nullptr;
  246 
  247     for (const auto& action : extProgStore->getActions ()) {
  248         if (action.target == 1 || action.target == 2) {
  249             mMenuExtProgs[action.getFullName ()] = &action;
  250         }
  251     }
  252 
  253     // Attach them to menu
  254     if (!mMenuExtProgs.empty() || miOpenDefaultViewer) {
  255         amiExtProg = new Gtk::MenuItem*[mMenuExtProgs.size()];
  256         int itemNo = 0;
  257 
  258         if (options.menuGroupExtProg) {
  259             pmenu->attach (*Gtk::manage(menuExtProg = new Gtk::MenuItem (M("FILEBROWSER_EXTPROGMENU"))), 0, 1, p, p + 1);
  260             p++;
  261             Gtk::Menu* submenuExtProg = Gtk::manage (new Gtk::Menu());
  262 
  263 #ifdef WIN32
  264             if (miOpenDefaultViewer) {
  265                 submenuExtProg->attach (*miOpenDefaultViewer, 0, 1, p, p + 1);
  266                 p++;
  267             }
  268 #endif
  269             for (auto it = mMenuExtProgs.begin(); it != mMenuExtProgs.end(); it++, itemNo++) {
  270                 submenuExtProg->attach (*Gtk::manage(amiExtProg[itemNo] = new Gtk::MenuItem ((*it).first)), 0, 1, p, p + 1);
  271                 p++;
  272             }
  273 
  274             submenuExtProg->show_all ();
  275             menuExtProg->set_submenu (*submenuExtProg);
  276         } else {
  277 #ifdef WIN32
  278             if (miOpenDefaultViewer) {
  279                 pmenu->attach (*miOpenDefaultViewer, 0, 1, p, p + 1);
  280                 p++;
  281             }
  282 #endif
  283             for (auto it = mMenuExtProgs.begin(); it != mMenuExtProgs.end(); it++, itemNo++) {
  284                 pmenu->attach (*Gtk::manage(amiExtProg[itemNo] = new Gtk::MenuItem ((*it).first)), 0, 1, p, p + 1);
  285                 p++;
  286             }
  287         }
  288 
  289         pmenu->attach (*Gtk::manage(new Gtk::SeparatorMenuItem ()), 0, 1, p, p + 1);
  290         p++;
  291     }
  292 
  293     /***********************
  294      * File Operations
  295      * *********************/
  296     if (options.menuGroupFileOperations) {
  297         pmenu->attach (*Gtk::manage(menuFileOperations = new Gtk::MenuItem (M("FILEBROWSER_POPUPFILEOPERATIONS"))), 0, 1, p, p + 1);
  298         p++;
  299         Gtk::Menu* submenuFileOperations = Gtk::manage (new Gtk::Menu ());
  300 
  301         submenuFileOperations->attach (*Gtk::manage(trash = new Gtk::MenuItem (M("FILEBROWSER_POPUPTRASH"))), 0, 1, p, p + 1);
  302         p++;
  303         submenuFileOperations->attach (*Gtk::manage(untrash = new Gtk::MenuItem (M("FILEBROWSER_POPUPUNTRASH"))), 0, 1, p, p + 1);
  304         p++;
  305         submenuFileOperations->attach (*Gtk::manage(new Gtk::SeparatorMenuItem ()), 0, 1, p, p + 1);
  306         p++;
  307         submenuFileOperations->attach (*Gtk::manage(rename = new Gtk::MenuItem (M("FILEBROWSER_POPUPRENAME"))), 0, 1, p, p + 1);
  308         p++;
  309         submenuFileOperations->attach (*Gtk::manage(remove = new Gtk::MenuItem (M("FILEBROWSER_POPUPREMOVE"))), 0, 1, p, p + 1);
  310         p++;
  311         submenuFileOperations->attach (*Gtk::manage(removeInclProc = new Gtk::MenuItem (M("FILEBROWSER_POPUPREMOVEINCLPROC"))), 0, 1, p, p + 1);
  312         p++;
  313         submenuFileOperations->attach (*Gtk::manage(new Gtk::SeparatorMenuItem ()), 0, 1, p, p + 1);
  314         p++;
  315         submenuFileOperations->attach (*Gtk::manage(copyTo = new Gtk::MenuItem (M("FILEBROWSER_POPUPCOPYTO"))), 0, 1, p, p + 1);
  316         p++;
  317         submenuFileOperations->attach (*Gtk::manage(moveTo = new Gtk::MenuItem (M("FILEBROWSER_POPUPMOVETO"))), 0, 1, p, p + 1);
  318         p++;
  319 
  320         submenuFileOperations->show_all ();
  321         menuFileOperations->set_submenu (*submenuFileOperations);
  322     } else {
  323         pmenu->attach (*Gtk::manage(trash = new Gtk::MenuItem (M("FILEBROWSER_POPUPTRASH"))), 0, 1, p, p + 1);
  324         p++;
  325         pmenu->attach (*Gtk::manage(untrash = new Gtk::MenuItem (M("FILEBROWSER_POPUPUNTRASH"))), 0, 1, p, p + 1);
  326         p++;
  327         pmenu->attach (*Gtk::manage(new Gtk::SeparatorMenuItem ()), 0, 1, p, p + 1);
  328         p++;
  329         pmenu->attach (*Gtk::manage(rename = new Gtk::MenuItem (M("FILEBROWSER_POPUPRENAME"))), 0, 1, p, p + 1);
  330         p++;
  331         pmenu->attach (*Gtk::manage(remove = new Gtk::MenuItem (M("FILEBROWSER_POPUPREMOVE"))), 0, 1, p, p + 1);
  332         p++;
  333         pmenu->attach (*Gtk::manage(removeInclProc = new Gtk::MenuItem (M("FILEBROWSER_POPUPREMOVEINCLPROC"))), 0, 1, p, p + 1);
  334         p++;
  335         pmenu->attach (*Gtk::manage(new Gtk::SeparatorMenuItem ()), 0, 1, p, p + 1);
  336         p++;
  337         pmenu->attach (*Gtk::manage(copyTo = new Gtk::MenuItem (M("FILEBROWSER_POPUPCOPYTO"))), 0, 1, p, p + 1);
  338         p++;
  339         pmenu->attach (*Gtk::manage(moveTo = new Gtk::MenuItem (M("FILEBROWSER_POPUPMOVETO"))), 0, 1, p, p + 1);
  340         p++;
  341     }
  342 
  343     pmenu->attach (*Gtk::manage(new Gtk::SeparatorMenuItem ()), 0, 1, p, p + 1);
  344     p++;
  345 
  346     /***********************
  347      * Profile Operations
  348      * *********************/
  349     if (options.menuGroupProfileOperations) {
  350         pmenu->attach (*Gtk::manage(menuProfileOperations = new Gtk::MenuItem (M("FILEBROWSER_POPUPPROFILEOPERATIONS"))), 0, 1, p, p + 1);
  351         p++;
  352 
  353         Gtk::Menu* submenuProfileOperations = Gtk::manage (new Gtk::Menu ());
  354 
  355         submenuProfileOperations->attach (*Gtk::manage(copyprof = new Gtk::MenuItem (M("FILEBROWSER_COPYPROFILE"))), 0, 1, p, p + 1);
  356         p++;
  357         submenuProfileOperations->attach (*Gtk::manage(pasteprof = new Gtk::MenuItem (M("FILEBROWSER_PASTEPROFILE"))), 0, 1, p, p + 1);
  358         p++;
  359         submenuProfileOperations->attach (*Gtk::manage(partpasteprof = new Gtk::MenuItem (M("FILEBROWSER_PARTIALPASTEPROFILE"))), 0, 1, p, p + 1);
  360         p++;
  361         submenuProfileOperations->attach (*Gtk::manage(applyprof = new Gtk::MenuItem (M("FILEBROWSER_APPLYPROFILE"))), 0, 1, p, p + 1);
  362         p++;
  363         submenuProfileOperations->attach (*Gtk::manage(applypartprof = new Gtk::MenuItem (M("FILEBROWSER_APPLYPROFILE_PARTIAL"))), 0, 1, p, p + 1);
  364         p++;
  365         submenuProfileOperations->attach (*Gtk::manage(resetdefaultprof = new Gtk::MenuItem (M("FILEBROWSER_RESETDEFAULTPROFILE"))), 0, 1, p, p + 1);
  366         p++;
  367         submenuProfileOperations->attach (*Gtk::manage(clearprof = new Gtk::MenuItem (M("FILEBROWSER_CLEARPROFILE"))), 0, 1, p, p + 1);
  368         p++;
  369 
  370         submenuProfileOperations->show_all ();
  371         menuProfileOperations->set_submenu (*submenuProfileOperations);
  372     } else {
  373         pmenu->attach (*Gtk::manage(copyprof = new Gtk::MenuItem (M("FILEBROWSER_COPYPROFILE"))), 0, 1, p, p + 1);
  374         p++;
  375         pmenu->attach (*Gtk::manage(pasteprof = new Gtk::MenuItem (M("FILEBROWSER_PASTEPROFILE"))), 0, 1, p, p + 1);
  376         p++;
  377         pmenu->attach (*Gtk::manage(partpasteprof = new Gtk::MenuItem (M("FILEBROWSER_PARTIALPASTEPROFILE"))), 0, 1, p, p + 1);
  378         p++;
  379         pmenu->attach (*Gtk::manage(applyprof = new Gtk::MenuItem (M("FILEBROWSER_APPLYPROFILE"))), 0, 1, p, p + 1);
  380         p++;
  381         pmenu->attach (*Gtk::manage(applypartprof = new Gtk::MenuItem (M("FILEBROWSER_APPLYPROFILE_PARTIAL"))), 0, 1, p, p + 1);
  382         p++;
  383         pmenu->attach (*Gtk::manage(resetdefaultprof = new Gtk::MenuItem (M("FILEBROWSER_RESETDEFAULTPROFILE"))), 0, 1, p, p + 1);
  384         p++;
  385         pmenu->attach (*Gtk::manage(clearprof = new Gtk::MenuItem (M("FILEBROWSER_CLEARPROFILE"))), 0, 1, p, p + 1);
  386         p++;
  387     }
  388 
  389 
  390     pmenu->attach (*Gtk::manage(new Gtk::SeparatorMenuItem ()), 0, 1, p, p + 1);
  391     p++;
  392     pmenu->attach (*Gtk::manage(menuDF = new Gtk::MenuItem (M("FILEBROWSER_DARKFRAME"))), 0, 1, p, p + 1);
  393     p++;
  394     pmenu->attach (*Gtk::manage(menuFF = new Gtk::MenuItem (M("FILEBROWSER_FLATFIELD"))), 0, 1, p, p + 1);
  395     p++;
  396 
  397     pmenu->attach (*Gtk::manage(new Gtk::SeparatorMenuItem ()), 0, 1, p, p + 1);
  398     p++;
  399     pmenu->attach (*Gtk::manage(cachemenu = new Gtk::MenuItem (M("FILEBROWSER_CACHE"))), 0, 1, p, p + 1);
  400 
  401     pmenu->show_all ();
  402 
  403     /***********************
  404      * Accelerators
  405      * *********************/
  406     pmaccelgroup = Gtk::AccelGroup::create ();
  407     pmenu->set_accel_group (pmaccelgroup);
  408     selall->add_accelerator ("activate", pmenu->get_accel_group(), GDK_KEY_a, Gdk::CONTROL_MASK, Gtk::ACCEL_VISIBLE);
  409     trash->add_accelerator ("activate", pmenu->get_accel_group(), GDK_KEY_Delete, (Gdk::ModifierType)0, Gtk::ACCEL_VISIBLE);
  410     untrash->add_accelerator ("activate", pmenu->get_accel_group(), GDK_KEY_Delete, Gdk::SHIFT_MASK, Gtk::ACCEL_VISIBLE);
  411     open->add_accelerator ("activate", pmenu->get_accel_group(), GDK_KEY_Return, (Gdk::ModifierType)0, Gtk::ACCEL_VISIBLE);
  412     develop->add_accelerator ("activate", pmenu->get_accel_group(), GDK_KEY_B, Gdk::CONTROL_MASK, Gtk::ACCEL_VISIBLE);
  413     developfast->add_accelerator ("activate", pmenu->get_accel_group(), GDK_KEY_B, Gdk::CONTROL_MASK | Gdk::SHIFT_MASK, Gtk::ACCEL_VISIBLE);
  414     copyprof->add_accelerator ("activate", pmenu->get_accel_group(), GDK_KEY_C, Gdk::CONTROL_MASK, Gtk::ACCEL_VISIBLE);
  415     pasteprof->add_accelerator ("activate", pmenu->get_accel_group(), GDK_KEY_V, Gdk::CONTROL_MASK, Gtk::ACCEL_VISIBLE);
  416     partpasteprof->add_accelerator ("activate", pmenu->get_accel_group(), GDK_KEY_V, Gdk::CONTROL_MASK | Gdk::SHIFT_MASK, Gtk::ACCEL_VISIBLE);
  417     copyTo->add_accelerator ("activate", pmenu->get_accel_group(), GDK_KEY_C, Gdk::CONTROL_MASK | Gdk::SHIFT_MASK, Gtk::ACCEL_VISIBLE);
  418     moveTo->add_accelerator ("activate", pmenu->get_accel_group(), GDK_KEY_M, Gdk::CONTROL_MASK | Gdk::SHIFT_MASK, Gtk::ACCEL_VISIBLE);
  419 
  420     // Bind to event handlers
  421     open->signal_activate().connect (sigc::bind(sigc::mem_fun(*this, &FileBrowser::menuItemActivated), open));
  422 
  423     for (int i = 0; i < 6; i++) {
  424         rank[i]->signal_activate().connect (sigc::bind(sigc::mem_fun(*this, &FileBrowser::menuItemActivated), rank[i]));
  425     }
  426 
  427     for (int i = 0; i < 6; i++) {
  428         colorlabel[i]->signal_activate().connect (sigc::bind(sigc::mem_fun(*this, &FileBrowser::menuItemActivated), colorlabel[i]));
  429     }
  430 
  431     for (size_t i = 0; i < mMenuExtProgs.size(); i++) {
  432         amiExtProg[i]->signal_activate().connect (sigc::bind(sigc::mem_fun(*this, &FileBrowser::menuItemActivated), amiExtProg[i]));
  433     }
  434 
  435 #ifdef WIN32
  436     if (miOpenDefaultViewer) {
  437         miOpenDefaultViewer->signal_activate().connect (sigc::bind(sigc::mem_fun(*this, &FileBrowser::menuItemActivated), miOpenDefaultViewer));
  438     }
  439 #endif
  440 
  441     trash->signal_activate().connect (sigc::bind(sigc::mem_fun(*this, &FileBrowser::menuItemActivated), trash));
  442     untrash->signal_activate().connect (sigc::bind(sigc::mem_fun(*this, &FileBrowser::menuItemActivated), untrash));
  443     develop->signal_activate().connect (sigc::bind(sigc::mem_fun(*this, &FileBrowser::menuItemActivated), develop));
  444     developfast->signal_activate().connect (sigc::bind(sigc::mem_fun(*this, &FileBrowser::menuItemActivated), developfast));
  445     rename->signal_activate().connect (sigc::bind(sigc::mem_fun(*this, &FileBrowser::menuItemActivated), rename));
  446     remove->signal_activate().connect (sigc::bind(sigc::mem_fun(*this, &FileBrowser::menuItemActivated), remove));
  447     removeInclProc->signal_activate().connect (sigc::bind(sigc::mem_fun(*this, &FileBrowser::menuItemActivated), removeInclProc));
  448     selall->signal_activate().connect (sigc::bind(sigc::mem_fun(*this, &FileBrowser::menuItemActivated), selall));
  449     copyTo->signal_activate().connect (sigc::bind(sigc::mem_fun(*this, &FileBrowser::menuItemActivated), copyTo));
  450     moveTo->signal_activate().connect (sigc::bind(sigc::mem_fun(*this, &FileBrowser::menuItemActivated), moveTo));
  451     copyprof->signal_activate().connect (sigc::bind(sigc::mem_fun(*this, &FileBrowser::menuItemActivated), copyprof));
  452     pasteprof->signal_activate().connect (sigc::bind(sigc::mem_fun(*this, &FileBrowser::menuItemActivated), pasteprof));
  453     partpasteprof->signal_activate().connect (sigc::bind(sigc::mem_fun(*this, &FileBrowser::menuItemActivated), partpasteprof));
  454     applyprof->signal_activate().connect (sigc::bind(sigc::mem_fun(*this, &FileBrowser::menuItemActivated), applyprof));
  455     applypartprof->signal_activate().connect (sigc::bind(sigc::mem_fun(*this, &FileBrowser::menuItemActivated), applypartprof));
  456     resetdefaultprof->signal_activate().connect (sigc::bind(sigc::mem_fun(*this, &FileBrowser::menuItemActivated), resetdefaultprof));
  457     clearprof->signal_activate().connect (sigc::bind(sigc::mem_fun(*this, &FileBrowser::menuItemActivated), clearprof));
  458     cachemenu->signal_activate().connect (sigc::bind(sigc::mem_fun(*this, &FileBrowser::menuItemActivated), cachemenu));
  459 
  460     // A separate pop-up menu for Color Labels
  461     int c = 0;
  462     pmenuColorLabels = new Gtk::Menu();
  463 
  464     for (int i = 0; i <= 5; i++) {
  465         pmenuColorLabels->attach(*Gtk::manage(colorlabel_pop[i] = new MyImageMenuItem(M(Glib::ustring::compose("%1%2", "FILEBROWSER_POPUPCOLORLABEL", i)), clabelActiveIcons[i])), 0, 1, c, c + 1);
  466         c++;
  467     }
  468 
  469     pmenuColorLabels->show_all();
  470 
  471     // Has to be located after creation of applyprof and applypartprof
  472     updateProfileList ();
  473 
  474     // Bind to event handlers
  475     for (int i = 0; i <= 5; i++) {
  476         colorlabel_pop[i]->signal_activate().connect (sigc::bind(sigc::mem_fun(*this, &FileBrowser::menuColorlabelActivated), colorlabel_pop[i]));
  477     }
  478 }
  479 
  480 FileBrowser::~FileBrowser ()
  481 {
  482     idle_register.destroy();
  483 
  484     ProfileStore::getInstance()->removeListener(this);
  485     delete pmenu;
  486     delete pmenuColorLabels;
  487     delete[] amiExtProg;
  488 }
  489 
  490 void FileBrowser::rightClicked (ThumbBrowserEntryBase* entry)
  491 {
  492 
  493     {
  494         MYREADERLOCK(l, entryRW);
  495 
  496         trash->set_sensitive (false);
  497         untrash->set_sensitive (false);
  498 
  499         for (size_t i = 0; i < selected.size(); i++)
  500             if ((static_cast<FileBrowserEntry*>(selected[i]))->thumbnail->getStage()) {
  501                 untrash->set_sensitive (true);
  502                 break;
  503             }
  504 
  505         for (size_t i = 0; i < selected.size(); i++)
  506             if (!(static_cast<FileBrowserEntry*>(selected[i]))->thumbnail->getStage()) {
  507                 trash->set_sensitive (true);
  508                 break;
  509             }
  510 
  511         pasteprof->set_sensitive (clipboard.hasProcParams());
  512         partpasteprof->set_sensitive (clipboard.hasProcParams());
  513         copyprof->set_sensitive (selected.size() == 1);
  514         clearprof->set_sensitive (!selected.empty());
  515         copyTo->set_sensitive (!selected.empty());
  516         moveTo->set_sensitive (!selected.empty());
  517     }
  518 
  519     // submenuDF
  520     int p = 0;
  521     Gtk::Menu* submenuDF = Gtk::manage (new Gtk::Menu ());
  522     submenuDF->attach (*Gtk::manage(selectDF = new Gtk::MenuItem (M("FILEBROWSER_SELECTDARKFRAME"))), 0, 1, p, p + 1);
  523     p++;
  524     submenuDF->attach (*Gtk::manage(autoDF = new Gtk::MenuItem (M("FILEBROWSER_AUTODARKFRAME"))), 0, 1, p, p + 1);
  525     p++;
  526     submenuDF->attach (*Gtk::manage(thisIsDF = new Gtk::MenuItem (M("FILEBROWSER_MOVETODARKFDIR"))), 0, 1, p, p + 1);
  527     selectDF->signal_activate().connect (sigc::bind(sigc::mem_fun(*this, &FileBrowser::menuItemActivated), selectDF));
  528     autoDF->signal_activate().connect (sigc::bind(sigc::mem_fun(*this, &FileBrowser::menuItemActivated), autoDF));
  529     thisIsDF->signal_activate().connect (sigc::bind(sigc::mem_fun(*this, &FileBrowser::menuItemActivated), thisIsDF ));
  530     submenuDF->show_all ();
  531     menuDF->set_submenu (*submenuDF);
  532 
  533     // submenuFF
  534     p = 0;
  535     Gtk::Menu* submenuFF = Gtk::manage (new Gtk::Menu ());
  536     submenuFF->attach (*Gtk::manage(selectFF = new Gtk::MenuItem (M("FILEBROWSER_SELECTFLATFIELD"))), 0, 1, p, p + 1);
  537     p++;
  538     submenuFF->attach (*Gtk::manage(autoFF = new Gtk::MenuItem (M("FILEBROWSER_AUTOFLATFIELD"))), 0, 1, p, p + 1);
  539     p++;
  540     submenuFF->attach (*Gtk::manage(thisIsFF = new Gtk::MenuItem (M("FILEBROWSER_MOVETOFLATFIELDDIR"))), 0, 1, p, p + 1);
  541     selectFF->signal_activate().connect (sigc::bind(sigc::mem_fun(*this, &FileBrowser::menuItemActivated), selectFF));
  542     autoFF->signal_activate().connect (sigc::bind(sigc::mem_fun(*this, &FileBrowser::menuItemActivated), autoFF));
  543     thisIsFF->signal_activate().connect (sigc::bind(sigc::mem_fun(*this, &FileBrowser::menuItemActivated), thisIsFF ));
  544     submenuFF->show_all ();
  545     menuFF->set_submenu (*submenuFF);
  546 
  547     // build cache sub menu
  548     p = 0;
  549     Gtk::Menu* cachesubmenu = Gtk::manage (new Gtk::Menu ());
  550     cachesubmenu->attach (*Gtk::manage(clearFromCache = new Gtk::MenuItem (M("FILEBROWSER_CACHECLEARFROMPARTIAL"))), 0, 1, p, p + 1);
  551     p++;
  552     cachesubmenu->attach (*Gtk::manage(clearFromCacheFull = new Gtk::MenuItem (M("FILEBROWSER_CACHECLEARFROMFULL"))), 0, 1, p, p + 1);
  553     p++;
  554     clearFromCache->signal_activate().connect (sigc::bind(sigc::mem_fun(*this, &FileBrowser::menuItemActivated), clearFromCache));
  555     clearFromCacheFull->signal_activate().connect (sigc::bind(sigc::mem_fun(*this, &FileBrowser::menuItemActivated), clearFromCacheFull));
  556     cachesubmenu->show_all ();
  557     cachemenu->set_submenu (*cachesubmenu);
  558 
  559     pmenu->popup (3, this->eventTime);
  560 }
  561 
  562 void FileBrowser::doubleClicked (ThumbBrowserEntryBase* entry)
  563 {
  564 
  565     if (tbl && entry) {
  566         std::vector<Thumbnail*> entries;
  567         entries.push_back ((static_cast<FileBrowserEntry*>(entry))->thumbnail);
  568         tbl->openRequested (entries);
  569     }
  570 }
  571 
  572 void FileBrowser::addEntry (FileBrowserEntry* entry)
  573 {
  574     entry->setParent(this);
  575 
  576     const unsigned int sid = session_id();
  577 
  578     idle_register.add(
  579         [this, entry, sid]() -> bool
  580         {
  581             if (sid != session_id()) {
  582                 delete entry;
  583             } else {
  584                 addEntry_(entry);
  585             }
  586 
  587             return false;
  588         }
  589     );
  590 }
  591 
  592 void FileBrowser::addEntry_ (FileBrowserEntry* entry)
  593 {
  594     entry->selected = false;
  595     entry->drawable = false;
  596     entry->framed = editedFiles.find(entry->filename) != editedFiles.end();
  597 
  598     // add button set to the thumbbrowserentry
  599     entry->addButtonSet(new FileThumbnailButtonSet(entry));
  600     entry->getThumbButtonSet()->setRank(entry->thumbnail->getRank());
  601     entry->getThumbButtonSet()->setColorLabel(entry->thumbnail->getColorLabel());
  602     entry->getThumbButtonSet()->setInTrash(entry->thumbnail->getStage());
  603     entry->getThumbButtonSet()->setButtonListener(this);
  604     entry->resize(getThumbnailHeight());
  605     entry->filtered = !checkFilter(entry);
  606 
  607     // find place in abc order
  608     {
  609         MYWRITERLOCK(l, entryRW);
  610 
  611         fd.insert(
  612             std::lower_bound(
  613                 fd.begin(),
  614                 fd.end(),
  615                 entry,
  616                 [](const ThumbBrowserEntryBase* a, const ThumbBrowserEntryBase* b)
  617                 {
  618                     return *a < *b;
  619                 }
  620             ),
  621             entry
  622         );
  623 
  624         initEntry(entry);
  625     }
  626     redraw(entry);
  627 }
  628 
  629 FileBrowserEntry* FileBrowser::delEntry (const Glib::ustring& fname)
  630 {
  631     MYWRITERLOCK(l, entryRW);
  632 
  633     for (std::vector<ThumbBrowserEntryBase*>::iterator i = fd.begin(); i != fd.end(); ++i)
  634         if ((*i)->filename == fname) {
  635             ThumbBrowserEntryBase* entry = *i;
  636             entry->selected = false;
  637             fd.erase (i);
  638             std::vector<ThumbBrowserEntryBase*>::iterator j = std::find (selected.begin(), selected.end(), entry);
  639 
  640             MYWRITERLOCK_RELEASE(l);
  641 
  642             if (j != selected.end()) {
  643                 if (checkFilter (*j)) {
  644                     numFiltered--;
  645                 }
  646 
  647                 selected.erase (j);
  648                 notifySelectionListener ();
  649             }
  650 
  651             if (lastClicked == entry) {
  652                 lastClicked = nullptr;
  653             }
  654 
  655             redraw ();
  656 
  657             return (static_cast<FileBrowserEntry*>(entry));
  658         }
  659 
  660     return nullptr;
  661 }
  662 
  663 void FileBrowser::close ()
  664 {
  665     ++session_id_;
  666 
  667     {
  668         MYWRITERLOCK(l, entryRW);
  669 
  670         selected.clear ();
  671         anchor = nullptr;
  672 
  673         MYWRITERLOCK_RELEASE(l); // notifySelectionListener will need read access!
  674 
  675         notifySelectionListener ();
  676 
  677         MYWRITERLOCK_ACQUIRE(l);
  678 
  679         // The listener merges parameters with old values, so delete afterwards
  680         for (size_t i = 0; i < fd.size(); i++) {
  681             delete fd.at(i);
  682         }
  683 
  684         fd.clear ();
  685     }
  686 
  687     lastClicked = nullptr;
  688 }
  689 
  690 void FileBrowser::menuColorlabelActivated (Gtk::MenuItem* m)
  691 {
  692 
  693     std::vector<FileBrowserEntry*> tbe;
  694     tbe.push_back (static_cast<FileBrowserEntry*>(colorLabel_actionData));
  695 
  696     for (int i = 0; i < 6; i++)
  697         if (m == colorlabel_pop[i]) {
  698             colorlabelRequested (tbe, i);
  699             return;
  700         }
  701 }
  702 
  703 void FileBrowser::menuItemActivated (Gtk::MenuItem* m)
  704 {
  705 
  706     std::vector<FileBrowserEntry*> mselected;
  707 
  708     {
  709         MYREADERLOCK(l, entryRW);
  710 
  711         for (size_t i = 0; i < selected.size(); i++) {
  712             mselected.push_back (static_cast<FileBrowserEntry*>(selected[i]));
  713         }
  714     }
  715 
  716 
  717     if (!tbl || (m != selall && mselected.empty()) ) {
  718         return;
  719     }
  720 
  721     for (int i = 0; i < 6; i++)
  722         if (m == rank[i]) {
  723             rankingRequested (mselected, i);
  724             return;
  725         }
  726 
  727     for (int i = 0; i < 6; i++)
  728         if (m == colorlabel[i]) {
  729             colorlabelRequested (mselected, i);
  730             return;
  731         }
  732 
  733     for (size_t j = 0; j < mMenuExtProgs.size(); j++) {
  734         if (m == amiExtProg[j]) {
  735             const auto pAct = mMenuExtProgs[m->get_label()];
  736 
  737             // Build vector of all file names
  738             std::vector<Glib::ustring> selFileNames;
  739 
  740             for (size_t i = 0; i < mselected.size(); i++) {
  741                 Glib::ustring fn = mselected[i]->thumbnail->getFileName();
  742 
  743                 // Maybe batch processed version
  744                 if (pAct->target == 2) {
  745                     fn = Glib::ustring::compose ("%1.%2", BatchQueue::calcAutoFileNameBase(fn), options.saveFormatBatch.format);
  746                 }
  747 
  748                 selFileNames.push_back(fn);
  749             }
  750 
  751             pAct->execute (selFileNames);
  752             return;
  753         }
  754     }
  755 
  756     if (m == open) {
  757         openRequested(mselected);
  758     } else if (m == remove) {
  759         tbl->deleteRequested (mselected, false, true);
  760     } else if (m == removeInclProc) {
  761         tbl->deleteRequested (mselected, true, true);
  762     } else if (m == trash) {
  763         toTrashRequested (mselected);
  764     } else if (m == untrash) {
  765         fromTrashRequested (mselected);
  766     }
  767 
  768     else if (m == develop) {
  769         tbl->developRequested (mselected, false);
  770     } else if (m == developfast) {
  771         if (exportPanel) {
  772             // force saving export panel settings
  773             exportPanel->setExportPanelListener(nullptr);
  774             exportPanel->FastExportPressed();
  775             exportPanel->setExportPanelListener(this);
  776         }
  777         tbl->developRequested (mselected, true);
  778     }
  779 
  780     else if (m == rename) {
  781         tbl->renameRequested (mselected);
  782     } else if (m == selall) {
  783         lastClicked = nullptr;
  784         {
  785             MYWRITERLOCK(l, entryRW);
  786 
  787             selected.clear();
  788 
  789             for (size_t i = 0; i < fd.size(); ++i) {
  790                 if (checkFilter(fd[i])) {
  791                     fd[i]->selected = true;
  792                     selected.push_back(fd[i]);
  793                 }
  794             }
  795             if (!anchor && !selected.empty()) {
  796                 anchor = selected[0];
  797             }
  798         }
  799         queue_draw ();
  800         notifySelectionListener();
  801     } else if( m == copyTo) {
  802         tbl->copyMoveRequested (mselected, false);
  803     }
  804 
  805     else if( m == moveTo) {
  806         tbl->copyMoveRequested (mselected, true);
  807     }
  808 
  809     else if (m == autoDF) {
  810         if (!mselected.empty() && bppcl) {
  811             bppcl->beginBatchPParamsChange(mselected.size());
  812         }
  813 
  814         for (size_t i = 0; i < mselected.size(); i++) {
  815             rtengine::procparams::ProcParams pp = mselected[i]->thumbnail->getProcParams();
  816             pp.raw.df_autoselect = true;
  817             pp.raw.dark_frame.clear();
  818             mselected[i]->thumbnail->setProcParams(pp, nullptr, FILEBROWSER, false);
  819         }
  820 
  821         if (!mselected.empty() && bppcl) {
  822             bppcl->endBatchPParamsChange();
  823         }
  824 
  825     } else if (m == selectDF) {
  826         if( !mselected.empty() ) {
  827             rtengine::procparams::ProcParams pp = mselected[0]->thumbnail->getProcParams();
  828             Gtk::FileChooserDialog fc (getToplevelWindow (this), "Dark Frame", Gtk::FILE_CHOOSER_ACTION_OPEN );
  829             bindCurrentFolder (fc, options.lastDarkframeDir);
  830             fc.add_button( M("GENERAL_CANCEL"), Gtk::RESPONSE_CANCEL);
  831             fc.add_button( M("GENERAL_APPLY"), Gtk::RESPONSE_APPLY);
  832 
  833             if(!pp.raw.dark_frame.empty()) {
  834                 fc.set_filename( pp.raw.dark_frame );
  835             }
  836 
  837             if( fc.run() == Gtk::RESPONSE_APPLY ) {
  838                 if (bppcl) {
  839                     bppcl->beginBatchPParamsChange(mselected.size());
  840                 }
  841 
  842                 for (size_t i = 0; i < mselected.size(); i++) {
  843                     rtengine::procparams::ProcParams lpp = mselected[i]->thumbnail->getProcParams();
  844                     lpp.raw.dark_frame = fc.get_filename();
  845                     lpp.raw.df_autoselect = false;
  846                     mselected[i]->thumbnail->setProcParams(lpp, nullptr, FILEBROWSER, false);
  847                 }
  848 
  849                 if (bppcl) {
  850                     bppcl->endBatchPParamsChange();
  851                 }
  852             }
  853         }
  854     } else if( m == thisIsDF) {
  855         if( !options.rtSettings.darkFramesPath.empty()) {
  856             if (Gio::File::create_for_path(options.rtSettings.darkFramesPath)->query_exists() ) {
  857                 for (size_t i = 0; i < mselected.size(); i++) {
  858                     Glib::RefPtr<Gio::File> file = Gio::File::create_for_path ( mselected[i]->filename );
  859 
  860                     if( !file ) {
  861                         continue;
  862                     }
  863 
  864                     Glib::ustring destName = options.rtSettings.darkFramesPath + "/" + file->get_basename();
  865                     Glib::RefPtr<Gio::File> dest = Gio::File::create_for_path ( destName );
  866                     file->move(  dest );
  867                 }
  868 
  869                 // Reinit cache
  870                 rtengine::dfm.init( options.rtSettings.darkFramesPath );
  871             } else {
  872                 // Target directory creation failed, we clear the darkFramesPath setting
  873                 options.rtSettings.darkFramesPath.clear();
  874                 Glib::ustring msg_ = Glib::ustring::compose (M("MAIN_MSG_PATHDOESNTEXIST"), options.rtSettings.darkFramesPath)
  875                                      + "\n\n" + M("MAIN_MSG_OPERATIONCANCELLED");
  876                 Gtk::MessageDialog msgd (msg_, true, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
  877                 msgd.set_title(M("TP_DARKFRAME_LABEL"));
  878                 msgd.run ();
  879             }
  880         } else {
  881             Glib::ustring msg_ = M("MAIN_MSG_SETPATHFIRST") + "\n\n" + M("MAIN_MSG_OPERATIONCANCELLED");
  882             Gtk::MessageDialog msgd (msg_, true, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
  883             msgd.set_title(M("TP_DARKFRAME_LABEL"));
  884             msgd.run ();
  885         }
  886     } else if (m == autoFF) {
  887         if (!mselected.empty() && bppcl) {
  888             bppcl->beginBatchPParamsChange(mselected.size());
  889         }
  890 
  891         for (size_t i = 0; i < mselected.size(); i++) {
  892             rtengine::procparams::ProcParams pp = mselected[i]->thumbnail->getProcParams();
  893             pp.raw.ff_AutoSelect = true;
  894             pp.raw.ff_file.clear();
  895             mselected[i]->thumbnail->setProcParams(pp, nullptr, FILEBROWSER, false);
  896         }
  897 
  898         if (!mselected.empty() && bppcl) {
  899             bppcl->endBatchPParamsChange();
  900         }
  901     } else if (m == selectFF) {
  902         if( !mselected.empty() ) {
  903             rtengine::procparams::ProcParams pp = mselected[0]->thumbnail->getProcParams();
  904             Gtk::FileChooserDialog fc (getToplevelWindow (this), "Flat Field", Gtk::FILE_CHOOSER_ACTION_OPEN );
  905             bindCurrentFolder (fc, options.lastFlatfieldDir);
  906             fc.add_button( M("GENERAL_CANCEL"), Gtk::RESPONSE_CANCEL);
  907             fc.add_button( M("GENERAL_APPLY"), Gtk::RESPONSE_APPLY);
  908 
  909             if(!pp.raw.ff_file.empty()) {
  910                 fc.set_filename( pp.raw.ff_file );
  911             }
  912 
  913             if( fc.run() == Gtk::RESPONSE_APPLY ) {
  914                 if (bppcl) {
  915                     bppcl->beginBatchPParamsChange(mselected.size());
  916                 }
  917 
  918                 for (size_t i = 0; i < mselected.size(); i++) {
  919                     rtengine::procparams::ProcParams lpp = mselected[i]->thumbnail->getProcParams();
  920                     lpp.raw.ff_file = fc.get_filename();
  921                     lpp.raw.ff_AutoSelect = false;
  922                     mselected[i]->thumbnail->setProcParams(lpp, nullptr, FILEBROWSER, false);
  923                 }
  924 
  925                 if (bppcl) {
  926                     bppcl->endBatchPParamsChange();
  927                 }
  928             }
  929         }
  930     } else if( m == thisIsFF) {
  931         if( !options.rtSettings.flatFieldsPath.empty()) {
  932             if (Gio::File::create_for_path(options.rtSettings.flatFieldsPath)->query_exists() ) {
  933                 for (size_t i = 0; i < mselected.size(); i++) {
  934                     Glib::RefPtr<Gio::File> file = Gio::File::create_for_path ( mselected[i]->filename );
  935 
  936                     if( !file ) {
  937                         continue;
  938                     }
  939 
  940                     Glib::ustring destName = options.rtSettings.flatFieldsPath + "/" + file->get_basename();
  941                     Glib::RefPtr<Gio::File> dest = Gio::File::create_for_path ( destName );
  942                     file->move(  dest );
  943                 }
  944 
  945                 // Reinit cache
  946                 rtengine::ffm.init( options.rtSettings.flatFieldsPath );
  947             } else {
  948                 // Target directory creation failed, we clear the flatFieldsPath setting
  949                 options.rtSettings.flatFieldsPath.clear();
  950                 Glib::ustring msg_ = Glib::ustring::compose (M("MAIN_MSG_PATHDOESNTEXIST"), options.rtSettings.flatFieldsPath)
  951                                      + "\n\n" + M("MAIN_MSG_OPERATIONCANCELLED");
  952                 Gtk::MessageDialog msgd (msg_, true, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
  953                 msgd.set_title(M("TP_FLATFIELD_LABEL"));
  954                 msgd.run ();
  955             }
  956         } else {
  957             Glib::ustring msg_ = M("MAIN_MSG_SETPATHFIRST") + "\n\n" + M("MAIN_MSG_OPERATIONCANCELLED");
  958             Gtk::MessageDialog msgd (msg_, true, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
  959             msgd.set_title(M("TP_FLATFIELD_LABEL"));
  960             msgd.run ();
  961         }
  962     } else if (m == copyprof) {
  963         copyProfile ();
  964     } else if (m == pasteprof) {
  965         pasteProfile ();
  966     } else if (m == partpasteprof) {
  967         partPasteProfile ();
  968     } else if (m == clearprof) {
  969         for (size_t i = 0; i < mselected.size(); i++) {
  970             mselected[i]->thumbnail->clearProcParams (FILEBROWSER);
  971         }
  972 
  973         queue_draw ();
  974     } else if (m == resetdefaultprof) {
  975         if (!mselected.empty() && bppcl) {
  976             bppcl->beginBatchPParamsChange(mselected.size());
  977         }
  978 
  979         for (size_t i = 0; i < mselected.size(); i++)  {
  980             mselected[i]->thumbnail->createProcParamsForUpdate (false, true);
  981 
  982             // Empty run to update the thumb
  983             rtengine::procparams::ProcParams params = mselected[i]->thumbnail->getProcParams ();
  984             mselected[i]->thumbnail->setProcParams (params, nullptr, FILEBROWSER, true, true);
  985         }
  986 
  987         if (!mselected.empty() && bppcl) {
  988             bppcl->endBatchPParamsChange();
  989         }
  990     } else if (m == clearFromCache) {
  991         tbl->clearFromCacheRequested (mselected, false);
  992 
  993         //queue_draw ();
  994     } else if (m == clearFromCacheFull) {
  995         tbl->clearFromCacheRequested (mselected, true);
  996 
  997         //queue_draw ();
  998 #ifdef WIN32
  999     } else if (miOpenDefaultViewer && m == miOpenDefaultViewer) {
 1000         openDefaultViewer(1);
 1001 #endif
 1002     }
 1003 }
 1004 
 1005 void FileBrowser::copyProfile ()
 1006 {
 1007     MYREADERLOCK(l, entryRW);
 1008 
 1009     if (selected.size() == 1) {
 1010         clipboard.setProcParams ((static_cast<FileBrowserEntry*>(selected[0]))->thumbnail->getProcParams());
 1011     }
 1012 }
 1013 
 1014 void FileBrowser::pasteProfile ()
 1015 {
 1016 
 1017     if (clipboard.hasProcParams()) {
 1018         std::vector<FileBrowserEntry*> mselected;
 1019         {
 1020             MYREADERLOCK(l, entryRW);
 1021 
 1022             for (unsigned int i = 0; i < selected.size(); i++) {
 1023                 mselected.push_back (static_cast<FileBrowserEntry*>(selected[i]));
 1024             }
 1025         }
 1026 
 1027         if (!tbl || mselected.empty()) {
 1028             return;
 1029         }
 1030 
 1031         if (!mselected.empty() && bppcl) {
 1032             bppcl->beginBatchPParamsChange(mselected.size());
 1033         }
 1034 
 1035         for (unsigned int i = 0; i < mselected.size(); i++) {
 1036             // copying read only clipboard PartialProfile to a temporary one
 1037             const rtengine::procparams::PartialProfile& cbPartProf = clipboard.getPartialProfile();
 1038             rtengine::procparams::PartialProfile pastedPartProf(cbPartProf.pparams, cbPartProf.pedited, true);
 1039 
 1040             // applying the PartialProfile to the thumb's ProcParams
 1041             mselected[i]->thumbnail->setProcParams (*pastedPartProf.pparams, pastedPartProf.pedited, FILEBROWSER);
 1042             pastedPartProf.deleteInstance();
 1043         }
 1044 
 1045         if (!mselected.empty() && bppcl) {
 1046             bppcl->endBatchPParamsChange();
 1047         }
 1048 
 1049         queue_draw ();
 1050     }
 1051 }
 1052 
 1053 void FileBrowser::partPasteProfile ()
 1054 {
 1055 
 1056     if (clipboard.hasProcParams()) {
 1057 
 1058         std::vector<FileBrowserEntry*> mselected;
 1059         {
 1060             MYREADERLOCK(l, entryRW);
 1061 
 1062             for (unsigned int i = 0; i < selected.size(); i++) {
 1063                 mselected.push_back (static_cast<FileBrowserEntry*>(selected[i]));
 1064             }
 1065         }
 1066 
 1067         if (!tbl || mselected.empty()) {
 1068             return;
 1069         }
 1070 
 1071         auto toplevel = static_cast<Gtk::Window*> (get_toplevel ());
 1072         PartialPasteDlg partialPasteDlg (M("PARTIALPASTE_DIALOGLABEL"), toplevel);
 1073 
 1074         int i = partialPasteDlg.run ();
 1075 
 1076         if (i == Gtk::RESPONSE_OK) {
 1077 
 1078             if (!mselected.empty() && bppcl) {
 1079                 bppcl->beginBatchPParamsChange(mselected.size());
 1080             }
 1081 
 1082             for (auto entry : mselected) {
 1083                 // copying read only clipboard PartialProfile to a temporary one, initialized to the thumb's ProcParams
 1084                 entry->thumbnail->createProcParamsForUpdate(false, false); // this can execute customprofilebuilder to generate param file
 1085                 const rtengine::procparams::PartialProfile& cbPartProf = clipboard.getPartialProfile();
 1086                 rtengine::procparams::PartialProfile pastedPartProf(&entry->thumbnail->getProcParams (), nullptr);
 1087 
 1088                 // pushing the selected values of the clipboard PartialProfile to the temporary PartialProfile
 1089                 partialPasteDlg.applyPaste (pastedPartProf.pparams, pastedPartProf.pedited, cbPartProf.pparams, cbPartProf.pedited);
 1090 
 1091                 // applying the temporary PartialProfile to the thumb's ProcParams
 1092                 entry->thumbnail->setProcParams (*pastedPartProf.pparams, pastedPartProf.pedited, FILEBROWSER);
 1093                 pastedPartProf.deleteInstance();
 1094             }
 1095 
 1096             if (!mselected.empty() && bppcl) {
 1097                 bppcl->endBatchPParamsChange();
 1098             }
 1099 
 1100             queue_draw ();
 1101         }
 1102 
 1103         partialPasteDlg.hide ();
 1104     }
 1105 }
 1106 
 1107 #ifdef WIN32
 1108 void FileBrowser::openDefaultViewer (int destination)
 1109 {
 1110     bool success = true;
 1111 
 1112     {
 1113         MYREADERLOCK(l, entryRW);
 1114 
 1115         if (selected.size() == 1) {
 1116             success = (static_cast<FileBrowserEntry*>(selected[0]))->thumbnail->openDefaultViewer(destination);
 1117         }
 1118     }
 1119 
 1120     if (!success) {
 1121         Gtk::MessageDialog msgd(getToplevelWindow(this), M("MAIN_MSG_IMAGEUNPROCESSED"), true, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
 1122         msgd.run ();
 1123     }
 1124 }
 1125 #endif
 1126 
 1127 bool FileBrowser::keyPressed (GdkEventKey* event)
 1128 {
 1129     bool ctrl  = event->state & GDK_CONTROL_MASK;
 1130     bool shift = event->state & GDK_SHIFT_MASK;
 1131     bool alt   = event->state & GDK_MOD1_MASK;
 1132 #ifdef __WIN32__
 1133     bool altgr = event->state & GDK_MOD2_MASK;
 1134 #endif
 1135 
 1136     if ((event->keyval == GDK_KEY_C || event->keyval == GDK_KEY_c) && ctrl && shift) {
 1137         menuItemActivated (copyTo);
 1138         return true;
 1139     } else if ((event->keyval == GDK_KEY_M || event->keyval == GDK_KEY_m) && ctrl && shift) {
 1140         menuItemActivated (moveTo);
 1141         return true;
 1142     } else if ((event->keyval == GDK_KEY_C || event->keyval == GDK_KEY_c || event->keyval == GDK_KEY_Insert) && ctrl) {
 1143         copyProfile ();
 1144         return true;
 1145     } else if ((event->keyval == GDK_KEY_V || event->keyval == GDK_KEY_v) && ctrl && !shift) {
 1146         pasteProfile ();
 1147         return true;
 1148     } else if (event->keyval == GDK_KEY_Insert && shift) {
 1149         pasteProfile ();
 1150         return true;
 1151     } else if ((event->keyval == GDK_KEY_V || event->keyval == GDK_KEY_v) && ctrl && shift) {
 1152         partPasteProfile ();
 1153         return true;
 1154     } else if (event->keyval == GDK_KEY_Delete && !shift) {
 1155         menuItemActivated (trash);
 1156         return true;
 1157     } else if (event->keyval == GDK_KEY_Delete && shift) {
 1158         menuItemActivated (untrash);
 1159         return true;
 1160     } else if ((event->keyval == GDK_KEY_B || event->keyval == GDK_KEY_b) && ctrl && !shift) {
 1161         menuItemActivated (develop);
 1162         return true;
 1163     } else if ((event->keyval == GDK_KEY_B || event->keyval == GDK_KEY_b) && ctrl && shift) {
 1164         menuItemActivated (developfast);
 1165         return true;
 1166     } else if ((event->keyval == GDK_KEY_A || event->keyval == GDK_KEY_a) && ctrl) {
 1167         menuItemActivated (selall);
 1168         return true;
 1169     } else if (event->keyval == GDK_KEY_F2 && !ctrl) {
 1170         menuItemActivated (rename);
 1171         return true;
 1172     } else if (event->keyval == GDK_KEY_F3 && !(ctrl || shift || alt)) { // open Previous image from FileBrowser perspective
 1173         FileBrowser::openPrevImage ();
 1174         return true;
 1175     } else if (event->keyval == GDK_KEY_F4 && !(ctrl || shift || alt)) { // open Next image from FileBrowser perspective
 1176         FileBrowser::openNextImage ();
 1177         return true;
 1178     } else if (event->keyval == GDK_KEY_Left) {
 1179         selectPrev (1, shift);
 1180         return true;
 1181     } else if (event->keyval == GDK_KEY_Right) {
 1182         selectNext (1, shift);
 1183         return true;
 1184     } else if (event->keyval == GDK_KEY_Up) {
 1185         selectPrev (numOfCols, shift);
 1186         return true;
 1187     } else if (event->keyval == GDK_KEY_Down) {
 1188         selectNext (numOfCols, shift);
 1189         return true;
 1190     } else if (event->keyval == GDK_KEY_Home) {
 1191         selectFirst (shift);
 1192         return true;
 1193     } else if (event->keyval == GDK_KEY_End) {
 1194         selectLast (shift);
 1195         return true;
 1196     } else if(event->keyval == GDK_KEY_Return || event->keyval == GDK_KEY_KP_Enter) {
 1197         std::vector<FileBrowserEntry*> mselected;
 1198 
 1199         for (size_t i = 0; i < selected.size(); i++) {
 1200             mselected.push_back (static_cast<FileBrowserEntry*>(selected[i]));
 1201         }
 1202 
 1203         openRequested(mselected);
 1204 #ifdef WIN32
 1205     } else if (event->keyval == GDK_KEY_F5) {
 1206         int dest = 1;
 1207 
 1208         if (event->state & GDK_SHIFT_MASK) {
 1209             dest = 2;
 1210         } else if (event->state & GDK_CONTROL_MASK) {
 1211             dest = 3;
 1212         }
 1213 
 1214         openDefaultViewer (dest);
 1215         return true;
 1216 #endif
 1217     } else if (event->keyval == GDK_KEY_Page_Up) {
 1218         scrollPage(GDK_SCROLL_UP);
 1219         return true;
 1220     } else if (event->keyval == GDK_KEY_Page_Down) {
 1221         scrollPage(GDK_SCROLL_DOWN);
 1222         return true;
 1223     }
 1224 
 1225 #ifdef __WIN32__
 1226     else if (shift && !ctrl && !alt && !altgr) { // rank
 1227         switch(event->hardware_keycode) {
 1228         case 0x30:  // 0-key
 1229             requestRanking (0);
 1230             return true;
 1231 
 1232         case 0x31:  // 1-key
 1233             requestRanking (1);
 1234             return true;
 1235 
 1236         case 0x32:  // 2-key
 1237             requestRanking (2);
 1238             return true;
 1239 
 1240         case 0x33:  // 3-key
 1241             requestRanking (3);
 1242             return true;
 1243 
 1244         case 0x34:  // 4-key
 1245             requestRanking (4);
 1246             return true;
 1247 
 1248         case 0x35:  // 5-key
 1249             requestRanking (5);
 1250             return true;
 1251         }
 1252     } else if (shift && ctrl && !alt && !altgr) { // color labels
 1253         switch(event->hardware_keycode) {
 1254         case 0x30:  // 0-key
 1255             requestColorLabel (0);
 1256             return true;
 1257 
 1258         case 0x31:  // 1-key
 1259             requestColorLabel (1);
 1260             return true;
 1261 
 1262         case 0x32:  // 2-key
 1263             requestColorLabel (2);
 1264             return true;
 1265 
 1266         case 0x33:  // 3-key
 1267             requestColorLabel (3);
 1268             return true;
 1269 
 1270         case 0x34:  // 4-key
 1271             requestColorLabel (4);
 1272             return true;
 1273 
 1274         case 0x35:  // 5-key
 1275             requestColorLabel (5);
 1276             return true;
 1277         }
 1278     }
 1279 
 1280 #else
 1281     else if (shift && !ctrl && !alt) { // rank
 1282         switch(event->hardware_keycode) {
 1283         case 0x13:
 1284             requestRanking (0);
 1285             return true;
 1286 
 1287         case 0x0a:
 1288             requestRanking (1);
 1289             return true;
 1290 
 1291         case 0x0b:
 1292             requestRanking (2);
 1293             return true;
 1294 
 1295         case 0x0c:
 1296             requestRanking (3);
 1297             return true;
 1298 
 1299         case 0x0d:
 1300             requestRanking (4);
 1301             return true;
 1302 
 1303         case 0x0e:
 1304             requestRanking (5);
 1305             return true;
 1306         }
 1307     } else if (shift && ctrl && !alt) { // color labels
 1308         switch(event->hardware_keycode) {
 1309         case 0x13:
 1310             requestColorLabel (0);
 1311             return true;
 1312 
 1313         case 0x0a:
 1314             requestColorLabel (1);
 1315             return true;
 1316 
 1317         case 0x0b:
 1318             requestColorLabel (2);
 1319             return true;
 1320 
 1321         case 0x0c:
 1322             requestColorLabel (3);
 1323             return true;
 1324 
 1325         case 0x0d:
 1326             requestColorLabel (4);
 1327             return true;
 1328 
 1329         case 0x0e:
 1330             requestColorLabel (5);
 1331             return true;
 1332         }
 1333     }
 1334 
 1335 #endif
 1336 
 1337     return false;
 1338 }
 1339 
 1340 void FileBrowser::saveThumbnailHeight (int height)
 1341 {
 1342     if (!options.sameThumbSize && getLocation() == THLOC_EDITOR) {
 1343         options.thumbSizeTab = height;
 1344     } else {
 1345         options.thumbSize = height;
 1346     }
 1347 }
 1348 
 1349 int FileBrowser::getThumbnailHeight ()
 1350 {
 1351     // The user could have manually forced the option to a too big value
 1352     if (!options.sameThumbSize && getLocation() == THLOC_EDITOR) {
 1353         return std::max(std::min(options.thumbSizeTab, 800), 10);
 1354     } else {
 1355         return std::max(std::min(options.thumbSize, 800), 10);
 1356     }
 1357 }
 1358 
 1359 void FileBrowser::applyMenuItemActivated (ProfileStoreLabel *label)
 1360 {
 1361     MYREADERLOCK(l, entryRW);
 1362 
 1363     const rtengine::procparams::PartialProfile* partProfile = ProfileStore::getInstance()->getProfile (label->entry);
 1364 
 1365     if (partProfile->pparams && !selected.empty()) {
 1366         if (bppcl) {
 1367             bppcl->beginBatchPParamsChange(selected.size());
 1368         }
 1369 
 1370         for (size_t i = 0; i < selected.size(); i++) {
 1371             (static_cast<FileBrowserEntry*>(selected[i]))->thumbnail->setProcParams (*partProfile->pparams, partProfile->pedited, FILEBROWSER);
 1372         }
 1373 
 1374         if (bppcl) {
 1375             bppcl->endBatchPParamsChange();
 1376         }
 1377 
 1378         queue_draw ();
 1379     }
 1380 }
 1381 
 1382 void FileBrowser::applyPartialMenuItemActivated (ProfileStoreLabel *label)
 1383 {
 1384 
 1385     {
 1386         MYREADERLOCK(l, entryRW);
 1387 
 1388         if (!tbl || selected.empty()) {
 1389             return;
 1390         }
 1391     }
 1392 
 1393     const rtengine::procparams::PartialProfile* srcProfiles = ProfileStore::getInstance()->getProfile (label->entry);
 1394 
 1395     if (srcProfiles->pparams) {
 1396 
 1397         auto toplevel = static_cast<Gtk::Window*> (get_toplevel ());
 1398         PartialPasteDlg partialPasteDlg (M("PARTIALPASTE_DIALOGLABEL"), toplevel);
 1399 
 1400         if (partialPasteDlg.run() == Gtk::RESPONSE_OK) {
 1401 
 1402             MYREADERLOCK(l, entryRW);
 1403 
 1404             if (bppcl) {
 1405                 bppcl->beginBatchPParamsChange(selected.size());
 1406             }
 1407 
 1408             for (size_t i = 0; i < selected.size(); i++) {
 1409                 selected[i]->thumbnail->createProcParamsForUpdate(false, false);  // this can execute customprofilebuilder to generate param file
 1410 
 1411                 rtengine::procparams::PartialProfile dstProfile(true);
 1412                 *dstProfile.pparams = (static_cast<FileBrowserEntry*>(selected[i]))->thumbnail->getProcParams ();
 1413                 dstProfile.set(true);
 1414                 partialPasteDlg.applyPaste (dstProfile.pparams, dstProfile.pedited, srcProfiles->pparams, srcProfiles->pedited);
 1415                 (static_cast<FileBrowserEntry*>(selected[i]))->thumbnail->setProcParams (*dstProfile.pparams, dstProfile.pedited, FILEBROWSER);
 1416                 dstProfile.deleteInstance();
 1417             }
 1418 
 1419             if (bppcl) {
 1420                 bppcl->endBatchPParamsChange();
 1421             }
 1422 
 1423             queue_draw ();
 1424         }
 1425 
 1426         partialPasteDlg.hide ();
 1427     }
 1428 }
 1429 
 1430 void FileBrowser::applyFilter (const BrowserFilter& filter)
 1431 {
 1432 
 1433     this->filter = filter;
 1434 
 1435     // remove items not complying the filter from the selection
 1436     bool selchanged = false;
 1437     numFiltered = 0;
 1438     {
 1439         MYWRITERLOCK(l, entryRW);
 1440 
 1441         if (filter.showOriginal) {
 1442             findOriginalEntries(fd);
 1443         }
 1444 
 1445         for (size_t i = 0; i < fd.size(); i++) {
 1446             if (checkFilter(fd[i])) {
 1447                 numFiltered++;
 1448             } else if (fd[i]->selected) {
 1449                 fd[i]->selected = false;
 1450                 std::vector<ThumbBrowserEntryBase*>::iterator j = std::find(selected.begin(), selected.end(), fd[i]);
 1451                 selected.erase(j);
 1452 
 1453                 if (lastClicked == fd[i]) {
 1454                     lastClicked = nullptr;
 1455                 }
 1456 
 1457                 selchanged = true;
 1458             }
 1459         }
 1460         if (selected.empty() || (anchor && std::find(selected.begin(), selected.end(), anchor) == selected.end())) {
 1461             anchor = nullptr;
 1462         }
 1463     }
 1464 
 1465     if (selchanged) {
 1466         notifySelectionListener ();
 1467     }
 1468 
 1469     tbl->filterApplied();
 1470     redraw ();
 1471 }
 1472 
 1473 bool FileBrowser::checkFilter (ThumbBrowserEntryBase* entryb) const   // true -> entry complies filter
 1474 {
 1475 
 1476     FileBrowserEntry* entry = static_cast<FileBrowserEntry*>(entryb);
 1477 
 1478     if (filter.showOriginal && entry->getOriginal()) {
 1479         return false;
 1480     }
 1481 
 1482     // return false if basic filter settings are not satisfied
 1483     if ((!filter.showRanked[entry->thumbnail->getRank()] ) ||
 1484             (!filter.showCLabeled[entry->thumbnail->getColorLabel()] ) ||
 1485 
 1486             ((entry->thumbnail->hasProcParams() && filter.showEdited[0]) && !filter.showEdited[1]) ||
 1487             ((!entry->thumbnail->hasProcParams() && filter.showEdited[1]) && !filter.showEdited[0]) ||
 1488 
 1489             ((entry->thumbnail->isRecentlySaved() && filter.showRecentlySaved[0]) && !filter.showRecentlySaved[1]) ||
 1490             ((!entry->thumbnail->isRecentlySaved() && filter.showRecentlySaved[1]) && !filter.showRecentlySaved[0]) ||
 1491 
 1492             (entry->thumbnail->getStage() && !filter.showTrash) ||
 1493             (!entry->thumbnail->getStage() && !filter.showNotTrash)) {
 1494         return false;
 1495     }
 1496 
 1497     // return false if query is not satisfied
 1498     if (!filter.vFilterStrings.empty()) {
 1499         // check if image's FileName contains queryFileName (case insensitive)
 1500         // TODO should we provide case-sensitive search option via preferences?
 1501         std::string FileName = Glib::path_get_basename(entry->thumbnail->getFileName());
 1502         std::transform(FileName.begin(), FileName.end(), FileName.begin(), ::toupper);
 1503         int iFilenameMatch = 0;
 1504 
 1505         for (const auto& filterString : filter.vFilterStrings) {
 1506             if (FileName.find(filterString) != std::string::npos) {
 1507                 ++iFilenameMatch;
 1508                 break;
 1509             }
 1510         }
 1511 
 1512         if (filter.matchEqual) {
 1513             if (iFilenameMatch == 0) { //none of the vFilterStrings found in FileName
 1514                 return false;
 1515             }
 1516         } else {
 1517             if (iFilenameMatch > 0) { // match is found for at least one of vFilterStrings in FileName
 1518                 return false;
 1519             }
 1520         }
 1521     }
 1522 
 1523     if (!filter.exifFilterEnabled) {
 1524         return true;
 1525     }
 1526 
 1527     // check exif filter
 1528     const CacheImageData* cfs = entry->thumbnail->getCacheImageData();
 1529     double tol = 0.01;
 1530     double tol2 = 1e-8;
 1531 
 1532     if (!cfs->exifValid) {
 1533         return (!filter.exifFilter.filterCamera || filter.exifFilter.cameras.count(cfs->getCamera()) > 0)
 1534                && (!filter.exifFilter.filterLens || filter.exifFilter.lenses.count(cfs->lens) > 0)
 1535                && (!filter.exifFilter.filterFiletype || filter.exifFilter.filetypes.count(cfs->filetype) > 0)
 1536                && (!filter.exifFilter.filterExpComp || filter.exifFilter.expcomp.count(cfs->expcomp) > 0);
 1537     }
 1538 
 1539     return
 1540         (!filter.exifFilter.filterShutter || (rtengine::FramesMetaData::shutterFromString(rtengine::FramesMetaData::shutterToString(cfs->shutter)) >= filter.exifFilter.shutterFrom - tol2 && rtengine::FramesMetaData::shutterFromString(rtengine::FramesMetaData::shutterToString(cfs->shutter)) <= filter.exifFilter.shutterTo + tol2))
 1541         && (!filter.exifFilter.filterFNumber || (rtengine::FramesMetaData::apertureFromString(rtengine::FramesMetaData::apertureToString(cfs->fnumber)) >= filter.exifFilter.fnumberFrom - tol2 && rtengine::FramesMetaData::apertureFromString(rtengine::FramesMetaData::apertureToString(cfs->fnumber)) <= filter.exifFilter.fnumberTo + tol2))
 1542         && (!filter.exifFilter.filterFocalLen || (cfs->focalLen >= filter.exifFilter.focalFrom - tol && cfs->focalLen <= filter.exifFilter.focalTo + tol))
 1543         && (!filter.exifFilter.filterISO     || (cfs->iso >= filter.exifFilter.isoFrom && cfs->iso <= filter.exifFilter.isoTo))
 1544         && (!filter.exifFilter.filterExpComp || filter.exifFilter.expcomp.count(cfs->expcomp) > 0)
 1545         && (!filter.exifFilter.filterCamera  || filter.exifFilter.cameras.count(cfs->getCamera()) > 0)
 1546         && (!filter.exifFilter.filterLens    || filter.exifFilter.lenses.count(cfs->lens) > 0)
 1547         && (!filter.exifFilter.filterFiletype  || filter.exifFilter.filetypes.count(cfs->filetype) > 0);
 1548 }
 1549 
 1550 void FileBrowser::toTrashRequested (std::vector<FileBrowserEntry*> tbe)
 1551 {
 1552 
 1553     for (size_t i = 0; i < tbe.size(); i++) {
 1554         // try to load the last saved parameters from the cache or from the paramfile file
 1555         tbe[i]->thumbnail->createProcParamsForUpdate(false, false, true);  // this can execute customprofilebuilder to generate param file in "flagging" mode
 1556 
 1557         // no need to notify listeners as item goes to trash, likely to be deleted
 1558 
 1559         if (tbe[i]->thumbnail->getStage()) {
 1560             continue;
 1561         }
 1562 
 1563         tbe[i]->thumbnail->setStage (true);
 1564 
 1565         if (tbe[i]->getThumbButtonSet()) {
 1566             tbe[i]->getThumbButtonSet()->setRank (tbe[i]->thumbnail->getRank());
 1567             tbe[i]->getThumbButtonSet()->setColorLabel (tbe[i]->thumbnail->getColorLabel());
 1568             tbe[i]->getThumbButtonSet()->setInTrash (true);
 1569             tbe[i]->thumbnail->updateCache (); // needed to save the colorlabel to disk in the procparam file(s) and the cache image data file
 1570         }
 1571     }
 1572 
 1573     trash_changed().emit();
 1574     applyFilter (filter);
 1575 }
 1576 
 1577 void FileBrowser::fromTrashRequested (std::vector<FileBrowserEntry*> tbe)
 1578 {
 1579 
 1580     for (size_t i = 0; i < tbe.size(); i++) {
 1581         // if thumbnail was marked inTrash=true then param file must be there, no need to run customprofilebuilder
 1582 
 1583         if (!tbe[i]->thumbnail->getStage()) {
 1584             continue;
 1585         }
 1586 
 1587         tbe[i]->thumbnail->setStage (false);
 1588 
 1589         if (tbe[i]->getThumbButtonSet()) {
 1590             tbe[i]->getThumbButtonSet()->setRank (tbe[i]->thumbnail->getRank());
 1591             tbe[i]->getThumbButtonSet()->setColorLabel (tbe[i]->thumbnail->getColorLabel());
 1592             tbe[i]->getThumbButtonSet()->setInTrash (false);
 1593             tbe[i]->thumbnail->updateCache (); // needed to save the colorlabel to disk in the procparam file(s) and the cache image data file
 1594         }
 1595     }
 1596 
 1597     trash_changed().emit();
 1598     applyFilter (filter);
 1599 }
 1600 
 1601 void FileBrowser::rankingRequested (std::vector<FileBrowserEntry*> tbe, int rank)
 1602 {
 1603 
 1604     if (!tbe.empty() && bppcl) {
 1605         bppcl->beginBatchPParamsChange(tbe.size());
 1606     }
 1607 
 1608     for (size_t i = 0; i < tbe.size(); i++) {
 1609 
 1610         // try to load the last saved parameters from the cache or from the paramfile file
 1611         tbe[i]->thumbnail->createProcParamsForUpdate(false, false, true);  // this can execute customprofilebuilder to generate param file in "flagging" mode
 1612 
 1613         // notify listeners TODO: should do this ONLY when params changed by customprofilebuilder?
 1614         tbe[i]->thumbnail->notifylisterners_procParamsChanged(FILEBROWSER);
 1615 
 1616         tbe[i]->thumbnail->setRank (rank);
 1617         tbe[i]->thumbnail->updateCache (); // needed to save the colorlabel to disk in the procparam file(s) and the cache image data file
 1618         //TODO? - should update pparams instead?
 1619 
 1620         if (tbe[i]->getThumbButtonSet()) {
 1621             tbe[i]->getThumbButtonSet()->setRank (tbe[i]->thumbnail->getRank());
 1622         }
 1623     }
 1624 
 1625     applyFilter (filter);
 1626 
 1627     if (!tbe.empty() && bppcl) {
 1628         bppcl->endBatchPParamsChange();
 1629     }
 1630 }
 1631 
 1632 void FileBrowser::colorlabelRequested (std::vector<FileBrowserEntry*> tbe, int colorlabel)
 1633 {
 1634 
 1635     if (!tbe.empty() && bppcl) {
 1636         bppcl->beginBatchPParamsChange(tbe.size());
 1637     }
 1638 
 1639     for (size_t i = 0; i < tbe.size(); i++) {
 1640         // try to load the last saved parameters from the cache or from the paramfile file
 1641         tbe[i]->thumbnail->createProcParamsForUpdate(false, false, true);  // this can execute customprofilebuilder to generate param file in "flagging" mode
 1642 
 1643         // notify listeners TODO: should do this ONLY when params changed by customprofilebuilder?
 1644         tbe[i]->thumbnail->notifylisterners_procParamsChanged(FILEBROWSER);
 1645 
 1646         tbe[i]->thumbnail->setColorLabel (colorlabel);
 1647         tbe[i]->thumbnail->updateCache(); // needed to save the colorlabel to disk in the procparam file(s) and the cache image data file
 1648 
 1649         //TODO? - should update pparams instead?
 1650         if (tbe[i]->getThumbButtonSet()) {
 1651             tbe[i]->getThumbButtonSet()->setColorLabel (tbe[i]->thumbnail->getColorLabel());
 1652         }
 1653     }
 1654 
 1655     applyFilter (filter);
 1656 
 1657     if (!tbe.empty() && bppcl) {
 1658         bppcl->endBatchPParamsChange();
 1659     }
 1660 }
 1661 
 1662 void FileBrowser::requestRanking(int rank)
 1663 {
 1664     std::vector<FileBrowserEntry*> mselected;
 1665     {
 1666         MYREADERLOCK(l, entryRW);
 1667 
 1668         for (size_t i = 0; i < selected.size(); i++) {
 1669             mselected.push_back (static_cast<FileBrowserEntry*>(selected[i]));
 1670         }
 1671     }
 1672 
 1673     rankingRequested (mselected, rank);
 1674 }
 1675 
 1676 void FileBrowser::requestColorLabel(int colorlabel)
 1677 {
 1678     std::vector<FileBrowserEntry*> mselected;
 1679     {
 1680         MYREADERLOCK(l, entryRW);
 1681 
 1682         for (size_t i = 0; i < selected.size(); i++) {
 1683             mselected.push_back (static_cast<FileBrowserEntry*>(selected[i]));
 1684         }
 1685     }
 1686 
 1687     colorlabelRequested (mselected, colorlabel);
 1688 }
 1689 
 1690 void FileBrowser::buttonPressed (LWButton* button, int actionCode, void* actionData)
 1691 {
 1692 
 1693     if (actionCode >= 0 && actionCode <= 5) { // rank
 1694         std::vector<FileBrowserEntry*> tbe;
 1695         tbe.push_back (static_cast<FileBrowserEntry*>(actionData));
 1696         rankingRequested (tbe, actionCode);
 1697     } else if (actionCode == 6 && tbl) { // to processing queue
 1698         std::vector<FileBrowserEntry*> tbe;
 1699         tbe.push_back (static_cast<FileBrowserEntry*>(actionData));
 1700         tbl->developRequested (tbe, false); // not a fast, but a FULL mode
 1701     } else if (actionCode == 7) { // to trash / undelete
 1702         std::vector<FileBrowserEntry*> tbe;
 1703         FileBrowserEntry* entry = static_cast<FileBrowserEntry*>(actionData);
 1704         tbe.push_back (entry);
 1705 
 1706         if (!entry->thumbnail->getStage()) {
 1707             toTrashRequested (tbe);
 1708         } else {
 1709             fromTrashRequested (tbe);
 1710         }
 1711     } else if (actionCode == 8 && tbl) { // color label
 1712         // show popup menu
 1713         colorLabel_actionData = actionData;// this will be reused when pmenuColorLabels is clicked
 1714         pmenuColorLabels->popup (3, this->eventTime);
 1715     }
 1716 }
 1717 
 1718 void FileBrowser::openNextImage()
 1719 {
 1720     MYWRITERLOCK(l, entryRW);
 1721 
 1722     if (!fd.empty() && selected.size() > 0 && !options.tabbedUI) {
 1723         for (size_t i = 0; i < fd.size() - 1; i++) {
 1724             if (selected[0]->thumbnail->getFileName() == fd[i]->filename) { // located 1-st image in current selection
 1725                 if (i < fd.size() && tbl) {
 1726                     // find the first not-filtered-out (next) image
 1727                     for (size_t k = i + 1; k < fd.size(); k++) {
 1728                         if (!fd[k]->filtered/*checkFilter (fd[k])*/) {
 1729 
 1730                             // clear current selection
 1731                             for (size_t j = 0; j < selected.size(); j++) {
 1732                                 selected[j]->selected = false;
 1733                             }
 1734 
 1735                             selected.clear();
 1736 
 1737                             // set new selection
 1738                             fd[k]->selected = true;
 1739                             selected.push_back(fd[k]);
 1740                             //queue_draw();
 1741 
 1742                             MYWRITERLOCK_RELEASE(l);
 1743 
 1744                             // this will require a read access
 1745                             notifySelectionListener();
 1746 
 1747                             MYWRITERLOCK_ACQUIRE(l);
 1748 
 1749                             // scroll to the selected position, centered horizontally in the container
 1750                             double x1, y1;
 1751                             getScrollPosition(x1, y1);
 1752 
 1753                             double x2 = selected[0]->getStartX();
 1754                             double y2 = selected[0]->getStartY();
 1755 
 1756                             Thumbnail* thumb = (static_cast<FileBrowserEntry*>(fd[k]))->thumbnail;
 1757                             int tw = fd[k]->getMinimalWidth(); // thumb width
 1758 
 1759                             int ww = get_width(); // window width
 1760 
 1761                             MYWRITERLOCK_RELEASE(l);
 1762 
 1763                             // scroll only when selected[0] is outside of the displayed bounds
 1764                             // or less than a thumbnail's width from either edge.
 1765                             if ((x2 > x1 + ww - 1.5 * tw) || (x2 - tw / 2 < x1)) {
 1766                                 setScrollPosition(x2 - (ww - tw) / 2, y2);
 1767                             }
 1768 
 1769                             // open the selected image
 1770                             tbl->openRequested({thumb});
 1771 
 1772                             return;
 1773                         }
 1774                     }
 1775                 }
 1776             }
 1777         }
 1778     }
 1779 }
 1780 
 1781 void FileBrowser::openPrevImage()
 1782 {
 1783     MYWRITERLOCK(l, entryRW);
 1784 
 1785     if (!fd.empty() && selected.size() > 0 && !options.tabbedUI) {
 1786         for (size_t i = 1; i < fd.size(); i++) {
 1787             if (selected[0]->thumbnail->getFileName() == fd[i]->filename) { // located 1-st image in current selection
 1788                 if (i > 0 && tbl) {
 1789                     // find the first not-filtered-out (previous) image
 1790                     for (ssize_t k = (ssize_t)i - 1; k >= 0; k--) {
 1791                         if (!fd[k]->filtered/*checkFilter (fd[k])*/) {
 1792 
 1793                             // clear current selection
 1794                             for (size_t j = 0; j < selected.size(); j++) {
 1795                                 selected[j]->selected = false;
 1796                             }
 1797 
 1798                             selected.clear();
 1799 
 1800                             // set new selection
 1801                             fd[k]->selected = true;
 1802                             selected.push_back(fd[k]);
 1803                             //queue_draw();
 1804 
 1805                             MYWRITERLOCK_RELEASE(l);
 1806 
 1807                             // this will require a read access
 1808                             notifySelectionListener();
 1809 
 1810                             MYWRITERLOCK_ACQUIRE(l);
 1811 
 1812                             // scroll to the selected position, centered horizontally in the container
 1813                             double x1, y1;
 1814                             getScrollPosition(x1, y1);
 1815 
 1816                             double x2 = selected[0]->getStartX();
 1817                             double y2 = selected[0]->getStartY();
 1818 
 1819                             Thumbnail* thumb = (static_cast<FileBrowserEntry*>(fd[k]))->thumbnail;
 1820                             int tw = fd[k]->getMinimalWidth(); // thumb width
 1821 
 1822                             int ww = get_width(); // window width
 1823 
 1824                             MYWRITERLOCK_RELEASE(l);
 1825 
 1826                             // scroll only when selected[0] is outside of the displayed bounds
 1827                             // or less than a thumbnail's width from either edge.
 1828                             if ((x2 > x1 + ww - 1.5 * tw) || (x2 - tw / 2 < x1)) {
 1829                                 setScrollPosition(x2 - (ww - tw) / 2, y2);
 1830                             }
 1831 
 1832                             // open the selected image
 1833                             tbl->openRequested({thumb});
 1834 
 1835                             return;
 1836                         }
 1837                     }
 1838                 }
 1839             }
 1840         }
 1841     }
 1842 }
 1843 
 1844 void FileBrowser::selectImage(const Glib::ustring& fname, bool doScroll)
 1845 {
 1846     MYWRITERLOCK(l, entryRW);
 1847 
 1848     if (!fd.empty() && !options.tabbedUI) {
 1849         for (size_t i = 0; i < fd.size(); i++) {
 1850             if (fname == fd[i]->filename && !fd[i]->filtered) {
 1851                 // matching file found for sync
 1852 
 1853                 // clear current selection
 1854                 for (size_t j = 0; j < selected.size(); j++) {
 1855                     selected[j]->selected = false;
 1856                 }
 1857 
 1858                 selected.clear();
 1859 
 1860                 // set new selection
 1861                 fd[i]->selected = true;
 1862                 selected.push_back(fd[i]);
 1863                 queue_draw();
 1864 
 1865                 MYWRITERLOCK_RELEASE(l);
 1866 
 1867                 // this will require a read access
 1868                 notifySelectionListener();
 1869 
 1870                 MYWRITERLOCK_ACQUIRE(l);
 1871 
 1872                 // scroll to the selected position, centered horizontally in the container
 1873                 double x = selected[0]->getStartX();
 1874                 double y = selected[0]->getStartY();
 1875 
 1876                 int tw = fd[i]->getMinimalWidth(); // thumb width
 1877 
 1878                 int ww = get_width(); // window width
 1879 
 1880                 MYWRITERLOCK_RELEASE(l);
 1881 
 1882                 if (doScroll) {
 1883                     // Center thumb
 1884                     setScrollPosition(x - (ww - tw) / 2, y);
 1885                 }
 1886 
 1887                 return;
 1888             }
 1889         }
 1890     }
 1891 }
 1892 
 1893 void FileBrowser::openNextPreviousEditorImage (const Glib::ustring& fname, eRTNav nextPrevious)
 1894 {
 1895 
 1896     // let FileBrowser acquire Editor's perspective
 1897     selectImage (fname, false);
 1898 
 1899     // now switch to the requested image
 1900     if (nextPrevious == NAV_NEXT) {
 1901         openNextImage();
 1902     } else if (nextPrevious == NAV_PREVIOUS) {
 1903         openPrevImage();
 1904     }
 1905 }
 1906 
 1907 void FileBrowser::thumbRearrangementNeeded ()
 1908 {
 1909     idle_register.add(
 1910         [this]() -> bool
 1911         {
 1912             refreshThumbImages();// arrangeFiles is NOT enough
 1913             return false;
 1914         }
 1915     );
 1916 }
 1917 
 1918 void FileBrowser::selectionChanged ()
 1919 {
 1920 
 1921     notifySelectionListener ();
 1922 }
 1923 
 1924 void FileBrowser::notifySelectionListener ()
 1925 {
 1926 
 1927     if (tbl) {
 1928         MYREADERLOCK(l, entryRW);
 1929 
 1930         std::vector<Thumbnail*> thm;
 1931 
 1932         for (size_t i = 0; i < selected.size(); i++) {
 1933             thm.push_back ((static_cast<FileBrowserEntry*>(selected[i]))->thumbnail);
 1934         }
 1935 
 1936         tbl->selectionChanged (thm);
 1937     }
 1938 }
 1939 
 1940 void FileBrowser::redrawNeeded (LWButton* button)
 1941 {
 1942     GThreadLock lock;
 1943     queue_draw ();
 1944 }
 1945 FileBrowser::type_trash_changed FileBrowser::trash_changed ()
 1946 {
 1947     return m_trash_changed;
 1948 }
 1949 
 1950 
 1951 // ExportPanel interface
 1952 void FileBrowser::exportRequested ()
 1953 {
 1954     FileBrowser::menuItemActivated(developfast);
 1955 }
 1956 
 1957 void FileBrowser::setExportPanel (ExportPanel* expanel)
 1958 {
 1959 
 1960     exportPanel = expanel;
 1961     exportPanel->set_sensitive (false);
 1962     exportPanel->setExportPanelListener (this);
 1963 }
 1964 
 1965 void FileBrowser::storeCurrentValue()
 1966 {
 1967 }
 1968 
 1969 void FileBrowser::updateProfileList()
 1970 {
 1971     // submenu applmenu
 1972     int p = 0;
 1973 
 1974     const std::vector<const ProfileStoreEntry*> *profEntries = ProfileStore::getInstance()->getFileList();  // lock and get a pointer to the profiles' list
 1975 
 1976     std::map<unsigned short /* folderId */, Gtk::Menu*> subMenuList;  // store the Gtk::Menu that Gtk::MenuItem will have to be attached to
 1977 
 1978     subMenuList[0] = Gtk::manage (new Gtk::Menu ()); // adding the root submenu
 1979 
 1980     // iterate the profile store's profile list
 1981     for (size_t i = 0; i < profEntries->size(); i++) {
 1982         // create a new label for the current entry (be it a folder or file)
 1983         ProfileStoreLabel *currLabel = Gtk::manage(new ProfileStoreLabel( profEntries->at(i) ));
 1984 
 1985         // create the MenuItem object
 1986         Gtk::MenuItem* mi = Gtk::manage (new Gtk::MenuItem (*currLabel));
 1987 
 1988         // create a new Menu object if the entry is a folder and not the root one
 1989         if (currLabel->entry->type == PSET_FOLDER) {
 1990             // creating the new sub-menu
 1991             Gtk::Menu* subMenu = Gtk::manage (new Gtk::Menu ());
 1992 
 1993             // add it to the menu list
 1994             subMenuList[currLabel->entry->folderId] = subMenu;
 1995 
 1996             // add it to the parent MenuItem
 1997             mi->set_submenu(*subMenu);
 1998         }
 1999 
 2000         // Hombre: ... does parentMenuId sounds like a hack?         ... Yes.
 2001         int parentMenuId = !options.useBundledProfiles && currLabel->entry->parentFolderId == 1 ? 0 : currLabel->entry->parentFolderId;
 2002         subMenuList[parentMenuId]->attach (*mi, 0, 1, p, p + 1);
 2003         p++;
 2004 
 2005         if (currLabel->entry->type == PSET_FILE) {
 2006             mi->signal_activate().connect (sigc::bind(sigc::mem_fun(*this, &FileBrowser::applyMenuItemActivated), currLabel));
 2007         }
 2008 
 2009         mi->show ();
 2010     }
 2011 
 2012     if (subMenuList.size() && applyprof)
 2013         // TODO: Check that the previous one has been deleted, including all childrens
 2014     {
 2015         applyprof->set_submenu (*(subMenuList.at(0)));
 2016     }
 2017 
 2018     subMenuList.clear();
 2019     subMenuList[0] = Gtk::manage (new Gtk::Menu ()); // adding the root submenu
 2020     // keep profEntries list
 2021 
 2022     // submenu applpartmenu
 2023     p = 0;
 2024 
 2025     for (size_t i = 0; i < profEntries->size(); i++) {
 2026         ProfileStoreLabel *currLabel = Gtk::manage(new ProfileStoreLabel( profEntries->at(i) ));
 2027 
 2028         Gtk::MenuItem* mi = Gtk::manage (new Gtk::MenuItem (*currLabel));
 2029 
 2030         if (currLabel->entry->type == PSET_FOLDER) {
 2031             // creating the new sub-menu
 2032             Gtk::Menu* subMenu = Gtk::manage (new Gtk::Menu ());
 2033 
 2034             // add it to the menu list
 2035             subMenuList[currLabel->entry->folderId] = subMenu;
 2036 
 2037             // add it to the parent MenuItem
 2038             mi->set_submenu(*subMenu);
 2039         }
 2040 
 2041         // Hombre: ... does parentMenuId sounds like a hack?         ... yes.
 2042         int parentMenuId = !options.useBundledProfiles && currLabel->entry->parentFolderId == 1 ? 0 : currLabel->entry->parentFolderId;
 2043         subMenuList[parentMenuId]->attach (*mi, 0, 1, p, p + 1);
 2044         p++;
 2045 
 2046         if (currLabel->entry->type == PSET_FILE) {
 2047             mi->signal_activate().connect (sigc::bind(sigc::mem_fun(*this, &FileBrowser::applyPartialMenuItemActivated), currLabel));
 2048         }
 2049 
 2050         mi->show ();
 2051     }
 2052 
 2053     if (subMenuList.size() && applypartprof)
 2054         // TODO: Check that the previous one has been deleted, including all childrens
 2055     {
 2056         applypartprof->set_submenu (*(subMenuList.at(0)));
 2057     }
 2058 
 2059     ProfileStore::getInstance()->releaseFileList();
 2060     subMenuList.clear();
 2061 }
 2062 
 2063 void FileBrowser::restoreValue()
 2064 {
 2065 }
 2066 
 2067 void FileBrowser::openRequested( std::vector<FileBrowserEntry*> mselected)
 2068 {
 2069     std::vector<Thumbnail*> entries;
 2070     // in Single Editor Mode open only last selected image
 2071     size_t openStart = options.tabbedUI ? 0 : ( mselected.size() > 0 ? mselected.size() - 1 : 0);
 2072 
 2073     for (size_t i = openStart; i < mselected.size(); i++) {
 2074         entries.push_back (mselected[i]->thumbnail);
 2075     }
 2076 
 2077     tbl->openRequested (entries);
 2078 }