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