1 /*
2 * This file is part of RawTherapee.
3 *
4 * Copyright (c) 2004-2010 Gabor Horvath <hgabor@rawtherapee.com>
5 *
6 * RawTherapee is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * RawTherapee is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with RawTherapee. If not, see <http://www.gnu.org/licenses/>.
18 */
19 #include "placesbrowser.h"
20
21 #ifdef WIN32
22 #include <windows.h>
23 #include <shlobj.h>
24 #include <Shlwapi.h>
25 #endif
26
27 #include "guiutils.h"
28 #include "rtimage.h"
29 #include "options.h"
30 #include "toolpanel.h"
31
PlacesBrowser()32 PlacesBrowser::PlacesBrowser ()
33 {
34
35 scrollw = Gtk::manage (new Gtk::ScrolledWindow ());
36 scrollw->set_policy (Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
37 pack_start (*scrollw);
38
39 // Since Gtk3, we can't have image+text buttons natively. We'll comply to the Gtk guidelines and choose one of them (icons here)
40 add = Gtk::manage (new Gtk::Button ());
41 add->set_tooltip_text(M("MAIN_FRAME_PLACES_ADD"));
42 setExpandAlignProperties(add, true, false, Gtk::ALIGN_FILL, Gtk::ALIGN_START);
43 //add->get_style_context()->set_junction_sides(Gtk::JUNCTION_RIGHT);
44 add->get_style_context()->add_class("Left");
45 add->set_image (*Gtk::manage (new RTImage ("add-small.png")));
46 del = Gtk::manage (new Gtk::Button ());
47 del->set_tooltip_text(M("MAIN_FRAME_PLACES_DEL"));
48 setExpandAlignProperties(del, true, false, Gtk::ALIGN_FILL, Gtk::ALIGN_START);
49 //del->get_style_context()->set_junction_sides(Gtk::JUNCTION_LEFT);
50 del->get_style_context()->add_class("Right");
51 del->set_image (*Gtk::manage (new RTImage ("remove-small.png")));
52 Gtk::Grid* buttonBox = Gtk::manage (new Gtk::Grid ());
53 buttonBox->set_orientation(Gtk::ORIENTATION_HORIZONTAL);
54 buttonBox->attach_next_to(*add, Gtk::POS_LEFT, 1, 1);
55 buttonBox->attach_next_to(*del, *add, Gtk::POS_RIGHT, 1, 1);
56
57 pack_start (*buttonBox, Gtk::PACK_SHRINK, 2);
58
59 treeView = Gtk::manage (new Gtk::TreeView ());
60 treeView->set_can_focus(false);
61 scrollw->add (*treeView);
62
63 placesModel = Gtk::ListStore::create (placesColumns);
64 treeView->set_model (placesModel);
65 treeView->set_headers_visible (true);
66 treeView->set_tooltip_column(2);
67
68 Gtk::TreeView::Column *iviewcol = Gtk::manage (new Gtk::TreeView::Column (M("MAIN_FRAME_PLACES")));
69 Gtk::CellRendererPixbuf *iconCR = Gtk::manage (new Gtk::CellRendererPixbuf());
70 Gtk::CellRendererText *labelCR = Gtk::manage (new Gtk::CellRendererText());
71 labelCR->property_ellipsize() = Pango::ELLIPSIZE_MIDDLE;
72 iviewcol->pack_start (*iconCR, false);
73 iviewcol->pack_start (*labelCR, true);
74 iviewcol->add_attribute (*iconCR, "gicon", 0);
75 iviewcol->add_attribute (*labelCR, "text", placesColumns.label);
76 treeView->append_column (*iviewcol);
77
78 treeView->set_row_separator_func(sigc::mem_fun(*this, &PlacesBrowser::rowSeparatorFunc));
79
80 vm = Gio::VolumeMonitor::get();
81
82 vm->signal_mount_changed().connect (sigc::mem_fun(*this, &PlacesBrowser::mountChanged));
83 vm->signal_mount_added().connect (sigc::mem_fun(*this, &PlacesBrowser::mountChanged));
84 vm->signal_mount_removed().connect (sigc::mem_fun(*this, &PlacesBrowser::mountChanged));
85 vm->signal_volume_changed().connect (sigc::mem_fun(*this, &PlacesBrowser::volumeChanged));
86 vm->signal_volume_added().connect (sigc::mem_fun(*this, &PlacesBrowser::volumeChanged));
87 vm->signal_volume_removed().connect (sigc::mem_fun(*this, &PlacesBrowser::volumeChanged));
88 vm->signal_drive_connected().connect (sigc::mem_fun(*this, &PlacesBrowser::driveChanged));
89 vm->signal_drive_disconnected().connect (sigc::mem_fun(*this, &PlacesBrowser::driveChanged));
90 vm->signal_drive_changed().connect (sigc::mem_fun(*this, &PlacesBrowser::driveChanged));
91
92 treeView->get_selection()->signal_changed().connect(sigc::mem_fun(*this, &PlacesBrowser::selectionChanged));
93 add->signal_clicked().connect(sigc::mem_fun(*this, &PlacesBrowser::addPressed));
94 del->signal_clicked().connect(sigc::mem_fun(*this, &PlacesBrowser::delPressed));
95
96 show_all ();
97 }
98
99 // For drive letter comparison
compareMountByRoot(Glib::RefPtr<Gio::Mount> a,Glib::RefPtr<Gio::Mount> b)100 bool compareMountByRoot (Glib::RefPtr<Gio::Mount> a, Glib::RefPtr<Gio::Mount> b)
101 {
102 return a->get_root()->get_parse_name() < b->get_root()->get_parse_name();
103 }
104
refreshPlacesList()105 void PlacesBrowser::refreshPlacesList ()
106 {
107 placesModel->clear ();
108
109 // append home directory
110 Glib::RefPtr<Gio::File> hfile = Gio::File::create_for_path (userHomeDir()); // Will send back "My documents" on Windows now, which has no restricted access
111
112 if (hfile && hfile->query_exists()) {
113 try {
114 if (auto info = hfile->query_info ()) {
115 Gtk::TreeModel::Row newrow = *(placesModel->append());
116 newrow[placesColumns.label] = info->get_display_name ();
117 newrow[placesColumns.icon] = info->get_icon ();
118 newrow[placesColumns.root] = hfile->get_parse_name ();
119 newrow[placesColumns.type] = 4;
120 newrow[placesColumns.rowSeparator] = false;
121 }
122 } catch (Gio::Error&) {}
123 }
124
125 // append pictures directory
126 auto hfile2 = Gio::File::create_for_path(userPicturesDir());
127
128 if (hfile2 && hfile2->query_exists() && !hfile2->equal(hfile)) {
129 try {
130 if (auto info = hfile2->query_info ()) {
131 Gtk::TreeModel::Row newrow = *(placesModel->append());
132 newrow[placesColumns.label] = info->get_display_name ();
133 newrow[placesColumns.icon] = info->get_icon ();
134 newrow[placesColumns.root] = hfile2->get_parse_name ();
135 newrow[placesColumns.type] = 4;
136 newrow[placesColumns.rowSeparator] = false;
137 }
138 } catch (Gio::Error&) {}
139 }
140
141 // append favorites
142 if (!placesModel->children().empty()) {
143 Gtk::TreeModel::Row newrow = *(placesModel->append());
144 newrow[placesColumns.rowSeparator] = true;
145 }
146
147 for (size_t i = 0; i < options.favoriteDirs.size(); i++) {
148 Glib::RefPtr<Gio::File> hfile = Gio::File::create_for_path (options.favoriteDirs[i]);
149
150 if (hfile && hfile->query_exists()) {
151 try {
152 if (auto info = hfile->query_info ()) {
153 Gtk::TreeModel::Row newrow = *(placesModel->append());
154 newrow[placesColumns.label] = info->get_display_name ();
155 newrow[placesColumns.icon] = info->get_icon ();
156 newrow[placesColumns.root] = hfile->get_parse_name ();
157 newrow[placesColumns.type] = 5;
158 newrow[placesColumns.rowSeparator] = false;
159 }
160 } catch(Gio::Error&) {}
161 }
162 }
163
164 if (!placesModel->children().empty()) {
165 Gtk::TreeModel::Row newrow = *(placesModel->append());
166 newrow[placesColumns.rowSeparator] = true;
167 }
168
169 // scan all drives
170 std::vector<Glib::RefPtr<Gio::Drive> > drives = vm->get_connected_drives ();
171
172 for (size_t j = 0; j < drives.size (); j++) {
173 std::vector<Glib::RefPtr<Gio::Volume> > volumes = drives[j]->get_volumes ();
174
175 if (volumes.empty()) {
176 Gtk::TreeModel::Row newrow = *(placesModel->append());
177 newrow[placesColumns.label] = drives[j]->get_name ();
178 newrow[placesColumns.icon] = drives[j]->get_icon ();
179 newrow[placesColumns.root] = "";
180 newrow[placesColumns.type] = 3;
181 newrow[placesColumns.rowSeparator] = false;
182 }
183
184 for (size_t i = 0; i < volumes.size (); i++) {
185 Glib::RefPtr<Gio::Mount> mount = volumes[i]->get_mount ();
186
187 if (mount) { // placesed volumes
188 Gtk::TreeModel::Row newrow = *(placesModel->append());
189 newrow[placesColumns.label] = mount->get_name ();
190 newrow[placesColumns.icon] = mount->get_icon ();
191 newrow[placesColumns.root] = mount->get_root ()->get_parse_name ();
192 newrow[placesColumns.type] = 1;
193 newrow[placesColumns.rowSeparator] = false;
194 } else { // unplacesed volumes
195 Gtk::TreeModel::Row newrow = *(placesModel->append());
196 newrow[placesColumns.label] = volumes[i]->get_name ();
197 newrow[placesColumns.icon] = volumes[i]->get_icon ();
198 newrow[placesColumns.root] = "";
199 newrow[placesColumns.type] = 2;
200 newrow[placesColumns.rowSeparator] = false;
201 }
202 }
203 }
204
205 // volumes not belonging to drives
206 std::vector<Glib::RefPtr<Gio::Volume> > volumes = vm->get_volumes ();
207
208 for (size_t i = 0; i < volumes.size (); i++) {
209 if (!volumes[i]->get_drive ()) {
210 Glib::RefPtr<Gio::Mount> mount = volumes[i]->get_mount ();
211
212 if (mount) { // placesed volumes
213 Gtk::TreeModel::Row newrow = *(placesModel->append());
214 newrow[placesColumns.label] = mount->get_name ();
215 newrow[placesColumns.icon] = mount->get_icon ();
216 newrow[placesColumns.root] = mount->get_root ()->get_parse_name ();
217 newrow[placesColumns.type] = 1;
218 newrow[placesColumns.rowSeparator] = false;
219 } else { // unplacesed volumes
220 Gtk::TreeModel::Row newrow = *(placesModel->append());
221 newrow[placesColumns.label] = volumes[i]->get_name ();
222 newrow[placesColumns.icon] = volumes[i]->get_icon ();
223 newrow[placesColumns.root] = "";
224 newrow[placesColumns.type] = 2;
225 newrow[placesColumns.rowSeparator] = false;
226 }
227 }
228 }
229
230 // places not belonging to volumes
231 // (Drives in Windows)
232 std::vector<Glib::RefPtr<Gio::Mount> > mounts = vm->get_mounts ();
233
234 #ifdef WIN32
235 // on Windows, it's usual to sort by drive letter, not by name
236 std::sort (mounts.begin(), mounts.end(), compareMountByRoot);
237 #endif
238
239 for (size_t i = 0; i < mounts.size (); i++) {
240 if (!mounts[i]->get_volume ()) {
241 Gtk::TreeModel::Row newrow = *(placesModel->append());
242 newrow[placesColumns.label] = mounts[i]->get_name ();
243 newrow[placesColumns.icon] = mounts[i]->get_icon ();
244 newrow[placesColumns.root] = mounts[i]->get_root ()->get_parse_name ();
245 newrow[placesColumns.type] = 1;
246 newrow[placesColumns.rowSeparator] = false;
247 }
248 }
249 }
250
rowSeparatorFunc(const Glib::RefPtr<Gtk::TreeModel> & model,const Gtk::TreeModel::iterator & iter)251 bool PlacesBrowser::rowSeparatorFunc (const Glib::RefPtr<Gtk::TreeModel>& model, const Gtk::TreeModel::iterator& iter)
252 {
253
254 return iter->get_value (placesColumns.rowSeparator);
255 }
256
mountChanged(const Glib::RefPtr<Gio::Mount> & m)257 void PlacesBrowser::mountChanged (const Glib::RefPtr<Gio::Mount>& m)
258 {
259 GThreadLock lock;
260 refreshPlacesList ();
261 }
262
volumeChanged(const Glib::RefPtr<Gio::Volume> & m)263 void PlacesBrowser::volumeChanged (const Glib::RefPtr<Gio::Volume>& m)
264 {
265 GThreadLock lock;
266 refreshPlacesList ();
267 }
268
driveChanged(const Glib::RefPtr<Gio::Drive> & m)269 void PlacesBrowser::driveChanged (const Glib::RefPtr<Gio::Drive>& m)
270 {
271 GThreadLock lock;
272 refreshPlacesList ();
273 }
274
selectionChanged()275 void PlacesBrowser::selectionChanged ()
276 {
277
278 Glib::RefPtr<Gtk::TreeSelection> selection = treeView->get_selection();
279 Gtk::TreeModel::iterator iter = selection->get_selected();
280
281 if (iter) {
282 if (iter->get_value (placesColumns.type) == 2) {
283 std::vector<Glib::RefPtr<Gio::Volume> > volumes = vm->get_volumes ();
284
285 for (size_t i = 0; i < volumes.size(); i++)
286 if (volumes[i]->get_name () == iter->get_value (placesColumns.label)) {
287 volumes[i]->mount ();
288 break;
289 }
290 } else if (iter->get_value (placesColumns.type) == 3) {
291 std::vector<Glib::RefPtr<Gio::Drive> > drives = vm->get_connected_drives ();
292
293 for (size_t i = 0; i < drives.size(); i++)
294 if (drives[i]->get_name () == iter->get_value (placesColumns.label)) {
295 drives[i]->poll_for_media ();
296 break;
297 }
298 } else if (selectDir) {
299 selectDir (iter->get_value (placesColumns.root));
300 }
301 }
302 }
303
dirSelected(const Glib::ustring & dirname,const Glib::ustring & openfile)304 void PlacesBrowser::dirSelected (const Glib::ustring& dirname, const Glib::ustring& openfile)
305 {
306
307 lastSelectedDir = dirname;
308 }
309
addPressed()310 void PlacesBrowser::addPressed ()
311 {
312
313 if (lastSelectedDir == "") {
314 return;
315 }
316
317 // check if the dirname is already in the list. If yes, return.
318 for (size_t i = 0; i < options.favoriteDirs.size(); i++)
319 if (options.favoriteDirs[i] == lastSelectedDir) {
320 return;
321 }
322
323 // append
324 Glib::RefPtr<Gio::File> hfile = Gio::File::create_for_path (lastSelectedDir);
325
326 if (hfile && hfile->query_exists()) {
327 try {
328 if (auto info = hfile->query_info ()) {
329 options.favoriteDirs.push_back (hfile->get_parse_name ());
330 refreshPlacesList ();
331 }
332 } catch(Gio::Error&) {}
333 }
334 }
335
delPressed()336 void PlacesBrowser::delPressed ()
337 {
338
339 // lookup the selected item in the bookmark
340 Glib::RefPtr<Gtk::TreeSelection> selection = treeView->get_selection();
341 Gtk::TreeModel::iterator iter = selection->get_selected();
342
343 if (iter && iter->get_value (placesColumns.type) == 5) {
344 std::vector<Glib::ustring>::iterator i = std::find (options.favoriteDirs.begin(), options.favoriteDirs.end(), iter->get_value (placesColumns.root));
345
346 if (i != options.favoriteDirs.end()) {
347 options.favoriteDirs.erase (i);
348 }
349 }
350
351 refreshPlacesList ();
352 }
353
userHomeDir()354 Glib::ustring PlacesBrowser::userHomeDir ()
355 {
356 #ifdef WIN32
357
358 // get_home_dir crashes on some Windows configurations,
359 // so we rather use the safe native functions here.
360 WCHAR pathW[MAX_PATH];
361 if (SHGetSpecialFolderPathW (NULL, pathW, CSIDL_PERSONAL, false)) {
362
363 char pathA[MAX_PATH];
364 if (WideCharToMultiByte (CP_UTF8, 0, pathW, -1, pathA, MAX_PATH, 0, 0)) {
365
366 return Glib::ustring (pathA);
367 }
368 }
369
370 return Glib::ustring ("C:\\");
371
372 #else
373
374 return Glib::get_home_dir ();
375
376 #endif
377 }
378
userPicturesDir()379 Glib::ustring PlacesBrowser::userPicturesDir ()
380 {
381 #ifdef WIN32
382
383 // get_user_special_dir crashes on some Windows configurations,
384 // so we rather use the safe native functions here.
385 WCHAR pathW[MAX_PATH];
386 if (SHGetSpecialFolderPathW (NULL, pathW, CSIDL_MYPICTURES, false)) {
387
388 char pathA[MAX_PATH];
389 if (WideCharToMultiByte (CP_UTF8, 0, pathW, -1, pathA, MAX_PATH, 0, 0)) {
390
391 return Glib::ustring (pathA);
392 }
393 }
394
395 return Glib::ustring ("C:\\");
396
397 #else
398
399 return Glib::get_user_special_dir (G_USER_DIRECTORY_PICTURES);
400
401 #endif
402 }
403