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