1 /*
2  *  This file is part of RawTherapee.
3  *
4  *  Copyright (c) 2004-2010 Gabor Horvath <hgabor@rawtherapee.com>
5  *  Copyright (c) 2011 Michael Ezra <www.michaelezra.com>
6  *
7  *  RawTherapee is free software: you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License as published by
9  *  the Free Software Foundation, either version 3 of the License, or
10  *  (at your option) any later version.
11  *
12  *  RawTherapee is distributed in the hope that it will be useful,
13  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *  GNU General Public License for more details.
16  *
17  *  You should have received a copy of the GNU General Public License
18  *  along with RawTherapee.  If not, see <http://www.gnu.org/licenses/>.
19  */
20 #include "filecatalog.h"
21 
22 #include <iostream>
23 #include <iomanip>
24 
25 #include <glib/gstdio.h>
26 
27 #include "../rtengine/rt_math.h"
28 
29 #include "guiutils.h"
30 #include "options.h"
31 #include "rtimage.h"
32 #include "cachemanager.h"
33 #include "multilangmgr.h"
34 #include "filepanel.h"
35 #include "thumbimageupdater.h"
36 #include "batchqueue.h"
37 #include "placesbrowser.h"
38 #include "fastexport.h"
39 #include "../rtengine/imagedata.h"
40 
41 using namespace std;
42 
43 namespace {
44 
45 class DirCompletion: public Gtk::EntryCompletion {
46 public:
DirCompletion()47     DirCompletion():
48         root_("")
49     {
50         model_ = Gtk::ListStore::create(record_);
51         set_model(model_);
52         set_text_column(record_.column);
53     }
54 
refresh(const Glib::ustring & root)55     void refresh(const Glib::ustring &root)
56     {
57         if (root != root_) {
58             root_ = root;
59 
60             try {
61                 std::vector<Glib::ustring> entries;
62                 Glib::Dir dir(root_);
63                 for (auto fn : dir) {
64                     auto pth = Glib::build_filename(root_, fn);
65                     if (Glib::file_test(pth, Glib::FILE_TEST_IS_DIR)) {
66                         entries.push_back(pth);
67                     }
68                 }
69 
70                 model_->clear();
71                 std::sort(entries.begin(), entries.end());
72                 for (auto &d : entries) {
73                     auto row = *(model_->append());
74                     row[record_.column] = d;
75                 }
76             } catch (Glib::Exception &exc) {
77             }
78         }
79     }
80 
81 private:
82     Glib::RefPtr<Gtk::ListStore> model_;
83 
84     class CompletionRecord: public Gtk::TreeModel::ColumnRecord {
85     public:
CompletionRecord()86         CompletionRecord()
87         {
88             add(column);
89         }
90         Gtk::TreeModelColumn<Glib::ustring> column;
91     };
92 
93     CompletionRecord record_;
94     Glib::ustring root_;
95 };
96 
97 } // namespace
98 
99 
100 #define CHECKTIME 2000
101 
FileCatalog(FilePanel * filepanel)102 FileCatalog::FileCatalog(FilePanel* filepanel) :
103     filepanel(filepanel),
104     selectedDirectoryId(1),
105     refresh_counter_(1),
106     actionNextPrevious(NAV_NONE),
107     listener(nullptr),
108     fslistener(nullptr),
109     hbToolBar1STB(nullptr),
110     hasValidCurrentEFS(false),
111     filterPanel(nullptr),
112     previewsToLoad(0),
113     previewsLoaded(0),
114     modifierKey(0),
115     bqueue_(nullptr)
116 {
117     inTabMode = false;
118 
119     set_name ("FileBrowser");
120 
121     //  construct and initialize thumbnail browsers
122     fileBrowser = Gtk::manage( new FileBrowser() );
123     fileBrowser->setFileBrowserListener (this);
124     fileBrowser->setArrangement (ThumbBrowserBase::TB_Vertical);
125     fileBrowser->show ();
126 
127     set_size_request(0, 250);
128     // construct trash panel with the extra "empty trash" button
129     trashButtonBox = Gtk::manage( new Gtk::VBox );
130     Gtk::Button* emptyT = Gtk::manage( new Gtk::Button ());
131     emptyT->set_tooltip_markup (M("FILEBROWSER_EMPTYTRASHHINT"));
132     emptyT->set_image (*Gtk::manage(new RTImage ("trash-delete.png")));
133     emptyT->signal_pressed().connect (sigc::mem_fun(*this, &FileCatalog::emptyTrash));
134     trashButtonBox->pack_start (*emptyT, Gtk::PACK_SHRINK, 4);
135     emptyT->show ();
136     trashButtonBox->show ();
137 
138     //initialize hbToolBar1
139     hbToolBar1 = Gtk::manage(new Gtk::HBox ());
140 
141     //setup BrowsePath
142     iRefreshWhite = new RTImage("refresh-small.png");
143     iRefreshRed = new RTImage("refresh-red-small.png");
144 
145     BrowsePath = Gtk::manage(new Gtk::Entry ());
146     BrowsePath->set_width_chars (50);
147     BrowsePath->set_tooltip_markup (M("FILEBROWSER_BROWSEPATHHINT"));
148     Gtk::HBox* hbBrowsePath = Gtk::manage(new Gtk::HBox ());
149     buttonBrowsePath = Gtk::manage(new Gtk::Button ());
150     buttonBrowsePath->set_image (*iRefreshWhite);
151     buttonBrowsePath->set_tooltip_markup (M("FILEBROWSER_BROWSEPATHBUTTONHINT"));
152     buttonBrowsePath->set_relief (Gtk::RELIEF_NONE);
153     buttonBrowsePath->signal_clicked().connect( sigc::mem_fun(*this, &FileCatalog::buttonBrowsePathPressed) );
154     hbBrowsePath->pack_start (*BrowsePath, Gtk::PACK_EXPAND_WIDGET, 0);
155     hbBrowsePath->pack_start (*buttonBrowsePath, Gtk::PACK_SHRINK, 0);
156     hbToolBar1->pack_start (*hbBrowsePath, Gtk::PACK_EXPAND_WIDGET, 0);
157 
158     browsePathCompletion = Glib::RefPtr<Gtk::EntryCompletion>(new DirCompletion());
159     BrowsePath->set_completion(browsePathCompletion);
160     browsePathCompletion->set_minimum_key_length(1);
161 
162     BrowsePath->signal_activate().connect (sigc::mem_fun(*this, &FileCatalog::buttonBrowsePathPressed)); //respond to the Enter key
163     BrowsePath->signal_key_press_event().connect(sigc::mem_fun(*this, &FileCatalog::BrowsePath_key_pressed));
164     BrowsePath->signal_changed().connect(sigc::mem_fun(*this, &FileCatalog::onBrowsePathChanged));
165 
166     //setup Query
167     iQueryClear = new RTImage("cancel-small.png");
168     Gtk::Label* labelQuery = Gtk::manage(new Gtk::Label(M("FILEBROWSER_QUERYLABEL")));
169     Query = Gtk::manage(new Gtk::Entry ()); // cannot use Gtk::manage here as FileCatalog::getFilter will fail on Query->get_text()
170     Query->set_text("");
171     Query->set_width_chars (20); // TODO !!! add this value to options?
172     Query->set_max_width_chars (20);
173     Query->set_tooltip_markup (M("FILEBROWSER_QUERYHINT"));
174     Gtk::HBox* hbQuery = Gtk::manage(new Gtk::HBox ());
175     buttonQueryClear = Gtk::manage(new Gtk::Button ());
176     buttonQueryClear->set_image (*iQueryClear);
177     buttonQueryClear->set_tooltip_markup (M("FILEBROWSER_QUERYBUTTONHINT"));
178     buttonQueryClear->set_relief (Gtk::RELIEF_NONE);
179     buttonQueryClear->signal_clicked().connect( sigc::mem_fun(*this, &FileCatalog::buttonQueryClearPressed) );
180     hbQuery->pack_start (*labelQuery, Gtk::PACK_SHRINK, 0);
181     hbQuery->pack_start (*Query, Gtk::PACK_SHRINK, 0);
182     hbQuery->pack_start (*buttonQueryClear, Gtk::PACK_SHRINK, 0);
183     hbToolBar1->pack_start (*hbQuery, Gtk::PACK_SHRINK, 0);
184 
185     Query->signal_activate().connect (sigc::mem_fun(*this, &FileCatalog::executeQuery)); //respond to the Enter key
186     Query->signal_key_press_event().connect(sigc::mem_fun(*this, &FileCatalog::Query_key_pressed));
187 
188     // if NOT a single row toolbar
189     if (!options.FileBrowserToolbarSingleRow) {
190         hbToolBar1STB = Gtk::manage(new MyScrolledToolbar());
191         hbToolBar1STB->set_name("FileBrowserQueryToolbar");
192         hbToolBar1STB->add(*hbToolBar1);
193         pack_start (*hbToolBar1STB, Gtk::PACK_SHRINK, 0);
194     }
195 
196     // setup button bar
197     buttonBar = Gtk::manage( new Gtk::HBox () );
198     buttonBar->set_name ("ToolBarPanelFileBrowser");
199     MyScrolledToolbar *stb = Gtk::manage(new MyScrolledToolbar());
200     stb->set_name("FileBrowserIconToolbar");
201     stb->add(*buttonBar);
202     pack_start (*stb, Gtk::PACK_SHRINK);
203 
204     tbLeftPanel_1 = new Gtk::ToggleButton ();
205     iLeftPanel_1_Show = new RTImage("panel-to-right.png");
206     iLeftPanel_1_Hide = new RTImage("panel-to-left.png");
207 
208     tbLeftPanel_1->set_relief(Gtk::RELIEF_NONE);
209     tbLeftPanel_1->set_active (true);
210     tbLeftPanel_1->set_tooltip_markup (M("MAIN_TOOLTIP_SHOWHIDELP1"));
211     tbLeftPanel_1->set_image (*iLeftPanel_1_Hide);
212     tbLeftPanel_1->signal_toggled().connect( sigc::mem_fun(*this, &FileCatalog::tbLeftPanel_1_toggled) );
213     buttonBar->pack_start (*tbLeftPanel_1, Gtk::PACK_SHRINK);
214 
215     vSepiLeftPanel = new Gtk::VSeparator ();
216     buttonBar->pack_start (*vSepiLeftPanel, Gtk::PACK_SHRINK);
217 
218     iFilterClear = new RTImage ("filter-clear.png");
219     igFilterClear = new RTImage ("filter.png");
220     bFilterClear = Gtk::manage(new Gtk::ToggleButton ());
221     bFilterClear->set_active (true);
222     bFilterClear->set_image(*iFilterClear);// (*Gtk::manage(new RTImage ("filter-clear.png")));
223     bFilterClear->set_relief (Gtk::RELIEF_NONE);
224     bFilterClear->set_tooltip_markup (M("FILEBROWSER_SHOWDIRHINT"));
225     bFilterClear->signal_button_press_event().connect (sigc::mem_fun(*this, &FileCatalog::capture_event), false);
226     bCateg[0] = bFilterClear->signal_toggled().connect (sigc::bind(sigc::mem_fun(*this, &FileCatalog::categoryButtonToggled), bFilterClear, true));
227     buttonBar->pack_start (*bFilterClear, Gtk::PACK_SHRINK);
228     buttonBar->pack_start (*Gtk::manage(new Gtk::VSeparator), Gtk::PACK_SHRINK);
229 
230     fltrVbox1 = Gtk::manage (new Gtk::VBox());
231     fltrRankbox = Gtk::manage (new Gtk::HBox());
232     fltrRankbox->get_style_context()->add_class("smallbuttonbox");
233     fltrLabelbox = Gtk::manage (new Gtk::HBox());
234     fltrLabelbox->get_style_context()->add_class("smallbuttonbox");
235 
236     iUnRanked = new RTImage ("star-gold-hollow-small.png");
237     igUnRanked = new RTImage ("star-hollow-small.png");
238     bUnRanked = Gtk::manage( new Gtk::ToggleButton () );
239     bUnRanked->get_style_context()->add_class("smallbutton");
240     bUnRanked->set_active (false);
241     bUnRanked->set_image (*igUnRanked);
242     bUnRanked->set_relief (Gtk::RELIEF_NONE);
243     bUnRanked->set_tooltip_markup (M("FILEBROWSER_SHOWUNRANKHINT"));
244     bCateg[1] = bUnRanked->signal_toggled().connect (sigc::bind(sigc::mem_fun(*this, &FileCatalog::categoryButtonToggled), bUnRanked, true));
245     fltrRankbox->pack_start (*bUnRanked, Gtk::PACK_SHRINK);
246     bUnRanked->signal_button_press_event().connect (sigc::mem_fun(*this, &FileCatalog::capture_event), false);
247 
248     for (int i = 0; i < 5; i++) {
249         iranked[i] = new RTImage ("star-gold-small.png");
250         igranked[i] = new RTImage ("star-small.png");
251         iranked[i]->show ();
252         igranked[i]->show ();
253         bRank[i] = Gtk::manage( new Gtk::ToggleButton () );
254         bRank[i]->get_style_context()->add_class("smallbutton");
255         bRank[i]->set_image (*igranked[i]);
256         bRank[i]->set_relief (Gtk::RELIEF_NONE);
257         fltrRankbox->pack_start (*bRank[i], Gtk::PACK_SHRINK);
258         bCateg[i + 2] = bRank[i]->signal_toggled().connect (sigc::bind(sigc::mem_fun(*this, &FileCatalog::categoryButtonToggled), bRank[i], true));
259         bRank[i]->signal_button_press_event().connect (sigc::mem_fun(*this, &FileCatalog::capture_event), false);
260     }
261 
262     // Toolbar
263     // Similar image arrays in filebrowser.cc
264     std::array<std::string, 6> clabelActiveIcons = {"circle-gray-small.png", "circle-red-small.png", "circle-yellow-small.png", "circle-green-small.png", "circle-blue-small.png", "circle-purple-small.png"};
265     std::array<std::string, 6> clabelInactiveIcons = {"circle-empty-gray-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"};
266 
267     iUnCLabeled = new RTImage(clabelActiveIcons[0]);
268     igUnCLabeled = new RTImage(clabelInactiveIcons[0]);
269     bUnCLabeled = Gtk::manage(new Gtk::ToggleButton());
270     bUnCLabeled->get_style_context()->add_class("smallbutton");
271     bUnCLabeled->set_active(false);
272     bUnCLabeled->set_image(*igUnCLabeled);
273     bUnCLabeled->set_relief(Gtk::RELIEF_NONE);
274     bUnCLabeled->set_tooltip_markup(M("FILEBROWSER_SHOWUNCOLORHINT"));
275     bCateg[7] = bUnCLabeled->signal_toggled().connect (sigc::bind(sigc::mem_fun(*this, &FileCatalog::categoryButtonToggled), bUnCLabeled, true));
276     fltrLabelbox->pack_start(*bUnCLabeled, Gtk::PACK_SHRINK);
277     bUnCLabeled->signal_button_press_event().connect (sigc::mem_fun(*this, &FileCatalog::capture_event), false);
278 
279     for (int i = 0; i < 5; i++) {
280         iCLabeled[i] = new RTImage(clabelActiveIcons[i+1]);
281         igCLabeled[i] = new RTImage(clabelInactiveIcons[i+1]);
282         iCLabeled[i]->show();
283         igCLabeled[i]->show();
284         bCLabel[i] = Gtk::manage(new Gtk::ToggleButton());
285         bCLabel[i]->get_style_context()->add_class("smallbutton");
286         bCLabel[i]->set_image(*igCLabeled[i]);
287         bCLabel[i]->set_relief(Gtk::RELIEF_NONE);
288         fltrLabelbox->pack_start(*bCLabel[i], Gtk::PACK_SHRINK);
289         bCateg[i + 8] = bCLabel[i]->signal_toggled().connect (sigc::bind(sigc::mem_fun(*this, &FileCatalog::categoryButtonToggled), bCLabel[i], true));
290         bCLabel[i]->signal_button_press_event().connect (sigc::mem_fun(*this, &FileCatalog::capture_event), false);
291     }
292 
293     fltrVbox1->pack_start (*fltrRankbox, Gtk::PACK_SHRINK, 0);
294     fltrVbox1->pack_start (*fltrLabelbox, Gtk::PACK_SHRINK, 0);
295     buttonBar->pack_start (*fltrVbox1, Gtk::PACK_SHRINK);
296 
297     bRank[0]->set_tooltip_markup (M("FILEBROWSER_SHOWRANK1HINT"));
298     bRank[1]->set_tooltip_markup (M("FILEBROWSER_SHOWRANK2HINT"));
299     bRank[2]->set_tooltip_markup (M("FILEBROWSER_SHOWRANK3HINT"));
300     bRank[3]->set_tooltip_markup (M("FILEBROWSER_SHOWRANK4HINT"));
301     bRank[4]->set_tooltip_markup (M("FILEBROWSER_SHOWRANK5HINT"));
302 
303     bCLabel[0]->set_tooltip_markup (M("FILEBROWSER_SHOWCOLORLABEL1HINT"));
304     bCLabel[1]->set_tooltip_markup (M("FILEBROWSER_SHOWCOLORLABEL2HINT"));
305     bCLabel[2]->set_tooltip_markup (M("FILEBROWSER_SHOWCOLORLABEL3HINT"));
306     bCLabel[3]->set_tooltip_markup (M("FILEBROWSER_SHOWCOLORLABEL4HINT"));
307     bCLabel[4]->set_tooltip_markup (M("FILEBROWSER_SHOWCOLORLABEL5HINT"));
308 
309     buttonBar->pack_start (*Gtk::manage(new Gtk::VSeparator), Gtk::PACK_SHRINK);
310 
311     fltrVbox2 = Gtk::manage (new Gtk::VBox());
312     fltrEditedBox = Gtk::manage (new Gtk::HBox());
313     fltrEditedBox->get_style_context()->add_class("smallbuttonbox");
314     fltrRecentlySavedBox = Gtk::manage (new Gtk::HBox());
315     fltrRecentlySavedBox->get_style_context()->add_class("smallbuttonbox");
316 
317     // bEdited
318     // TODO The "g" variant was the more transparent variant of the icon, used
319     // when the button was not toggled. Simplify this, change to ordinary
320     // togglebutton, use CSS for opacity change.
321     iEdited[0] = new RTImage ("tick-hollow-small.png");
322     igEdited[0] = new RTImage ("tick-hollow-small.png");
323     iEdited[1] = new RTImage ("tick-small.png");
324     igEdited[1] = new RTImage ("tick-small.png");
325 
326     for (int i = 0; i < 2; i++) {
327         iEdited[i]->show ();
328         bEdited[i] = Gtk::manage(new Gtk::ToggleButton ());
329         bEdited[i]->get_style_context()->add_class("smallbutton");
330         bEdited[i]->set_active (false);
331         bEdited[i]->set_image (*igEdited[i]);
332         bEdited[i]->set_relief (Gtk::RELIEF_NONE);
333         fltrEditedBox->pack_start (*bEdited[i], Gtk::PACK_SHRINK);
334         //13, 14
335         bCateg[i + 13] = bEdited[i]->signal_toggled().connect (sigc::bind(sigc::mem_fun(*this, &FileCatalog::categoryButtonToggled), bEdited[i], true));
336         bEdited[i]->signal_button_press_event().connect (sigc::mem_fun(*this, &FileCatalog::capture_event), false);
337     }
338 
339     bEdited[0]->set_tooltip_markup (M("FILEBROWSER_SHOWEDITEDNOTHINT"));
340     bEdited[1]->set_tooltip_markup (M("FILEBROWSER_SHOWEDITEDHINT"));
341 
342     // RecentlySaved
343     // TODO The "g" variant was the more transparent variant of the icon, used
344     // when the button was not toggled. Simplify this, change to ordinary
345     // togglebutton, use CSS for opacity change.
346     iRecentlySaved[0] = new RTImage ("saved-no-small.png");
347     igRecentlySaved[0] = new RTImage ("saved-no-small.png");
348     iRecentlySaved[1] = new RTImage ("saved-yes-small.png");
349     igRecentlySaved[1] = new RTImage ("saved-yes-small.png");
350 
351     for (int i = 0; i < 2; i++) {
352         iRecentlySaved[i]->show ();
353         bRecentlySaved[i] = Gtk::manage(new Gtk::ToggleButton ());
354         bRecentlySaved[i]->get_style_context()->add_class("smallbutton");
355         bRecentlySaved[i]->set_active (false);
356         bRecentlySaved[i]->set_image (*igRecentlySaved[i]);
357         bRecentlySaved[i]->set_relief (Gtk::RELIEF_NONE);
358         fltrRecentlySavedBox->pack_start (*bRecentlySaved[i], Gtk::PACK_SHRINK);
359         //15, 16
360         bCateg[i + 15] = bRecentlySaved[i]->signal_toggled().connect (sigc::bind(sigc::mem_fun(*this, &FileCatalog::categoryButtonToggled), bRecentlySaved[i], true));
361         bRecentlySaved[i]->signal_button_press_event().connect (sigc::mem_fun(*this, &FileCatalog::capture_event), false);
362     }
363 
364     bRecentlySaved[0]->set_tooltip_markup (M("FILEBROWSER_SHOWRECENTLYSAVEDNOTHINT"));
365     bRecentlySaved[1]->set_tooltip_markup (M("FILEBROWSER_SHOWRECENTLYSAVEDHINT"));
366 
367     fltrVbox2->pack_start (*fltrEditedBox, Gtk::PACK_SHRINK, 0);
368     fltrVbox2->pack_start (*fltrRecentlySavedBox, Gtk::PACK_SHRINK, 0);
369     buttonBar->pack_start (*fltrVbox2, Gtk::PACK_SHRINK);
370 
371     buttonBar->pack_start (*Gtk::manage(new Gtk::VSeparator), Gtk::PACK_SHRINK);
372 
373     // Trash
374     iTrashShowEmpty = new RTImage("trash-empty-show.png") ;
375     iTrashShowFull  = new RTImage("trash-full-show.png") ;
376 
377     bTrash = Gtk::manage( new Gtk::ToggleButton () );
378     bTrash->set_image (*iTrashShowEmpty);
379     bTrash->set_relief (Gtk::RELIEF_NONE);
380     bTrash->set_tooltip_markup (M("FILEBROWSER_SHOWTRASHHINT"));
381     bCateg[17] = bTrash->signal_toggled().connect (sigc::bind(sigc::mem_fun(*this, &FileCatalog::categoryButtonToggled), bTrash, true));
382     bTrash->signal_button_press_event().connect (sigc::mem_fun(*this, &FileCatalog::capture_event), false);
383 
384     iNotTrash = new RTImage("trash-hide-deleted.png") ;
385     iOriginal = new RTImage("filter-original.png");
386 
387     bNotTrash = Gtk::manage( new Gtk::ToggleButton () );
388     bNotTrash->set_image (*iNotTrash);
389     bNotTrash->set_relief (Gtk::RELIEF_NONE);
390     bNotTrash->set_tooltip_markup (M("FILEBROWSER_SHOWNOTTRASHHINT"));
391     bCateg[18] = bNotTrash->signal_toggled().connect (sigc::bind(sigc::mem_fun(*this, &FileCatalog::categoryButtonToggled), bNotTrash, true));
392     bNotTrash->signal_button_press_event().connect (sigc::mem_fun(*this, &FileCatalog::capture_event), false);
393 
394     bOriginal = Gtk::manage( new Gtk::ToggleButton () );
395     bOriginal->set_image (*iOriginal);
396     bOriginal->set_tooltip_markup (M("FILEBROWSER_SHOWORIGINALHINT"));
397     bOriginal->set_relief (Gtk::RELIEF_NONE);
398     bCateg[19] = bOriginal->signal_toggled().connect (sigc::bind(sigc::mem_fun(*this, &FileCatalog::categoryButtonToggled), bOriginal, true));
399     bOriginal->signal_button_press_event().connect (sigc::mem_fun(*this, &FileCatalog::capture_event), false);
400 
401     buttonBar->pack_start (*bTrash, Gtk::PACK_SHRINK);
402     buttonBar->pack_start (*bNotTrash, Gtk::PACK_SHRINK);
403     buttonBar->pack_start (*bOriginal, Gtk::PACK_SHRINK);
404     buttonBar->pack_start (*Gtk::manage(new Gtk::VSeparator), Gtk::PACK_SHRINK);
405     fileBrowser->trash_changed().connect( sigc::mem_fun(*this, &FileCatalog::trashChanged) );
406 
407     // 0  - bFilterClear
408     // 1  - bUnRanked
409     // 2  - bRank[0]
410     // 3  - bRank[1]
411     // 4  - bRank[2]
412     // 5  - bRank[3]
413     // 6  - bRank[4]
414     // 7  - bUnCLabeled
415     // 8  - bCLabel[0]
416     // 9  - bCLabel[1]
417     // 10 - bCLabel[2]
418     // 11 - bCLabel[3]
419     // 12 - bCLabel[4]
420     // 13 - bEdited[0]
421     // 14 - bEdited[1]
422     // 15 - bRecentlySaved[0]
423     // 16 - bRecentlySaved[1]
424     // 17 - bTrash
425     // 18 - bNotTrash
426     // 19 - bOriginal
427 
428     categoryButtons[0] = bFilterClear;
429     categoryButtons[1] = bUnRanked;
430 
431     for (int i = 0; i < 5; i++) {
432         categoryButtons[i + 2] = bRank[i];
433     }
434 
435     categoryButtons[7] = bUnCLabeled;
436 
437     for (int i = 0; i < 5; i++) {
438         categoryButtons[i + 8] = bCLabel[i];
439     }
440 
441     for (int i = 0; i < 2; i++) {
442         categoryButtons[i + 13] = bEdited[i];
443     }
444 
445     for (int i = 0; i < 2; i++) {
446         categoryButtons[i + 15] = bRecentlySaved[i];
447     }
448 
449     categoryButtons[17] = bTrash;
450     categoryButtons[18] = bNotTrash;
451     categoryButtons[19] = bOriginal;
452 
453     exifInfo = Gtk::manage(new Gtk::ToggleButton ());
454     exifInfo->set_image (*Gtk::manage(new RTImage ("info.png")));
455     exifInfo->set_relief (Gtk::RELIEF_NONE);
456     exifInfo->set_tooltip_markup (M("FILEBROWSER_SHOWEXIFINFO"));
457     exifInfo->set_active( options.showFileNames );
458     exifInfo->signal_toggled().connect(sigc::mem_fun(*this, &FileCatalog::exifInfoButtonToggled));
459     buttonBar->pack_start (*exifInfo, Gtk::PACK_SHRINK);
460 
461     // thumbnail zoom
462     Gtk::HBox* zoomBox = Gtk::manage( new Gtk::HBox () );
463     zoomInButton  = Gtk::manage(  new Gtk::Button () );
464     zoomInButton->set_image (*Gtk::manage(new RTImage ("magnifier-plus.png")));
465     zoomInButton->signal_pressed().connect (sigc::mem_fun(*this, &FileCatalog::zoomIn));
466     zoomInButton->set_relief (Gtk::RELIEF_NONE);
467     zoomInButton->set_tooltip_markup (M("FILEBROWSER_ZOOMINHINT"));
468     zoomBox->pack_end (*zoomInButton, Gtk::PACK_SHRINK);
469     zoomOutButton  = Gtk::manage( new Gtk::Button () );
470     zoomOutButton->set_image (*Gtk::manage(new RTImage ("magnifier-minus.png")));
471     zoomOutButton->signal_pressed().connect (sigc::mem_fun(*this, &FileCatalog::zoomOut));
472     zoomOutButton->set_relief (Gtk::RELIEF_NONE);
473     zoomOutButton->set_tooltip_markup (M("FILEBROWSER_ZOOMOUTHINT"));
474     zoomBox->pack_end (*zoomOutButton, Gtk::PACK_SHRINK);
475 
476     buttonBar->pack_start (*zoomBox, Gtk::PACK_SHRINK);
477     buttonBar->pack_start (*Gtk::manage(new Gtk::VSeparator), Gtk::PACK_SHRINK);
478 
479     {
480         thumbOrder = Gtk::manage(new Gtk::MenuButton());
481         thumbOrder->set_image(*Gtk::manage(new RTImage("az-sort.png")));
482         Gtk::Menu *menu = Gtk::manage(new Gtk::Menu());
483         thumbOrder->set_menu(*menu);
484 
485         const auto on_activate =
486             [&]() -> void
487             {
488                 for (size_t i = 0; i < thumbOrderItems.size(); ++i) {
489                     auto l = static_cast<Gtk::Label *>(thumbOrderItems[i]->get_children()[0]);
490                     l->set_markup(thumbOrderLabels[i]);
491                 }
492                 int sel = thumbOrder->get_menu()->property_active();
493                 auto mi = thumbOrder->get_menu()->get_active();
494                 if (mi) {
495                     thumbOrder->set_tooltip_text(M("FILEBROWSER_SORT_LABEL") + ": " + thumbOrderLabels[sel]);
496                     auto l = static_cast<Gtk::Label *>(mi->get_children()[0]);
497                     l->set_markup("<b>" + thumbOrderLabels[sel] + "</b>");
498                 }
499                 options.thumbnailOrder = Options::ThumbnailOrder(sel);
500                 fileBrowser->sortThumbnails();
501             };
502 
503         const auto addItem =
504             [&](const Glib::ustring &lbl) -> void
505             {
506                 Gtk::MenuItem *mi = Gtk::manage(new Gtk::MenuItem(lbl));
507                 menu->append(*mi);
508                 thumbOrderItems.push_back(mi);
509                 thumbOrderLabels.push_back(lbl);
510                 mi->signal_activate().connect(sigc::slot<void>(on_activate));
511             };
512         addItem(M("FILEBROWSER_SORT_FILENAME"));
513         addItem(M("FILEBROWSER_SORT_DATE"));
514         addItem(M("FILEBROWSER_SORT_DATE_REV"));
515         addItem(M("FILEBROWSER_SORT_MODTIME"));
516         addItem(M("FILEBROWSER_SORT_MODTIME_REV"));
517         addItem(M("FILEBROWSER_SORT_PROCTIME"));
518         addItem(M("FILEBROWSER_SORT_PROCTIME_REV"));
519         menu->show_all_children();
520         menu->set_active(int(options.thumbnailOrder));
521         on_activate();
522         thumbOrder->set_relief(Gtk::RELIEF_NONE);
523         buttonBar->pack_start(*thumbOrder, Gtk::PACK_SHRINK);
524     }
525 
526 
527     //iRightArrow = new RTImage("right.png");
528     //iRightArrow_red = new RTImage("right_red.png");
529 
530     // if it IS a single row toolbar
531     if (options.FileBrowserToolbarSingleRow) {
532         buttonBar->pack_start (*hbToolBar1, Gtk::PACK_EXPAND_WIDGET, 0);
533     }
534 
535     tbRightPanel_1 = new Gtk::ToggleButton ();
536     iRightPanel_1_Show = new RTImage("panel-to-left.png");
537     iRightPanel_1_Hide = new RTImage("panel-to-right.png");
538 
539     tbRightPanel_1->set_relief(Gtk::RELIEF_NONE);
540     tbRightPanel_1->set_active (true);
541     tbRightPanel_1->set_tooltip_markup (M("MAIN_TOOLTIP_SHOWHIDERP1"));
542     tbRightPanel_1->set_image (*iRightPanel_1_Hide);
543     tbRightPanel_1->signal_toggled().connect( sigc::mem_fun(*this, &FileCatalog::tbRightPanel_1_toggled) );
544     buttonBar->pack_end (*tbRightPanel_1, Gtk::PACK_SHRINK);
545 
546     // add default panel
547     hBox = Gtk::manage( new Gtk::HBox () );
548     hBox->show ();
549     hBox->pack_end (*fileBrowser);
550     hBox->set_name ("FilmstripPanel");
551     fileBrowser->applyFilter (getFilter()); // warning: can call this only after all objects used in getFilter (e.g. Query) are instantiated
552     //printf("FileCatalog::FileCatalog  fileBrowser->applyFilter (getFilter())\n");
553     pack_start (*hBox);
554 
555     enabled = true;
556 
557     lastScrollPos = 0;
558 
559     for (int i = 0; i < 18; i++) {
560         hScrollPos[i] = 0;
561         vScrollPos[i] = 0;
562     }
563 
564     selectedDirectory = "";
565 }
566 
~FileCatalog()567 FileCatalog::~FileCatalog()
568 {
569     idle_register.destroy();
570 
571     for (int i = 0; i < 5; i++) {
572         delete iranked[i];
573         delete igranked[i];
574         delete iCLabeled[i];
575         delete igCLabeled[i];
576     }
577 
578     for (int i = 0; i < 2; i++) {
579         delete iEdited[i];
580         delete igEdited[i];
581         delete iRecentlySaved[i];
582         delete igRecentlySaved[i];
583     }
584 
585     delete iFilterClear;
586     delete igFilterClear;
587     delete iUnRanked;
588     delete igUnRanked;
589     delete iUnCLabeled;
590     delete igUnCLabeled;
591     delete iTrashShowEmpty;
592     delete iTrashShowFull;
593     delete iNotTrash;
594     delete iOriginal;
595     delete iRefreshWhite;
596     delete iRefreshRed;
597     delete iQueryClear;
598     delete iLeftPanel_1_Show;
599     delete iLeftPanel_1_Hide;
600     delete iRightPanel_1_Show;
601     delete iRightPanel_1_Hide;
602 }
603 
capture_event(GdkEventButton * event)604 bool FileCatalog::capture_event(GdkEventButton* event)
605 {
606     // need to record modifiers on the button press, because signal_toggled does not pass the event.
607     modifierKey = event->state;
608     return false;
609 }
610 
exifInfoButtonToggled()611 void FileCatalog::exifInfoButtonToggled()
612 {
613     if (inTabMode) {
614         options.filmStripShowFileNames =  exifInfo->get_active();
615     } else {
616         options.showFileNames =  exifInfo->get_active();
617     }
618 
619     fileBrowser->refreshThumbImages ();
620     refreshHeight();
621 }
622 
on_realize()623 void FileCatalog::on_realize()
624 {
625 
626     Gtk::VBox::on_realize();
627     Pango::FontDescription fontd = get_pango_context()->get_font_description ();
628     fileBrowser->get_pango_context()->set_font_description (fontd);
629 //    batchQueue->get_pango_context()->set_font_description (fontd);
630 }
631 
closeDir()632 void FileCatalog::closeDir ()
633 {
634 
635     if (filterPanel) {
636         filterPanel->set_sensitive (false);
637     }
638 
639     // if (exportPanel) {
640     //     exportPanel->set_sensitive (false);
641     // }
642 
643     if (dirMonitor) {
644         dirMonitor->cancel ();
645     }
646 
647     // ignore old requests
648     ++selectedDirectoryId;
649     refresh_counter_ = 1;
650 
651     // terminate thumbnail preview loading
652     previewLoader->removeAllJobs ();
653 
654     // terminate thumbnail updater
655     thumbImageUpdater->removeAllJobs ();
656 
657     // remove entries
658     selectedDirectory = "";
659     fileBrowser->close ();
660     fileNameList.clear ();
661 
662     {
663         MyMutex::MyLock lock(dirEFSMutex);
664         dirEFS.clear ();
665     }
666     hasValidCurrentEFS = false;
667     redrawAll ();
668 }
669 
670 
671 namespace {
672 
673 class FileSorter {
674 public:
FileSorter(Options::ThumbnailOrder order)675     FileSorter(Options::ThumbnailOrder order): order_(order) {}
676 
operator ()(const Glib::ustring & a,const Glib::ustring & b) const677     bool operator()(const Glib::ustring &a, const Glib::ustring &b) const
678     {
679         switch (order_) {
680         case Options::ThumbnailOrder::DATE:
681         case Options::ThumbnailOrder::DATE_REV:
682             return lt_date(a, b, order_ == Options::ThumbnailOrder::DATE_REV);
683             break;
684         case Options::ThumbnailOrder::MODTIME:
685         case Options::ThumbnailOrder::MODTIME_REV:
686             return lt_modtime(a, b, order_ == Options::ThumbnailOrder::MODTIME_REV);
687             break;
688         case Options::ThumbnailOrder::PROCTIME:
689         case Options::ThumbnailOrder::PROCTIME_REV:
690             return lt_proctime(a, b, order_ == Options::ThumbnailOrder::PROCTIME_REV);
691             break;
692         case Options::ThumbnailOrder::FILENAME:
693         default:
694             return a < b;
695         }
696     }
697 
698 private:
lt_date(const Glib::ustring & a,const Glib::ustring & b,bool reverse) const699     bool lt_date(const Glib::ustring &a, const Glib::ustring &b, bool reverse) const
700     {
701         try {
702             // rtengine::FramesData ma(a);
703             // rtengine::FramesData mb(b);
704             // auto ta = ma.getDateTimeAsTS();
705             // auto tb = mb.getDateTimeAsTS();
706             auto ta = get_date(a);
707             auto tb = get_date(b);
708             if (ta == tb) {
709                 return a < b;
710             }
711             return (ta < tb) == !reverse;
712         } catch (std::exception &) {
713             return a < b;
714         }
715     }
716 
lt_modtime(const Glib::ustring & a,const Glib::ustring & b,bool reverse) const717     bool lt_modtime(const Glib::ustring &a, const Glib::ustring &b, bool reverse) const
718     {
719         auto ia = Gio::File::create_for_path(a)->query_info(G_FILE_ATTRIBUTE_TIME_MODIFIED);
720         auto ib = Gio::File::create_for_path(b)->query_info(G_FILE_ATTRIBUTE_TIME_MODIFIED);
721         auto ta = ia->modification_time();
722         auto tb = ib->modification_time();
723         if (ta == tb) {
724             return a < b;
725         }
726         return (ta < tb) == !reverse;
727     }
728 
lt_proctime(const Glib::ustring & a,const Glib::ustring & b,bool reverse) const729     bool lt_proctime(const Glib::ustring &a, const Glib::ustring &b, bool reverse) const
730     {
731         auto fa = options.getParamFile(a);
732         auto fb = options.getParamFile(b);
733 
734         bool has_a = Glib::file_test(fa, Glib::FILE_TEST_EXISTS);
735         bool has_b = Glib::file_test(fb, Glib::FILE_TEST_EXISTS);
736 
737         if (has_a != has_b) {
738             return reverse ? has_a : has_b;
739         } else if (has_a) {
740             auto ia = Gio::File::create_for_path(fa)->query_info(G_FILE_ATTRIBUTE_TIME_MODIFIED);
741             auto ib = Gio::File::create_for_path(fb)->query_info(G_FILE_ATTRIBUTE_TIME_MODIFIED);
742             auto ta = ia->modification_time();
743             auto tb = ib->modification_time();
744             if (ta == tb) {
745                 return a < b;
746             }
747             return (ta < tb) == !reverse;
748         } else {
749             return a < b;
750         }
751     }
752 
get_date(const Glib::ustring & us) const753     time_t get_date(const Glib::ustring &us) const
754     {
755         CacheImageData d;
756         if (cacheMgr->getImageData(us, d)) {
757             if (!d.supported) {
758                 return time_t(-1);
759             } else {
760                 return d.getDateTimeAsTS();
761             }
762         } else {
763             return time_t(-1);
764         }
765     }
766 
767     Options::ThumbnailOrder order_;
768 };
769 
770 
771 } // namespace
772 
getFileList()773 std::vector<Glib::ustring> FileCatalog::getFileList()
774 {
775 
776     std::vector<Glib::ustring> names;
777 
778     const std::set<std::string>& extensions = options.parsedExtensionsSet;
779 
780     try {
781 
782         const auto dir = Gio::File::create_for_path(selectedDirectory);
783 
784         auto enumerator = dir->enumerate_children("standard::name,standard::type,standard::is-hidden");
785 
786         while (true) {
787             try {
788                 const auto file = enumerator->next_file();
789                 if (!file) {
790                     break;
791                 }
792 
793                 if (file->get_file_type() == Gio::FILE_TYPE_DIRECTORY) {
794                     continue;
795                 }
796 
797                 if (!options.fbShowHidden && file->is_hidden()) {
798                     continue;
799                 }
800 
801                 const Glib::ustring fname = file->get_name();
802                 const auto lastdot = fname.find_last_of('.');
803 
804                 if (lastdot >= fname.length() - 1) {
805                     continue;
806                 }
807 
808                 if (extensions.find(fname.substr(lastdot + 1).lowercase()) == extensions.end()) {
809                     continue;
810                 }
811 
812                 names.push_back(Glib::build_filename(selectedDirectory, fname));
813             } catch (Glib::Exception& exception) {
814                 if (options.rtSettings.verbose) {
815                     std::cerr << exception.what() << std::endl;
816                 }
817             }
818         }
819 
820     } catch (Glib::Exception& exception) {
821 
822         if (options.rtSettings.verbose) {
823             std::cerr << "Failed to list directory \"" << selectedDirectory << "\": " << exception.what() << std::endl;
824         }
825 
826     }
827 
828     std::sort(names.begin(), names.end(), FileSorter(options.thumbnailOrder));
829     return names;
830 }
831 
dirSelected(const Glib::ustring & dirname,const Glib::ustring & openfile)832 void FileCatalog::dirSelected (const Glib::ustring& dirname, const Glib::ustring& openfile)
833 {
834 
835     try {
836         const Glib::RefPtr<Gio::File> dir = Gio::File::create_for_path(dirname);
837 
838         if (!dir) {
839             return;
840         }
841 
842         closeDir();
843         previewsToLoad = 0;
844         previewsLoaded = 0;
845 
846         // if openfile exists, we have to open it first (it is a command line argument)
847         if (!openfile.empty()) {
848             addAndOpenFile (openfile);
849         }
850 
851         selectedDirectory = dir->get_parse_name();
852 
853         BrowsePath->set_text(selectedDirectory);
854         buttonBrowsePath->set_image(*iRefreshWhite);
855         fileNameList = getFileList();
856 
857         for (unsigned int i = 0; i < fileNameList.size(); i++) {
858             if (openfile.empty() || fileNameList[i] != openfile) { // if we opened a file at the beginning don't add it again
859                 addFile(fileNameList[i]);
860             }
861         }
862 
863         _refreshProgressBar ();
864 
865         if (previewsToLoad == 0) {
866             filepanel->loadingThumbs(M("PROGRESSBAR_NOIMAGES"), 0);
867         } else {
868             filepanel->loadingThumbs(M("PROGRESSBAR_LOADINGTHUMBS"), 0);
869         }
870 
871         dirMonitor = dir->monitor_directory ();
872         dirMonitor->signal_changed().connect (sigc::bind(sigc::mem_fun(*this, &FileCatalog::on_dir_changed), false));
873     } catch (Glib::Exception& ex) {
874         std::cout << ex.what();
875     }
876 }
877 
enableTabMode(bool enable)878 void FileCatalog::enableTabMode(bool enable)
879 {
880     inTabMode = enable;
881 
882     if (enable) {
883         if (options.showFilmStripToolBar) {
884             showToolBar();
885         } else {
886             hideToolBar();
887         }
888 
889         exifInfo->set_active( options.filmStripShowFileNames );
890 
891     } else {
892         buttonBar->show();
893         hbToolBar1->show();
894         if (hbToolBar1STB) {
895             hbToolBar1STB->show();
896         }
897         exifInfo->set_active( options.showFileNames );
898     }
899 
900     fileBrowser->enableTabMode(inTabMode);
901 
902     redrawAll();
903 }
904 
_refreshProgressBar()905 void FileCatalog::_refreshProgressBar ()
906 {
907     // In tab mode, no progress bar at all
908     // Also mention that this progress bar only measures the FIRST pass (quick thumbnails)
909     // The second, usually longer pass is done multithreaded down in the single entries and is NOT measured by this
910     if (!inTabMode && (!previewsToLoad || std::floor(100.f * previewsLoaded / previewsToLoad) != std::floor(100.f * (previewsLoaded - 1) / previewsToLoad))) {
911         GThreadLock lock; // All GUI access from idle_add callbacks or separate thread HAVE to be protected
912         Gtk::Notebook *nb = (Gtk::Notebook *)(filepanel->get_parent());
913         Gtk::Grid* grid = Gtk::manage(new Gtk::Grid());
914         Gtk::Label *label = nullptr;
915 
916         int tot = previewsToLoad ? previewsToLoad : previewsLoaded;
917         grid->attach_next_to(*Gtk::manage(new RTImage("folder-closed.png")), options.mainNBVertical ? Gtk::POS_TOP : Gtk::POS_RIGHT, 1, 1);
918         int filteredCount = fileBrowser->getNumFiltered() < 0 ? tot : min(fileBrowser->getNumFiltered(), tot);
919 
920         label = Gtk::manage(new Gtk::Label(M("MAIN_FRAME_FILEBROWSER") +
921                                            (filteredCount != tot ? " [" + Glib::ustring::format(filteredCount) + "/" : " (")
922                                                + Glib::ustring::format(tot) +
923                                                (filteredCount != tot ? "]" : ")")));
924 
925         // if (!previewsToLoad) {
926         //     grid->attach_next_to(*Gtk::manage(new RTImage("folder-closed.png")), options.mainNBVertical ? Gtk::POS_TOP : Gtk::POS_RIGHT, 1, 1);
927         //     int filteredCount = min(fileBrowser->getNumFiltered(), previewsLoaded);
928 
929         //     label = Gtk::manage(new Gtk::Label(M("MAIN_FRAME_FILEBROWSER") +
930         //                                        (filteredCount != previewsLoaded ? " [" + Glib::ustring::format(filteredCount) + "/" : " (")
931         //                                        + Glib::ustring::format(previewsLoaded) +
932         //                                        (filteredCount != previewsLoaded ? "]" : ")")));
933         // } else {
934         //     grid->attach_next_to(*Gtk::manage(new RTImage("magnifier.png")), options.mainNBVertical ? Gtk::POS_TOP : Gtk::POS_RIGHT, 1, 1);
935         //     label = Gtk::manage(new Gtk::Label(M("MAIN_FRAME_FILEBROWSER") + " [" + Glib::ustring::format(std::fixed, std::setprecision(0), std::setw(3), (double)previewsLoaded / previewsToLoad * 100 ) + "%]" ));
936         //     filepanel->loadingThumbs("", (double)previewsLoaded / previewsToLoad);
937         // }
938 
939         if (options.mainNBVertical) {
940             label->set_angle(90);
941         }
942 
943         grid->attach_next_to(*label, options.mainNBVertical ? Gtk::POS_TOP : Gtk::POS_RIGHT, 1, 1);
944         grid->set_tooltip_markup(M("MAIN_FRAME_FILEBROWSER_TOOLTIP"));
945         grid->show_all();
946 
947         if (nb) {
948             nb->set_tab_label(*filepanel, *grid);
949         }
950     }
951 }
952 
previewReady(int dir_id,FileBrowserEntry * fdn)953 void FileCatalog::previewReady (int dir_id, FileBrowserEntry* fdn)
954 {
955 
956     if ( dir_id != selectedDirectoryId ) {
957         delete fdn;
958         return;
959     }
960 
961     // put it into the "full directory" browser
962     fileBrowser->addEntry (fdn);
963     if (!options.thumb_delay_update) {
964         if (++refresh_counter_ % 20 == 0) {
965             fileBrowser->enableThumbRefresh();
966         }
967     }
968 
969     // update exif filter settings (minimal & maximal values of exif tags, cameras, lenses, etc...)
970     const CacheImageData* cfs = fdn->thumbnail->getCacheImageData();
971 
972     {
973         MyMutex::MyLock lock(dirEFSMutex);
974 
975         if (cfs->exifValid) {
976             if (cfs->fnumber < dirEFS.fnumberFrom) {
977                 dirEFS.fnumberFrom = cfs->fnumber;
978             }
979 
980             if (cfs->fnumber > dirEFS.fnumberTo) {
981                 dirEFS.fnumberTo = cfs->fnumber;
982             }
983 
984             if (cfs->shutter < dirEFS.shutterFrom) {
985                 dirEFS.shutterFrom = cfs->shutter;
986             }
987 
988             if (cfs->shutter > dirEFS.shutterTo) {
989                 dirEFS.shutterTo = cfs->shutter;
990             }
991 
992             if (cfs->iso > 0 && cfs->iso < dirEFS.isoFrom) {
993                 dirEFS.isoFrom = cfs->iso;
994             }
995 
996             if (cfs->iso > 0 && cfs->iso > dirEFS.isoTo) {
997                 dirEFS.isoTo = cfs->iso;
998             }
999 
1000             if (cfs->focalLen < dirEFS.focalFrom) {
1001                 dirEFS.focalFrom = cfs->focalLen;
1002             }
1003 
1004             if (cfs->focalLen > dirEFS.focalTo) {
1005                 dirEFS.focalTo = cfs->focalLen;
1006             }
1007 
1008             //TODO: ass filters for HDR and PixelShift files
1009         }
1010 
1011         if (g_date_valid_dmy(int(cfs->day), GDateMonth(cfs->month), cfs->year)) {
1012             Glib::Date d(cfs->day, Glib::Date::Month(cfs->month), cfs->year);
1013             if (d < dirEFS.dateFrom) {
1014                 dirEFS.dateFrom = d;
1015             }
1016             if (d > dirEFS.dateTo) {
1017                 dirEFS.dateTo = d;
1018             }
1019         }
1020 
1021         dirEFS.filetypes.insert (cfs->filetype);
1022         dirEFS.cameras.insert (cfs->getCamera());
1023         dirEFS.lenses.insert (cfs->lens);
1024         dirEFS.expcomp.insert (cfs->expcomp);
1025     }
1026 
1027     previewsLoaded++;
1028 
1029     _refreshProgressBar();
1030 }
1031 
1032 // Called within GTK UI thread
previewsFinishedUI()1033 void FileCatalog::previewsFinishedUI ()
1034 {
1035 
1036     {
1037         GThreadLock lock; // All GUI access from idle_add callbacks or separate thread HAVE to be protected
1038         fileBrowser->enableThumbRefresh();
1039         //redrawAll ();
1040         previewsToLoad = 0;
1041 
1042         if (filterPanel) {
1043             filterPanel->set_sensitive (true);
1044 
1045             if ( !hasValidCurrentEFS ) {
1046                 MyMutex::MyLock lock(dirEFSMutex);
1047                 currentEFS = dirEFS;
1048                 filterPanel->setFilter ( dirEFS, true );
1049             } else {
1050                 filterPanel->setFilter ( currentEFS, false );
1051             }
1052         }
1053 
1054         // if (exportPanel) {
1055         //     exportPanel->set_sensitive (true);
1056         // }
1057 
1058         // restart anything that might have been loaded low quality
1059         fileBrowser->refreshQuickThumbImages();
1060         fileBrowser->applyFilter (getFilter());  // refresh total image count
1061         _refreshProgressBar();
1062     }
1063     filepanel->loadingThumbs(M("PROGRESSBAR_READY"), 0);
1064 
1065     if (!imageToSelect_fname.empty()) {
1066         fileBrowser->selectImage(imageToSelect_fname);
1067         imageToSelect_fname = "";
1068     }
1069 
1070     if (!refImageForOpen_fname.empty() && actionNextPrevious != NAV_NONE) {
1071         fileBrowser->openNextPreviousEditorImage(refImageForOpen_fname, actionNextPrevious);
1072         refImageForOpen_fname = "";
1073         actionNextPrevious = NAV_NONE;
1074     }
1075 
1076     // newly added item might have been already trashed in a previous session
1077     trashChanged();
1078 }
1079 
previewsFinished(int dir_id)1080 void FileCatalog::previewsFinished (int dir_id)
1081 {
1082 
1083     if ( dir_id != selectedDirectoryId ) {
1084         return;
1085     }
1086 
1087     if (!hasValidCurrentEFS) {
1088         MyMutex::MyLock lock(dirEFSMutex);
1089         currentEFS = dirEFS;
1090     }
1091 
1092     idle_register.add(
1093         [this]() -> bool
1094         {
1095             previewsFinishedUI();
1096             return false;
1097         }
1098     );
1099 }
1100 
setEnabled(bool e)1101 void FileCatalog::setEnabled (bool e)
1102 {
1103     enabled = e;
1104 }
1105 
redrawAll()1106 void FileCatalog::redrawAll ()
1107 {
1108     fileBrowser->queue_draw ();
1109 }
1110 
refreshThumbImages()1111 void FileCatalog::refreshThumbImages ()
1112 {
1113     fileBrowser->refreshThumbImages ();
1114 }
1115 
refreshHeight()1116 void FileCatalog::refreshHeight ()
1117 {
1118     int newHeight = fileBrowser->getEffectiveHeight();
1119 
1120     if (newHeight < 5) {  // This may occur if there's no thumbnail.
1121         int w, h;
1122         get_size_request(w, h);
1123         newHeight = h;
1124     }
1125 
1126     if (hbToolBar1STB && hbToolBar1STB->is_visible()) {
1127         newHeight += hbToolBar1STB->get_height();
1128     }
1129 
1130     if (buttonBar->is_visible()) {
1131         newHeight += buttonBar->get_height();
1132     }
1133 
1134     set_size_request(0, newHeight + 2); // HOMBRE: yeah, +2, there's always 2 pixels missing... sorry for this dirty hack O:)
1135 }
1136 
_openImage(const std::vector<Thumbnail * > & tmb)1137 void FileCatalog::_openImage(const std::vector<Thumbnail*>& tmb)
1138 {
1139     if (enabled && listener) {
1140         bool continueToLoad = true;
1141 
1142         for (size_t i = 0; i < tmb.size() && continueToLoad; i++) {
1143             // Open the image here, and stop if in Single Editor mode, or if an image couldn't
1144             // be opened, would it be because the file doesn't exist or because of lack of RAM
1145             if( !(listener->fileSelected (tmb[i])) && !options.tabbedUI ) {
1146                 continueToLoad = false;
1147             }
1148 
1149             tmb[i]->decreaseRef ();
1150         }
1151     }
1152 }
1153 
filterApplied()1154 void FileCatalog::filterApplied()
1155 {
1156     idle_register.add(
1157         [this]() -> bool
1158         {
1159             _refreshProgressBar();
1160             return false;
1161         }
1162     );
1163 }
1164 
openRequested(const std::vector<Thumbnail * > & tmb)1165 void FileCatalog::openRequested(const std::vector<Thumbnail*>& tmb)
1166 {
1167     for (const auto thumb : tmb) {
1168         thumb->increaseRef();
1169     }
1170 
1171     idle_register.add(
1172         [this, tmb]() -> bool
1173         {
1174             _openImage(tmb);
1175             return false;
1176         }
1177     );
1178 }
1179 
deleteRequested(const std::vector<FileBrowserEntry * > & tbe,bool inclBatchProcessed,bool onlySelected)1180 void FileCatalog::deleteRequested(const std::vector<FileBrowserEntry*>& tbe, bool inclBatchProcessed, bool onlySelected)
1181 {
1182     if (tbe.empty()) {
1183         return;
1184     }
1185 
1186     Gtk::MessageDialog msd (getToplevelWindow(this), M("FILEBROWSER_DELETEDIALOG_HEADER"), true, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO, true);
1187     if (onlySelected) {
1188         msd.set_secondary_text(Glib::ustring::compose (inclBatchProcessed ? M("FILEBROWSER_DELETEDIALOG_SELECTEDINCLPROC") : M("FILEBROWSER_DELETEDIALOG_SELECTED"), tbe.size()), true);
1189     } else {
1190         msd.set_secondary_text(Glib::ustring::compose (M("FILEBROWSER_DELETEDIALOG_ALL"), tbe.size()), true);
1191     }
1192 
1193     if (msd.run() == Gtk::RESPONSE_YES) {
1194         removeFromBatchQueue(tbe);
1195 
1196         for (unsigned int i = 0; i < tbe.size(); i++) {
1197             const auto fname = tbe[i]->filename;
1198             // remove from browser
1199             delete fileBrowser->delEntry (fname);
1200             // remove from cache
1201             cacheMgr->deleteEntry (fname);
1202             // delete from file system
1203             ::g_remove (fname.c_str ());
1204             // delete paramfile if found
1205             // ::g_remove ((fname + paramFileExtension).c_str ());
1206             // ::g_remove ((removeExtension(fname) + paramFileExtension).c_str ());
1207             ::g_remove(options.getParamFile(fname).c_str());
1208             // delete .thm file
1209             ::g_remove ((removeExtension(fname) + ".thm").c_str ());
1210             ::g_remove ((removeExtension(fname) + ".THM").c_str ());
1211             auto xmp_sidecar = Thumbnail::getXmpSidecarPath(fname);
1212             if (Glib::file_test(xmp_sidecar, Glib::FILE_TEST_EXISTS)) {
1213                 ::g_remove(xmp_sidecar.c_str());
1214             }
1215 
1216             if (inclBatchProcessed) {
1217                 Glib::ustring procfName = Glib::ustring::compose ("%1.%2", BatchQueue::calcAutoFileNameBase(fname), options.saveFormatBatch.format);
1218                 ::g_remove (procfName.c_str ());
1219 
1220                 Glib::ustring procfNameParamFile = Glib::ustring::compose ("%1.%2.out%3", BatchQueue::calcAutoFileNameBase(fname), options.saveFormatBatch.format, paramFileExtension);
1221                 ::g_remove (procfNameParamFile.c_str ());
1222             }
1223 
1224             previewsLoaded--;
1225         }
1226 
1227         _refreshProgressBar();
1228         redrawAll ();
1229     }
1230 }
1231 
copyMoveRequested(const std::vector<FileBrowserEntry * > & tbe,bool moveRequested)1232 void FileCatalog::copyMoveRequested(const std::vector<FileBrowserEntry*>& tbe, bool moveRequested)
1233 {
1234     if (tbe.empty()) {
1235         return;
1236     }
1237 
1238     Glib::ustring fc_title;
1239 
1240     if (moveRequested) {
1241         fc_title = M("FILEBROWSER_POPUPMOVETO");
1242     } else {
1243         fc_title = M("FILEBROWSER_POPUPCOPYTO");
1244     }
1245 
1246     Gtk::FileChooserDialog fc (getToplevelWindow (this), fc_title, Gtk::FILE_CHOOSER_ACTION_SELECT_FOLDER );
1247     fc.add_button( M("GENERAL_CANCEL"), Gtk::RESPONSE_CANCEL);
1248     fc.add_button( M("GENERAL_OK"), Gtk::RESPONSE_OK);
1249     if (!options.lastCopyMovePath.empty() && Glib::file_test(options.lastCopyMovePath, Glib::FILE_TEST_IS_DIR)) {
1250         fc.set_current_folder(options.lastCopyMovePath);
1251     } else {
1252         // open dialog at the 1-st file's path
1253         fc.set_current_folder(Glib::path_get_dirname(tbe[0]->filename));
1254     }
1255     //!!! TODO prevent dialog closing on "enter" key press
1256 
1257     if( fc.run() == Gtk::RESPONSE_OK ) {
1258         if (moveRequested) {
1259             removeFromBatchQueue(tbe);
1260         }
1261 
1262         options.lastCopyMovePath = fc.get_current_folder();
1263 
1264         // iterate through selected files
1265         for (unsigned int i = 0; i < tbe.size(); i++) {
1266             Glib::ustring src_fPath = tbe[i]->filename;
1267             Glib::ustring src_Dir = Glib::path_get_dirname(src_fPath);
1268             Glib::RefPtr<Gio::File> src_file = Gio::File::create_for_path ( src_fPath );
1269 
1270             if( !src_file ) {
1271                 continue;    // if file is missing - skip it
1272             }
1273 
1274             Glib::ustring fname = src_file->get_basename();
1275             Glib::ustring fname_noExt = removeExtension(fname);
1276             Glib::ustring fname_Ext = getExtension(fname);
1277 
1278             // construct  destination File Paths
1279             Glib::ustring dest_fPath = Glib::build_filename (options.lastCopyMovePath, fname);
1280             Glib::ustring dest_fPath_param = options.getParamFile(dest_fPath);
1281 
1282             if (moveRequested && (src_Dir == options.lastCopyMovePath)) {
1283                 continue;
1284             }
1285 
1286             /* comparison of src_Dir and dest_Dir is done per image for compatibility with
1287             possible future use of Collections as source where each file's source path may be different.*/
1288 
1289             bool filecopymovecomplete = false;
1290             int i_copyindex = 1;
1291 
1292             while(!filecopymovecomplete) {
1293                 // check for filename conflicts at destination - prevent overwriting (actually RT will crash on overwriting attempt)
1294                 if (!Glib::file_test(dest_fPath, Glib::FILE_TEST_EXISTS) && !Glib::file_test(dest_fPath_param, Glib::FILE_TEST_EXISTS) && !Glib::file_test(Thumbnail::getXmpSidecarPath(dest_fPath), Glib::FILE_TEST_EXISTS)) {
1295                     // copy/move file to destination
1296                     Glib::RefPtr<Gio::File> dest_file = Gio::File::create_for_path ( dest_fPath );
1297 
1298                     if (moveRequested) {
1299                         // move file
1300                         src_file->move(dest_file);
1301                         // re-attach cache files
1302                         cacheMgr->renameEntry (src_fPath, tbe[i]->thumbnail->getMD5(), dest_fPath);
1303                         // remove from browser
1304                         fileBrowser->delEntry (src_fPath);
1305 
1306                         previewsLoaded--;
1307                     } else {
1308                         src_file->copy(dest_file);
1309                     }
1310 
1311 
1312                     // attempt to copy/move paramFile only if it exist next to the src
1313                     Glib::RefPtr<Gio::File> scr_param = Gio::File::create_for_path (options.getParamFile(src_fPath));
1314 
1315                     if (Glib::file_test(options.getParamFile(src_fPath), Glib::FILE_TEST_EXISTS)) {
1316                         Glib::RefPtr<Gio::File> dest_param = Gio::File::create_for_path ( dest_fPath_param);
1317 
1318                         // copy/move paramFile to destination
1319                         if (moveRequested) {
1320                             if (Glib::file_test(options.getParamFile(dest_fPath), Glib::FILE_TEST_EXISTS)) {
1321                                 // profile already got copied to destination from cache after cacheMgr->renameEntry
1322                                 // delete source profile as cleanup
1323                                 ::g_remove (options.getParamFile(src_fPath).c_str ());
1324                             } else {
1325                                 scr_param->move(dest_param);
1326                             }
1327                         } else {
1328                             scr_param->copy(dest_param);
1329                         }
1330                     }
1331 
1332                     auto xmp_sidecar = Thumbnail::getXmpSidecarPath(src_fPath);
1333                     if (Glib::file_test(xmp_sidecar, Glib::FILE_TEST_EXISTS)) {
1334                         auto s = Gio::File::create_for_path(xmp_sidecar);
1335                         auto dst = Gio::File::create_for_path(Thumbnail::getXmpSidecarPath(dest_fPath));
1336                         if (moveRequested) {
1337                             s->move(dst);
1338                         } else {
1339                             s->copy(dst);
1340                         }
1341                     }
1342 
1343                     filecopymovecomplete = true;
1344                 } else {
1345                     // adjust destination fname to avoid conflicts (append "_<index>", preserve extension)
1346                     Glib::ustring dest_fname = Glib::ustring::compose("%1%2%3%4%5", fname_noExt, "_", i_copyindex, ".", fname_Ext);
1347                     // re-construct  destination File Paths
1348                     dest_fPath = Glib::build_filename (options.lastCopyMovePath, dest_fname);
1349                     dest_fPath_param = options.getParamFile(dest_fPath);
1350                     i_copyindex++;
1351                 }
1352             }//while
1353         } // i<tbe.size() loop
1354 
1355         redrawAll ();
1356 
1357         _refreshProgressBar();
1358     } // Gtk::RESPONSE_OK
1359 }
1360 
developRequested(const std::vector<FileBrowserEntry * > & tbe,bool fastmode)1361 void FileCatalog::developRequested(const std::vector<FileBrowserEntry*>& tbe, bool fastmode)
1362 {
1363     if (listener) {
1364         std::vector<BatchQueueEntry*> entries;
1365 
1366         for (size_t i = 0; i < tbe.size(); i++) {
1367             FileBrowserEntry* fbe = tbe[i];
1368             Thumbnail* th = fbe->thumbnail;
1369             if (!th->hasProcParams()) {
1370                 th->createProcParamsForUpdate(false, false, true);
1371             }
1372             rtengine::procparams::ProcParams params = th->getProcParams();
1373 
1374             auto pjob = create_processing_job(fbe->filename, th->getType() == FT_Raw, params, fastmode);
1375 
1376             int pw;
1377             int ph = BatchQueue::calcMaxThumbnailHeight();
1378             th->getThumbnailSize (pw, ph);
1379 
1380             auto bqh = new BatchQueueEntry(pjob, params, fbe->filename, pw, ph, th);
1381             entries.push_back(bqh);
1382         }
1383 
1384         listener->addBatchQueueJobs(entries);
1385     }
1386 }
1387 
selectionChanged(const std::vector<Thumbnail * > & tbe)1388 void FileCatalog::selectionChanged(const std::vector<Thumbnail*>& tbe)
1389 {
1390     if (fslistener) {
1391         fslistener->selectionChanged (tbe);
1392     }
1393 }
1394 
clearFromCacheRequested(const std::vector<FileBrowserEntry * > & tbe,bool leavenotrace)1395 void FileCatalog::clearFromCacheRequested(const std::vector<FileBrowserEntry*>& tbe, bool leavenotrace)
1396 {
1397     if (tbe.empty()) {
1398         return;
1399     }
1400 
1401     for (unsigned int i = 0; i < tbe.size(); i++) {
1402         Glib::ustring fname = tbe[i]->filename;
1403         // remove from cache
1404         cacheMgr->clearFromCache (fname, leavenotrace);
1405     }
1406 }
1407 
isInTabMode() const1408 bool FileCatalog::isInTabMode() const
1409 {
1410     return inTabMode;
1411 }
1412 
categoryButtonToggled(Gtk::ToggleButton * b,bool isMouseClick)1413 void FileCatalog::categoryButtonToggled (Gtk::ToggleButton* b, bool isMouseClick)
1414 {
1415 
1416     //was control key pressed
1417     bool control_down = modifierKey & GDK_CONTROL_MASK;
1418 
1419     //was shift key pressed
1420     bool shift_down   = modifierKey & GDK_SHIFT_MASK;
1421 
1422     // The event is process here, we can clear modifierKey now, it'll be set again on the next even
1423     modifierKey = 0;
1424 
1425     const int numCateg = sizeof(bCateg) / sizeof(bCateg[0]);
1426     const int numButtons = sizeof(categoryButtons) / sizeof(categoryButtons[0]);
1427 
1428     for (int i = 0; i < numCateg; i++) {
1429         bCateg[i].block (true);
1430     }
1431 
1432     // button already toggled when entering this function from a mouse click, so
1433     // we switch it back to its initial state.
1434     if (isMouseClick) {
1435         b->set_active(!b->get_active());
1436     }
1437 
1438     //if both control and shift keys were pressed, do nothing
1439     if (!(control_down && shift_down)) {
1440 
1441         fileBrowser->getScrollPosition (hScrollPos[lastScrollPos], vScrollPos[lastScrollPos]);
1442 
1443         //we look how many stars are already toggled on, if any
1444         int toggled_stars_count = 0, buttons = 0, start_star = 0, toggled_button = 0;
1445 
1446         for (int i = 0; i < numButtons; i++) {
1447             if (categoryButtons[i]->get_active()) {
1448                 if (i > 0 && i < 17) {
1449                     toggled_stars_count ++;
1450                     start_star = i;
1451                 }
1452 
1453                 buttons |= (1 << i);
1454             }
1455 
1456             if (categoryButtons[i] == b) {
1457                 toggled_button = i;
1458             }
1459         }
1460 
1461         // if no modifier key is pressed,
1462         if (!(control_down || shift_down)) {
1463             // if we're deselecting non-trashed or original
1464             if (toggled_button >= 18 && toggled_button <= 19 && (buttons & (1 << toggled_button))) {
1465                 categoryButtons[0]->set_active (true);
1466 
1467                 for (int i = 1; i < numButtons; i++) {
1468                     categoryButtons[i]->set_active (false);
1469                 }
1470             }
1471             // if we're deselecting the only star still active
1472             else if (toggled_stars_count == 1 && (buttons & (1 << toggled_button))) {
1473                 // activate clear-filters
1474                 categoryButtons[0]->set_active (true);
1475                 // deactivate the toggled filter
1476                 categoryButtons[toggled_button]->set_active (false);
1477             }
1478             // if we're deselecting trash
1479             else if (toggled_button == 17 && (buttons & (1 << toggled_button))) {
1480                 categoryButtons[0]->set_active (true);
1481                 categoryButtons[17]->set_active (false);
1482             } else {
1483                 // activate the toggled filter, deactivate the rest
1484                 for (int i = 0; i < numButtons; i++) {
1485                     categoryButtons[i]->set_active (i == toggled_button);
1486                 }
1487             }
1488         }
1489         //modifier key allowed only for stars and color labels...
1490         else if (toggled_button > 0 && toggled_button < 17) {
1491             if (control_down) {
1492                 //control is pressed
1493                 if (toggled_stars_count == 1 && (buttons & (1 << toggled_button))) {
1494                     //we're deselecting the only star still active, so we activate clear-filters
1495                     categoryButtons[0]->set_active(true);
1496                     //and we deselect the toggled star
1497                     categoryButtons[toggled_button]->set_active (false);
1498                 } else if (toggled_stars_count >= 1) {
1499                     //we toggle the state of a star (eventually another one than the only one selected)
1500                     categoryButtons[toggled_button]->set_active(!categoryButtons[toggled_button]->get_active());
1501                 } else {
1502                     //no star selected
1503                     //we deselect the 2 non star filters
1504                     if (buttons &  1    ) {
1505                         categoryButtons[0]->set_active(false);
1506                     }
1507 
1508                     if (buttons & (1 << 17)) {
1509                         categoryButtons[17]->set_active(false);
1510                     }
1511 
1512                     //and we toggle on the star
1513                     categoryButtons[toggled_button]->set_active (true);
1514                 }
1515             } else {
1516                 //shift is pressed, only allowed if 0 or 1 star & labels is selected
1517                 if (!toggled_stars_count) {
1518                     //we deselect the 2 non star filters
1519                     if (buttons &  1      ) {
1520                         categoryButtons[0]->set_active(false);
1521                     }
1522 
1523                     if (buttons & (1 << 7)) {
1524                         categoryButtons[7]->set_active(false);
1525                     }
1526 
1527                     if (buttons & (1 << 13)) {
1528                         categoryButtons[13]->set_active(false);
1529                     }
1530 
1531                     if (buttons & (1 << 17)) {
1532                         categoryButtons[17]->set_active(false);
1533                     }
1534 
1535                     //and we set the start star to 1 (unrated images)
1536                     start_star = 1;
1537                     //we act as if one star were selected
1538                     toggled_stars_count = 1;
1539                 }
1540 
1541                 if (toggled_stars_count == 1) {
1542                     int current_star = min(start_star, toggled_button);
1543                     int last_star   = max(start_star, toggled_button);
1544 
1545                     //we permute the start and the end star for the next loop
1546                     for (; current_star <= last_star; current_star++) {
1547                         //we toggle on all the star in the range
1548                         if (!(buttons & (1 << current_star))) {
1549                             categoryButtons[current_star]->set_active(true);
1550                         }
1551                     }
1552                 }
1553 
1554                 //if more than one star & color label is selected, do nothing
1555             }
1556         }
1557         // ...or non-trashed or original with Control modifier
1558         else if (toggled_button >= 18 && toggled_button <= 19 && control_down) {
1559             Gtk::ToggleButton* categoryButton = categoryButtons[toggled_button];
1560             categoryButton->set_active (!categoryButton->get_active ());
1561 
1562             // If it was the first or last one, we reset the clear filter.
1563             if (buttons == 1 || buttons == (1 << toggled_button)) {
1564                 bFilterClear->set_active (!categoryButton->get_active ());
1565             }
1566         }
1567 
1568         bool active_now, active_before;
1569 
1570         // FilterClear: set the right images
1571         // TODO: swapping FilterClear icon needs more work in categoryButtonToggled
1572         /*active_now = bFilterClear->get_active();
1573         active_before = buttons & (1 << (0)); // 0
1574         if      ( active_now && !active_before) bFilterClear->set_image (*iFilterClear);
1575         else if (!active_now &&  active_before) bFilterClear->set_image (*igFilterClear);*/
1576 
1577         // rank: set the right images
1578         for (int i = 0; i < 5; i++) {
1579             active_now = bRank[i]->get_active();
1580             active_before = buttons & (1 << (i + 2)); // 2,3,4,5,6
1581 
1582             if      ( active_now && !active_before) {
1583                 bRank[i]->set_image (*iranked[i]);
1584             } else if (!active_now &&  active_before) {
1585                 bRank[i]->set_image (*igranked[i]);
1586             }
1587         }
1588 
1589         active_now = bUnRanked->get_active();
1590         active_before = buttons & (1 << (1)); // 1
1591 
1592         if      ( active_now && !active_before) {
1593             bUnRanked->set_image (*iUnRanked);
1594         } else if (!active_now &&  active_before) {
1595             bUnRanked->set_image (*igUnRanked);
1596         }
1597 
1598         // color labels: set the right images
1599         for (int i = 0; i < 5; i++) {
1600             active_now = bCLabel[i]->get_active();
1601             active_before = buttons & (1 << (i + 8)); // 8,9,10,11,12
1602 
1603             if      ( active_now && !active_before) {
1604                 bCLabel[i]->set_image (*iCLabeled[i]);
1605             } else if (!active_now &&  active_before) {
1606                 bCLabel[i]->set_image (*igCLabeled[i]);
1607             }
1608         }
1609 
1610         active_now = bUnCLabeled->get_active();
1611         active_before = buttons & (1 << (7)); // 7
1612 
1613         if      ( active_now && !active_before) {
1614             bUnCLabeled->set_image (*iUnCLabeled);
1615         } else if (!active_now &&  active_before) {
1616             bUnCLabeled->set_image (*igUnCLabeled);
1617         }
1618 
1619         // Edited: set the right images
1620         for (int i = 0; i < 2; i++) {
1621             active_now = bEdited[i]->get_active();
1622             active_before = buttons & (1 << (i + 13)); //13,14
1623 
1624             if      ( active_now && !active_before) {
1625                 bEdited[i]->set_image (*iEdited[i]);
1626             } else if (!active_now &&  active_before) {
1627                 bEdited[i]->set_image (*igEdited[i]);
1628             }
1629         }
1630 
1631         // RecentlySaved: set the right images
1632         for (int i = 0; i < 2; i++) {
1633             active_now = bRecentlySaved[i]->get_active();
1634             active_before = buttons & (1 << (i + 15)); //15,16
1635 
1636             if      ( active_now && !active_before) {
1637                 bRecentlySaved[i]->set_image (*iRecentlySaved[i]);
1638             } else if (!active_now &&  active_before) {
1639                 bRecentlySaved[i]->set_image (*igRecentlySaved[i]);
1640             }
1641         }
1642 
1643         fileBrowser->applyFilter (getFilter ());
1644         _refreshProgressBar();
1645 
1646         //rearrange panels according to the selected filter
1647         removeIfThere (hBox, trashButtonBox);
1648 
1649         if (bTrash->get_active ()) {
1650             hBox->pack_start (*trashButtonBox, Gtk::PACK_SHRINK, 4);
1651         }
1652 
1653         hBox->queue_draw ();
1654 
1655         fileBrowser->setScrollPosition (hScrollPos[lastScrollPos], vScrollPos[lastScrollPos]);
1656     }
1657 
1658     for (int i = 0; i < numCateg; i++) {
1659         bCateg[i].block (false);
1660     }
1661 }
1662 
getFilter()1663 BrowserFilter FileCatalog::getFilter ()
1664 {
1665 
1666     BrowserFilter filter;
1667 
1668     bool anyRankFilterActive = bUnRanked->get_active () || bRank[0]->get_active () || bRank[1]->get_active () || bRank[2]->get_active () || bRank[3]->get_active () || bRank[4]->get_active ();
1669     bool anyCLabelFilterActive = bUnCLabeled->get_active () || bCLabel[0]->get_active () || bCLabel[1]->get_active () || bCLabel[2]->get_active () || bCLabel[3]->get_active () || bCLabel[4]->get_active ();
1670     bool anyEditedFilterActive = bEdited[0]->get_active() || bEdited[1]->get_active();
1671     bool anyRecentlySavedFilterActive = bRecentlySaved[0]->get_active() || bRecentlySaved[1]->get_active();
1672     const bool anySupplementaryActive = bNotTrash->get_active() || bOriginal->get_active();
1673     /*
1674      * filter is setup in 2 steps
1675      * Step 1: handle individual filters
1676     */
1677     filter.showRanked[0] = bFilterClear->get_active() || bUnRanked->get_active () || bTrash->get_active () || anySupplementaryActive ||
1678                            anyCLabelFilterActive || anyEditedFilterActive || anyRecentlySavedFilterActive;
1679 
1680     filter.showCLabeled[0] = bFilterClear->get_active() || bUnCLabeled->get_active () || bTrash->get_active ()  || anySupplementaryActive ||
1681                              anyRankFilterActive || anyEditedFilterActive || anyRecentlySavedFilterActive;
1682 
1683     for (int i = 1; i <= 5; i++) {
1684         filter.showRanked[i] = bFilterClear->get_active() || bRank[i - 1]->get_active () || bTrash->get_active () || anySupplementaryActive ||
1685                                anyCLabelFilterActive || anyEditedFilterActive || anyRecentlySavedFilterActive;
1686 
1687         filter.showCLabeled[i] = bFilterClear->get_active() || bCLabel[i - 1]->get_active () || bTrash->get_active ()  || anySupplementaryActive ||
1688                                  anyRankFilterActive || anyEditedFilterActive || anyRecentlySavedFilterActive;
1689     }
1690 
1691     for (int i = 0; i < 2; i++) {
1692         filter.showEdited[i] = bFilterClear->get_active() || bEdited[i]->get_active () || bTrash->get_active ()  || anySupplementaryActive ||
1693                                anyRankFilterActive || anyCLabelFilterActive || anyRecentlySavedFilterActive;
1694 
1695         filter.showRecentlySaved[i] = bFilterClear->get_active() || bRecentlySaved[i]->get_active () || bTrash->get_active ()  || anySupplementaryActive ||
1696                                       anyRankFilterActive || anyCLabelFilterActive || anyEditedFilterActive;
1697     }
1698 
1699     filter.multiselect = false;
1700 
1701     /*
1702      * Step 2
1703      * handle the case when more than 1 filter is selected. This overrides values set in Step
1704      * if no filters in a group are active, filter.show for each member of that group will be set to true
1705      * otherwise they are set based on UI input
1706      */
1707     if ((anyRankFilterActive && anyCLabelFilterActive ) ||
1708             (anyRankFilterActive && anyEditedFilterActive ) ||
1709             (anyRankFilterActive && anyRecentlySavedFilterActive ) ||
1710             (anyCLabelFilterActive && anyEditedFilterActive ) ||
1711             (anyCLabelFilterActive && anyRecentlySavedFilterActive ) ||
1712             (anyEditedFilterActive && anyRecentlySavedFilterActive) ||
1713             (anySupplementaryActive && (anyRankFilterActive || anyCLabelFilterActive || anyEditedFilterActive || anyRecentlySavedFilterActive))) {
1714 
1715         filter.multiselect = true;
1716         filter.showRanked[0] = anyRankFilterActive ? bUnRanked->get_active () : true;
1717         filter.showCLabeled[0] = anyCLabelFilterActive ? bUnCLabeled->get_active () : true;
1718 
1719         for (int i = 1; i <= 5; i++) {
1720             filter.showRanked[i] = anyRankFilterActive ? bRank[i - 1]->get_active () : true;
1721             filter.showCLabeled[i] = anyCLabelFilterActive ? bCLabel[i - 1]->get_active () : true;
1722         }
1723 
1724         for (int i = 0; i < 2; i++) {
1725             filter.showEdited[i] = anyEditedFilterActive ? bEdited[i]->get_active() : true;
1726             filter.showRecentlySaved[i] = anyRecentlySavedFilterActive ? bRecentlySaved[i]->get_active() : true;
1727         }
1728     }
1729 
1730 
1731     filter.showTrash = bTrash->get_active () || !bNotTrash->get_active ();
1732     filter.showNotTrash = !bTrash->get_active ();
1733     filter.showOriginal = bOriginal->get_active();
1734 
1735     if (!filterPanel) {
1736         filter.exifFilterEnabled = false;
1737     } else {
1738         if (!hasValidCurrentEFS) {
1739             MyMutex::MyLock lock(dirEFSMutex);
1740             filter.exifFilter = dirEFS;
1741         } else {
1742             filter.exifFilter = currentEFS;
1743         }
1744 
1745         filter.exifFilterEnabled = filterPanel->isEnabled ();
1746     }
1747 
1748     //TODO add support for more query options. e.g by date, iso, f-number, etc
1749     //TODO could use date:<value>;iso:<value>  etc
1750     // default will be filename
1751 
1752     /* // this is for safe execution if getFilter is called before Query object is instantiated
1753     Glib::ustring tempQuery;
1754     tempQuery="";
1755     if (Query) tempQuery = Query->get_text();
1756     */
1757     filter.queryString = Query->get_text(); // full query string from Query Entry
1758     filter.queryFileName = Query->get_text(); // for now Query is only by file name
1759 
1760     return filter;
1761 }
1762 
filterChanged()1763 void FileCatalog::filterChanged ()
1764 {
1765     //TODO !!! there is too many repetitive and unnecessary executions of
1766     // " fileBrowser->applyFilter (getFilter()); " throughout the code
1767     // this needs further analysis and cleanup
1768     fileBrowser->applyFilter (getFilter());
1769     _refreshProgressBar();
1770 }
1771 
reparseDirectory()1772 void FileCatalog::reparseDirectory ()
1773 {
1774 
1775     if (selectedDirectory.empty()) {
1776         return;
1777     }
1778 
1779     if (!Glib::file_test(selectedDirectory, Glib::FILE_TEST_IS_DIR)) {
1780         closeDir();
1781         return;
1782     }
1783 
1784     // check if a thumbnailed file has been deleted
1785     const std::vector<ThumbBrowserEntryBase*>& t = fileBrowser->getEntries();
1786     std::vector<Glib::ustring> fileNamesToDel;
1787 
1788     for (const auto& entry : t) {
1789         if (!Glib::file_test(entry->filename, Glib::FILE_TEST_EXISTS)) {
1790             fileNamesToDel.push_back(entry->filename);
1791         }
1792     }
1793 
1794     for (const auto& toDelete : fileNamesToDel) {
1795         delete fileBrowser->delEntry(toDelete);
1796         cacheMgr->deleteEntry(toDelete);
1797         --previewsLoaded;
1798     }
1799 
1800     if (!fileNamesToDel.empty()) {
1801         _refreshProgressBar();
1802     }
1803 
1804     // check if a new file has been added
1805     // build a set of collate-keys for faster search
1806     std::set<std::string> oldNames;
1807     for (const auto& oldName : fileNameList) {
1808         oldNames.insert(oldName.collate_key());
1809     }
1810 
1811     fileNameList = getFileList();
1812     for (const auto& newName : fileNameList) {
1813         if (oldNames.find(newName.collate_key()) == oldNames.end()) {
1814             addFile(newName);
1815             _refreshProgressBar();
1816         }
1817     }
1818 
1819 }
1820 
on_dir_changed(const Glib::RefPtr<Gio::File> & file,const Glib::RefPtr<Gio::File> & other_file,Gio::FileMonitorEvent event_type,bool internal)1821 void FileCatalog::on_dir_changed (const Glib::RefPtr<Gio::File>& file, const Glib::RefPtr<Gio::File>& other_file, Gio::FileMonitorEvent event_type, bool internal)
1822 {
1823 
1824     if (options.has_retained_extention(file->get_parse_name())
1825             && (event_type == Gio::FILE_MONITOR_EVENT_CREATED || event_type == Gio::FILE_MONITOR_EVENT_DELETED || event_type == Gio::FILE_MONITOR_EVENT_CHANGED)) {
1826         if (!internal) {
1827             GThreadLock lock;
1828             reparseDirectory ();
1829         } else {
1830             reparseDirectory ();
1831         }
1832     }
1833 }
1834 
addFile(const Glib::ustring & fName)1835 void FileCatalog::addFile (const Glib::ustring& fName)
1836 {
1837     if (!fName.empty()) {
1838         previewLoader->add(selectedDirectoryId, fName, this);
1839         previewsToLoad++;
1840     }
1841 }
1842 
addAndOpenFile(const Glib::ustring & fname)1843 void FileCatalog::addAndOpenFile (const Glib::ustring& fname)
1844 {
1845     auto file = Gio::File::create_for_path(fname);
1846 
1847     if (!file ) {
1848         return;
1849     }
1850 
1851     if (!file->query_exists()) {
1852         return;
1853     }
1854 
1855     try {
1856 
1857         const auto info = file->query_info();
1858 
1859         if (!info) {
1860             return;
1861         }
1862 
1863         const auto lastdot = info->get_name().find_last_of('.');
1864         if (lastdot != Glib::ustring::npos) {
1865             if (!options.is_extention_enabled(info->get_name().substr(lastdot + 1))) {
1866                 return;
1867             }
1868         } else {
1869             return;
1870         }
1871 
1872 
1873         // if supported, load thumbnail first
1874         const auto tmb = cacheMgr->getEntry(file->get_parse_name());
1875 
1876         if (!tmb) {
1877             return;
1878         }
1879 
1880         FileBrowserEntry* entry = new FileBrowserEntry(tmb, file->get_parse_name());
1881         previewReady(selectedDirectoryId, entry);
1882         // open the file
1883         tmb->increaseRef();
1884         idle_register.add(
1885             [this, tmb]() -> bool
1886             {
1887                 _openImage({tmb});
1888                 return false;
1889             }
1890         );
1891 
1892     } catch(Gio::Error&) {}
1893 }
1894 
emptyTrash()1895 void FileCatalog::emptyTrash ()
1896 {
1897 
1898     const auto& t = fileBrowser->getEntries();
1899     std::vector<FileBrowserEntry*> toDel;
1900 
1901     for (const auto entry : t) {
1902         if (static_cast<FileBrowserEntry*>(entry)->thumbnail->getInTrash()) {
1903             toDel.push_back(static_cast<FileBrowserEntry*>(entry));
1904         }
1905     }
1906     if (toDel.size() > 0) {
1907         deleteRequested(toDel, false, false);
1908         trashChanged();
1909     }
1910 }
1911 
trashIsEmpty()1912 bool FileCatalog::trashIsEmpty ()
1913 {
1914 
1915     const auto& t = fileBrowser->getEntries();
1916 
1917     for (const auto entry : t) {
1918         if ((static_cast<FileBrowserEntry*>(entry))->thumbnail->getInTrash()) {
1919             return false;
1920         }
1921     }
1922     return true;
1923 }
1924 
zoomIn()1925 void FileCatalog::zoomIn ()
1926 {
1927 
1928     fileBrowser->zoomIn ();
1929     refreshHeight();
1930 
1931 }
zoomOut()1932 void FileCatalog::zoomOut ()
1933 {
1934 
1935     fileBrowser->zoomOut ();
1936     refreshHeight();
1937 
1938 }
refreshEditedState(const std::set<Glib::ustring> & efiles)1939 void FileCatalog::refreshEditedState (const std::set<Glib::ustring>& efiles)
1940 {
1941 
1942     editedFiles = efiles;
1943     fileBrowser->refreshEditedState (efiles);
1944 }
1945 
1946 // void FileCatalog::exportRequested()
1947 // {
1948 // }
1949 
1950 // Called within GTK UI thread
exifFilterChanged()1951 void FileCatalog::exifFilterChanged ()
1952 {
1953 
1954     currentEFS = filterPanel->getFilter ();
1955     hasValidCurrentEFS = true;
1956     fileBrowser->applyFilter (getFilter ());
1957     _refreshProgressBar();
1958 }
1959 
setFilterPanel(FilterPanel * fpanel)1960 void FileCatalog::setFilterPanel (FilterPanel* fpanel)
1961 {
1962 
1963     filterPanel = fpanel;
1964     filterPanel->set_sensitive (false);
1965     filterPanel->setFilterPanelListener (this);
1966 }
1967 
1968 // void FileCatalog::setExportPanel(ExportPanel* expanel)
1969 // {
1970 //     exportPanel = expanel;
1971 //     exportPanel->set_sensitive (false);
1972 //     exportPanel->setExportPanelListener (this);
1973 //     fileBrowser->setExportPanel(expanel);
1974 // }
1975 
trashChanged()1976 void FileCatalog::trashChanged ()
1977 {
1978     if (trashIsEmpty()) {
1979         bTrash->set_image(*iTrashShowEmpty);
1980     } else {
1981         bTrash->set_image(*iTrashShowFull);
1982     }
1983 }
1984 
1985 // Called within GTK UI thread
buttonQueryClearPressed()1986 void FileCatalog::buttonQueryClearPressed ()
1987 {
1988     Query->set_text("");
1989     FileCatalog::executeQuery ();
1990 }
1991 
1992 // Called within GTK UI thread
executeQuery()1993 void FileCatalog::executeQuery()
1994 {
1995     // if BrowsePath text was changed, do a full browse;
1996     // otherwise filter only
1997 
1998     if (BrowsePath->get_text() != selectedDirectory) {
1999         buttonBrowsePathPressed ();
2000     } else {
2001         FileCatalog::filterChanged ();
2002     }
2003 }
2004 
Query_key_pressed(GdkEventKey * event)2005 bool FileCatalog::Query_key_pressed (GdkEventKey *event)
2006 {
2007 
2008     bool shift = event->state & GDK_SHIFT_MASK;
2009 
2010     switch (event->keyval) {
2011     case GDK_KEY_Escape:
2012 
2013         // Clear Query if the Escape character is pressed within it
2014         if (shift) {
2015             FileCatalog::buttonQueryClearPressed ();
2016             return true;
2017         }
2018 
2019         break;
2020 
2021     default:
2022         break;
2023     }
2024 
2025     return false;
2026 }
2027 
updateFBQueryTB(bool singleRow)2028 void FileCatalog::updateFBQueryTB (bool singleRow)
2029 {
2030     hbToolBar1->reference();
2031 
2032     if (singleRow) {
2033         if (hbToolBar1STB) {
2034             hbToolBar1STB->remove_with_viewport();
2035             removeIfThere(this, hbToolBar1STB, false);
2036             buttonBar->pack_start(*hbToolBar1, Gtk::PACK_EXPAND_WIDGET, 0);
2037             hbToolBar1STB = nullptr;
2038         }
2039     } else {
2040         if (!hbToolBar1STB) {
2041             removeIfThere(buttonBar, hbToolBar1, false);
2042             hbToolBar1STB = Gtk::manage(new MyScrolledToolbar());
2043             hbToolBar1STB->set_name("FileBrowserQueryToolbar");
2044             hbToolBar1STB->add(*hbToolBar1);
2045             hbToolBar1STB->show();
2046             pack_start (*hbToolBar1STB, Gtk::PACK_SHRINK, 0);
2047             reorder_child(*hbToolBar1STB, 0);
2048         }
2049     }
2050 
2051     hbToolBar1->unreference();
2052 }
2053 
updateFBToolBarVisibility(bool showFilmStripToolBar)2054 void FileCatalog::updateFBToolBarVisibility (bool showFilmStripToolBar)
2055 {
2056     if (showFilmStripToolBar) {
2057         showToolBar();
2058     } else {
2059         hideToolBar();
2060     }
2061 
2062     refreshHeight();
2063 }
2064 
buttonBrowsePathPressed()2065 void FileCatalog::buttonBrowsePathPressed ()
2066 {
2067     auto BrowsePathValue = getBrowsePath();
2068     BrowsePath->set_text(BrowsePathValue);
2069 
2070     // validate the path
2071     if (Glib::file_test(BrowsePathValue, Glib::FILE_TEST_IS_DIR) && selectDir) {
2072         selectDir(BrowsePathValue);
2073     } else { // error, likely path not found: show red arrow
2074         buttonBrowsePath->set_image (*iRefreshRed);
2075     }
2076 }
2077 
BrowsePath_key_pressed(GdkEventKey * event)2078 bool FileCatalog::BrowsePath_key_pressed (GdkEventKey *event)
2079 {
2080 
2081     bool shift = event->state & GDK_SHIFT_MASK;
2082 
2083     switch (event->keyval) {
2084     case GDK_KEY_Escape:
2085 
2086         // On Escape character Reset BrowsePath to selectedDirectory
2087         if (shift) {
2088             BrowsePath->set_text(selectedDirectory);
2089             // place cursor at the end
2090             BrowsePath->select_region(BrowsePath->get_text_length(), BrowsePath->get_text_length());
2091             return true;
2092         }
2093 
2094         break;
2095 
2096     default:
2097         break;
2098     }
2099 
2100     return false;
2101 }
2102 
tbLeftPanel_1_visible(bool visible)2103 void FileCatalog::tbLeftPanel_1_visible (bool visible)
2104 {
2105     if (visible) {
2106         tbLeftPanel_1->show();
2107         vSepiLeftPanel->show();
2108     } else {
2109         tbLeftPanel_1->hide();
2110         vSepiLeftPanel->hide();
2111     }
2112 }
tbRightPanel_1_visible(bool visible)2113 void FileCatalog::tbRightPanel_1_visible (bool visible)
2114 {
2115     if (visible) {
2116         tbRightPanel_1->show();
2117     } else {
2118         tbRightPanel_1->hide();
2119     }
2120 }
tbLeftPanel_1_toggled()2121 void FileCatalog::tbLeftPanel_1_toggled ()
2122 {
2123 //    removeIfThere (filepanel->dirpaned, filepanel->placespaned, false);
2124 
2125     bool in_inspector = fileBrowser && fileBrowser->getInspector() && fileBrowser->getInspector()->isActive();
2126 
2127     if (tbLeftPanel_1->get_active()) {
2128 //        filepanel->dirpaned->pack1 (*filepanel->placespaned, false, true);
2129         filepanel->placespaned->show();
2130         tbLeftPanel_1->set_image (*iLeftPanel_1_Hide);
2131         if (in_inspector) {
2132             options.inspectorDirPanelOpened = true;
2133         } else {
2134             options.browserDirPanelOpened = true;
2135         }
2136     } else {
2137         filepanel->placespaned->hide();
2138         tbLeftPanel_1->set_image (*iLeftPanel_1_Show);
2139         if (in_inspector) {
2140             options.inspectorDirPanelOpened = false;
2141         } else {
2142             options.browserDirPanelOpened = false;
2143         }
2144     }
2145 }
2146 
tbRightPanel_1_toggled()2147 void FileCatalog::tbRightPanel_1_toggled ()
2148 {
2149     if (tbRightPanel_1->get_active()) {
2150         filepanel->showRightBox(true);
2151         tbRightPanel_1->set_image (*iRightPanel_1_Hide);
2152         options.browserToolPanelOpened = true;
2153     } else {
2154         filepanel->showRightBox(false);
2155         tbRightPanel_1->set_image (*iRightPanel_1_Show);
2156         options.browserToolPanelOpened = false;
2157     }
2158 }
2159 
CheckSidePanelsVisibility()2160 bool FileCatalog::CheckSidePanelsVisibility()
2161 {
2162     return tbLeftPanel_1->get_active() || tbRightPanel_1->get_active();
2163 }
2164 
toggleSidePanels()2165 void FileCatalog::toggleSidePanels()
2166 {
2167     // toggle left AND right panels
2168 
2169     bool bAllSidePanelsVisible;
2170     bAllSidePanelsVisible = CheckSidePanelsVisibility();
2171 
2172     tbLeftPanel_1->set_active (!bAllSidePanelsVisible);
2173     tbRightPanel_1->set_active (!bAllSidePanelsVisible);
2174 }
2175 
toggleLeftPanel()2176 void FileCatalog::toggleLeftPanel()
2177 {
2178     tbLeftPanel_1->set_active (!tbLeftPanel_1->get_active());
2179 }
2180 
toggleRightPanel()2181 void FileCatalog::toggleRightPanel()
2182 {
2183     tbRightPanel_1->set_active (!tbRightPanel_1->get_active());
2184 }
2185 
2186 
selectImage(Glib::ustring fname,bool clearFilters)2187 void FileCatalog::selectImage (Glib::ustring fname, bool clearFilters)
2188 {
2189 
2190     Glib::ustring dirname = Glib::path_get_dirname(fname);
2191 
2192     if (!dirname.empty()) {
2193         BrowsePath->set_text(dirname);
2194 
2195 
2196         if (clearFilters) { // clear all filters
2197             Query->set_text("");
2198             categoryButtonToggled(bFilterClear, false);
2199 
2200             // disable exif filters
2201             if (filterPanel->isEnabled()) {
2202                 filterPanel->setEnabled (false);
2203             }
2204         }
2205 
2206         if (BrowsePath->get_text() != selectedDirectory) {
2207             // reload or refresh thumbs and select image
2208             buttonBrowsePathPressed ();
2209             // the actual selection of image will be handled asynchronously at the end of FileCatalog::previewsFinishedUI
2210             imageToSelect_fname = fname;
2211         } else {
2212             // FileCatalog::filterChanged ();//this will be replaced by queue_draw() in fileBrowser->selectImage
2213             fileBrowser->selectImage(fname);
2214             imageToSelect_fname = "";
2215         }
2216     }
2217 }
2218 
2219 
isSelected(const Glib::ustring & fname) const2220 bool FileCatalog::isSelected(const Glib::ustring &fname) const
2221 {
2222     return fileBrowser->isSelected(fname);
2223 }
2224 
2225 
openNextPreviousEditorImage(Glib::ustring fname,bool clearFilters,eRTNav nextPrevious)2226 void FileCatalog::openNextPreviousEditorImage (Glib::ustring fname, bool clearFilters, eRTNav nextPrevious)
2227 {
2228 
2229     Glib::ustring dirname = Glib::path_get_dirname(fname);
2230 
2231     if (!dirname.empty()) {
2232         BrowsePath->set_text(dirname);
2233 
2234 
2235         if (clearFilters) { // clear all filters
2236             Query->set_text("");
2237             categoryButtonToggled(bFilterClear, false);
2238 
2239             // disable exif filters
2240             if (filterPanel->isEnabled()) {
2241                 filterPanel->setEnabled (false);
2242             }
2243         }
2244 
2245         if (BrowsePath->get_text() != selectedDirectory) {
2246             // reload or refresh thumbs and select image
2247             buttonBrowsePathPressed ();
2248             // the actual selection of image will be handled asynchronously at the end of FileCatalog::previewsFinishedUI
2249             refImageForOpen_fname = fname;
2250             actionNextPrevious = nextPrevious;
2251         } else {
2252             // FileCatalog::filterChanged ();//this was replace by queue_draw() in fileBrowser->selectImage
2253             fileBrowser->openNextPreviousEditorImage(fname, nextPrevious);
2254             refImageForOpen_fname = "";
2255             actionNextPrevious = NAV_NONE;
2256         }
2257     }
2258 }
2259 
handleShortcutKey(GdkEventKey * event)2260 bool FileCatalog::handleShortcutKey (GdkEventKey* event)
2261 {
2262 
2263     bool ctrl = event->state & GDK_CONTROL_MASK;
2264     bool shift = event->state & GDK_SHIFT_MASK;
2265     bool alt = event->state & GDK_MOD1_MASK;
2266 #ifdef __WIN32__
2267     bool altgr = event->state & GDK_MOD2_MASK;
2268 #else
2269     bool altgr = event->state & GDK_MOD5_MASK;
2270 #endif
2271     modifierKey = event->state;
2272 
2273     // GUI Layout
2274     switch(event->keyval) {
2275     case GDK_KEY_l:
2276         if (!alt && !ctrl) {
2277             tbLeftPanel_1->set_active (!tbLeftPanel_1->get_active());    // toggle left panel
2278         }
2279 
2280         if (!alt && ctrl) {
2281             tbRightPanel_1->set_active (!tbRightPanel_1->get_active());    // toggle right panel
2282         }
2283 
2284         if (alt && ctrl) {
2285             tbLeftPanel_1->set_active (!tbLeftPanel_1->get_active()); // toggle left panel
2286             tbRightPanel_1->set_active (!tbRightPanel_1->get_active()); // toggle right panel
2287         }
2288 
2289         return true;
2290 
2291     case GDK_KEY_m:
2292         if (!ctrl && !alt) {
2293             toggleSidePanels();
2294         }
2295 
2296         return true;
2297     }
2298 
2299     if (!shift) {
2300         switch(event->keyval) {
2301         case GDK_KEY_Escape:
2302             BrowsePath->set_text(selectedDirectory);
2303             fileBrowser->getFocus();
2304             return true;
2305         }
2306     }
2307 
2308 #ifdef __WIN32__
2309 
2310     if (!alt && !shift && !altgr) { // shift is reserved for ranking
2311         switch(event->hardware_keycode) {
2312         case 0x30:
2313             categoryButtonToggled(bUnRanked, false);
2314             return true;
2315 
2316         case 0x31:
2317             categoryButtonToggled(bRank[0], false);
2318             return true;
2319 
2320         case 0x32:
2321             categoryButtonToggled(bRank[1], false);
2322             return true;
2323 
2324         case 0x33:
2325             categoryButtonToggled(bRank[2], false);
2326             return true;
2327 
2328         case 0x34:
2329             categoryButtonToggled(bRank[3], false);
2330             return true;
2331 
2332         case 0x35:
2333             categoryButtonToggled(bRank[4], false);
2334             return true;
2335 
2336         case 0x36:
2337             categoryButtonToggled(bEdited[0], false);
2338             return true;
2339 
2340         case 0x37:
2341             categoryButtonToggled(bEdited[1], false);
2342             return true;
2343         }
2344     }
2345 
2346     if (!alt && !shift) {
2347         switch(event->keyval) {
2348 
2349         case GDK_KEY_Return:
2350         case GDK_KEY_KP_Enter:
2351             if (BrowsePath->is_focus()) {
2352                 FileCatalog::buttonBrowsePathPressed ();
2353                 return true;
2354             }
2355 
2356             break;
2357         }
2358     }
2359 
2360     if (alt && !shift) { // shift is reserved for color labeling
2361         switch(event->hardware_keycode) {
2362         case 0x30:
2363             categoryButtonToggled(bUnCLabeled, false);
2364             return true;
2365 
2366         case 0x31:
2367             categoryButtonToggled(bCLabel[0], false);
2368             return true;
2369 
2370         case 0x32:
2371             categoryButtonToggled(bCLabel[1], false);
2372             return true;
2373 
2374         case 0x33:
2375             categoryButtonToggled(bCLabel[2], false);
2376             return true;
2377 
2378         case 0x34:
2379             categoryButtonToggled(bCLabel[3], false);
2380             return true;
2381 
2382         case 0x35:
2383             categoryButtonToggled(bCLabel[4], false);
2384             return true;
2385 
2386         case 0x36:
2387             categoryButtonToggled(bRecentlySaved[0], false);
2388             return true;
2389 
2390         case 0x37:
2391             categoryButtonToggled(bRecentlySaved[1], false);
2392             return true;
2393         }
2394     }
2395 
2396 #else
2397 
2398     if (!alt && !shift && !altgr) { // shift is reserved for ranking
2399         switch(event->hardware_keycode) {
2400         case 0x13:
2401             categoryButtonToggled(bUnRanked, false);
2402             return true;
2403 
2404         case 0x0a:
2405             categoryButtonToggled(bRank[0], false);
2406             return true;
2407 
2408         case 0x0b:
2409             categoryButtonToggled(bRank[1], false);
2410             return true;
2411 
2412         case 0x0c:
2413             categoryButtonToggled(bRank[2], false);
2414             return true;
2415 
2416         case 0x0d:
2417             categoryButtonToggled(bRank[3], false);
2418             return true;
2419 
2420         case 0x0e:
2421             categoryButtonToggled(bRank[4], false);
2422             return true;
2423 
2424         case 0x0f:
2425             categoryButtonToggled(bEdited[0], false);
2426             return true;
2427 
2428         case 0x10:
2429             categoryButtonToggled(bEdited[1], false);
2430             return true;
2431         }
2432     }
2433 
2434     if (!alt && !shift) {
2435         switch(event->keyval) {
2436 
2437         case GDK_KEY_Return:
2438         case GDK_KEY_KP_Enter:
2439             if (BrowsePath->is_focus()) {
2440                 FileCatalog::buttonBrowsePathPressed ();
2441                 return true;
2442             }
2443 
2444             break;
2445         }
2446     }
2447 
2448     if (alt && !shift) { // shift is reserved for color labeling
2449         switch(event->hardware_keycode) {
2450         case 0x13:
2451             categoryButtonToggled(bUnCLabeled, false);
2452             return true;
2453 
2454         case 0x0a:
2455             categoryButtonToggled(bCLabel[0], false);
2456             return true;
2457 
2458         case 0x0b:
2459             categoryButtonToggled(bCLabel[1], false);
2460             return true;
2461 
2462         case 0x0c:
2463             categoryButtonToggled(bCLabel[2], false);
2464             return true;
2465 
2466         case 0x0d:
2467             categoryButtonToggled(bCLabel[3], false);
2468             return true;
2469 
2470         case 0x0e:
2471             categoryButtonToggled(bCLabel[4], false);
2472             return true;
2473 
2474         case 0x0f:
2475             categoryButtonToggled(bRecentlySaved[0], false);
2476             return true;
2477 
2478         case 0x10:
2479             categoryButtonToggled(bRecentlySaved[1], false);
2480             return true;
2481         }
2482     }
2483 
2484 #endif
2485 
2486     if (!ctrl && !alt) {
2487         switch(event->keyval) {
2488         case GDK_KEY_d:
2489         case GDK_KEY_D:
2490             categoryButtonToggled(bFilterClear, false);
2491             return true;
2492         }
2493     }
2494 
2495     if (!ctrl || (alt && !options.tabbedUI)) {
2496         switch(event->keyval) {
2497         case GDK_KEY_i:
2498         case GDK_KEY_I:
2499             exifInfo->set_active (!exifInfo->get_active());
2500             return true;
2501 
2502         case GDK_KEY_plus:
2503         case GDK_KEY_equal:
2504             zoomIn();
2505             return true;
2506 
2507         case GDK_KEY_minus:
2508         case GDK_KEY_underscore:
2509             zoomOut();
2510             return true;
2511         }
2512     }
2513 
2514     if (ctrl && !alt) {
2515         switch (event->keyval) {
2516         case GDK_KEY_o:
2517             if (!BrowsePath->has_focus()) {
2518                 BrowsePath->select_region(0, BrowsePath->get_text_length());
2519                 BrowsePath->grab_focus();
2520             } else {
2521                 fileBrowser->getFocus();
2522             }
2523             return true;
2524 
2525         case GDK_KEY_f:
2526             if (!Query->has_focus()) {
2527                 Query->select_region(0, Query->get_text_length());
2528                 Query->grab_focus();
2529             } else {
2530                 fileBrowser->getFocus();
2531             }
2532             return true;
2533 
2534         case GDK_KEY_t:
2535         case GDK_KEY_T:
2536             modifierKey = 0; // HOMBRE: yet another hack.... otherwise the shortcut won't work
2537             categoryButtonToggled(bTrash, false);
2538             return true;
2539 
2540         case GDK_KEY_Delete:
2541             if (shift && bTrash->get_active()) {
2542                 emptyTrash();
2543                 return true;
2544             }
2545         }
2546     }
2547 
2548     if (!ctrl && !alt && shift) {
2549         switch (event->keyval) {
2550         case GDK_KEY_t:
2551         case GDK_KEY_T:
2552             if (inTabMode) {
2553                 if (options.showFilmStripToolBar) {
2554                     hideToolBar();
2555                 } else {
2556                     showToolBar();
2557                 }
2558 
2559                 options.showFilmStripToolBar = !options.showFilmStripToolBar;
2560             }
2561 
2562             return true;
2563         }
2564     }
2565 
2566     if (!ctrl && !alt && !shift) {
2567         switch (event->keyval) {
2568         case GDK_KEY_t:
2569         case GDK_KEY_T:
2570             if (inTabMode) {
2571                 if (options.showFilmStripToolBar) {
2572                     hideToolBar();
2573                 } else {
2574                     showToolBar();
2575                 }
2576 
2577                 options.showFilmStripToolBar = !options.showFilmStripToolBar;
2578             }
2579 
2580             refreshHeight();
2581             return true;
2582 
2583         case GDK_KEY_F5:
2584             FileCatalog::buttonBrowsePathPressed();
2585             return true;
2586         }
2587     }
2588 
2589     return fileBrowser->keyPressed(event);
2590 }
2591 
showToolBar()2592 void FileCatalog::showToolBar()
2593 {
2594     if (hbToolBar1STB) {
2595         hbToolBar1STB->show();
2596     }
2597 
2598     buttonBar->show();
2599 }
2600 
hideToolBar()2601 void FileCatalog::hideToolBar()
2602 {
2603     if (hbToolBar1STB) {
2604         hbToolBar1STB->hide();
2605     }
2606 
2607     buttonBar->hide();
2608 }
2609 
2610 
getBrowsePath()2611 Glib::ustring FileCatalog::getBrowsePath()
2612 {
2613     auto txt = BrowsePath->get_text();
2614     Glib::ustring expanded = "";
2615     auto prefix = txt.substr(0, 1);
2616     if (prefix == "~") { // home directory
2617         expanded = PlacesBrowser::userHomeDir();
2618     } else if (prefix == "!") { // user's pictures directory
2619         expanded = PlacesBrowser::userPicturesDir();
2620     }
2621 
2622     if (!expanded.empty()) {
2623         return Glib::ustring::compose("%1%2", expanded, txt.substr(1));
2624     } else {
2625         return txt;
2626     }
2627 }
2628 
2629 
onBrowsePathChanged()2630 void FileCatalog::onBrowsePathChanged()
2631 {
2632     auto txt = getBrowsePath();
2633     auto pos = txt.find_last_of(G_DIR_SEPARATOR_S);
2634     if (pos != Glib::ustring::npos) {
2635         auto root = txt.substr(0, pos+1);
2636         Glib::RefPtr<DirCompletion>::cast_static(browsePathCompletion)->refresh(root);
2637     }
2638 }
2639 
2640 
disableInspector()2641 void FileCatalog::disableInspector()
2642 {
2643     if (fileBrowser) {
2644         fileBrowser->disableInspector();
2645         tbRightPanel_1->show();
2646         tbRightPanel_1->set_active(options.browserToolPanelOpened);
2647         tbLeftPanel_1->set_active(options.browserDirPanelOpened);
2648     }
2649 }
2650 
2651 
enableInspector()2652 void FileCatalog::enableInspector()
2653 {
2654     if (fileBrowser) {
2655         fileBrowser->enableInspector();
2656         tbLeftPanel_1->set_active(options.inspectorDirPanelOpened);
2657         if (!tbRightPanel_1->get_active()) {
2658             toggleRightPanel();
2659             options.browserToolPanelOpened = false;
2660         }
2661         tbRightPanel_1->hide();
2662     }
2663 }
2664 
2665 
setupSidePanels()2666 void FileCatalog::setupSidePanels()
2667 {
2668     tbLeftPanel_1->set_active(options.browserDirPanelOpened);
2669     tbRightPanel_1->set_active(options.browserToolPanelOpened);
2670     filepanel->showRightBox(options.browserToolPanelOpened);
2671     filepanel->placespaned->set_visible(options.browserDirPanelOpened);
2672     enableInspector();
2673     disableInspector();
2674 }
2675 
2676 
removeFromBatchQueue(const std::vector<FileBrowserEntry * > & tbe)2677 void FileCatalog::removeFromBatchQueue(const std::vector<FileBrowserEntry*> &tbe)
2678 {
2679     if (!bqueue_) {
2680         return;
2681     }
2682 
2683     std::set<Glib::ustring> tbset;
2684     for (auto entry : tbe) {
2685         if (entry->thumbnail && entry->thumbnail->isEnqueued()) {
2686             tbset.insert(entry->thumbnail->getFileName());
2687         }
2688     }
2689 
2690     std::vector<ThumbBrowserEntryBase *> tocancel;
2691     for (auto entry : bqueue_->getEntries()) {
2692         if (entry->thumbnail && tbset.find(entry->thumbnail->getFileName()) != tbset.end()) {
2693             tocancel.push_back(entry);
2694         }
2695     }
2696 
2697     bqueue_->cancelItems(tocancel, true);
2698 }
2699