]> Dogcows Code - chaz/tint2/blob - src/tint2conf/thumbview.cpp
play with some tint2conf code
[chaz/tint2] / src / tint2conf / thumbview.cpp
1 /*
2
3 This file is from Nitrogen, an X11 background setter.
4 Copyright (C) 2006 Dave Foster & Javeed Shaikh
5
6 This program is free software; you can redistribute it and/or
7 modify it under the terms of the GNU General Public License
8 as published by the Free Software Foundation; either version 2
9 of the License, or (at your option) any later version.
10
11 This program 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 this program; if not, write to the Free Software
18 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19
20 */
21
22 //#include "md5.h"
23 #include <glib/gstdio.h>
24 #include <png.h>
25 #include <sys/types.h>
26 #include <sys/stat.h>
27 #include <unistd.h>
28 #include <iostream>
29 #include "thumbview.h"
30 //#include "Config.h"
31 //#include "Util.h"
32 //#include "gcs-i18n.h"
33
34 /**
35 * Returns the last modified time of a file.
36 * @param file The name of the file to get the modified time for.
37 */
38 static time_t get_file_mtime(std::string file) {
39 struct stat buf;
40 if (stat(file.c_str(), &buf) == -1) return 0; // error
41 return buf.st_mtime;
42 }
43
44 /**
45 * Returns the value of the "tEXt::Thumb::MTime" key for fd.o style thumbs.
46 * @param pixbuf The pixbuf of the fd.o thumbnail.
47 */
48 static time_t get_fdo_thumbnail_mtime(Glib::RefPtr<Gdk::Pixbuf> pixbuf) {
49 std::string mtime_str = pixbuf->get_option("tEXt::Thumb::MTime");
50 std::stringstream stream(mtime_str);
51 time_t mtime = 0;
52 stream >> mtime;
53 return mtime;
54 }
55
56 void DelayLoadingStore::get_value_vfunc (const iterator& iter, int column, Glib::ValueBase& value) const
57 {
58 g_async_queue_ref(aqueue_loadthumbs);
59 Gtk::ListStore::get_value_vfunc(iter, column, value);
60
61 Gtk::TreeModel::Row row = *iter;
62 if (column == 0)
63 {
64 Glib::Value< Glib::RefPtr<Gdk::Pixbuf> > base;
65 Gtk::ListStore::get_value_vfunc(iter, column, base);
66
67 Glib::RefPtr<Gdk::Pixbuf> thumb = base.get();
68
69 if (thumb == thumbview->loading_image && !row[thumbview->record.LoadingThumb])
70 {
71 TreePair* tp = new TreePair();
72 tp->file = row[thumbview->record.Filename];
73 tp->iter = iter;
74
75 row[thumbview->record.LoadingThumb] = true;
76
77 //Util::program_log("Custom model: planning on loading %s\n", tp->file.c_str());
78
79 g_async_queue_push(aqueue_loadthumbs, (gpointer)tp);
80 }
81 }
82 g_async_queue_unref(aqueue_loadthumbs);
83 }
84
85 /**
86 * Constructor, sets up gtk stuff, inits data and queues
87 */
88 Thumbview::Thumbview() : dir("") {
89 set_policy (Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
90 set_shadow_type (Gtk::SHADOW_IN);
91
92 // load map of setbgs (need this for add_file)
93 load_map_setbgs();
94
95 // make our async queues
96 this->aqueue_loadthumbs = g_async_queue_new();
97 this->aqueue_createthumbs = g_async_queue_new();
98 this->aqueue_donethumbs = g_async_queue_new();
99
100 // create store
101 store = DelayLoadingStore::create (record);
102 store->set_queue(aqueue_loadthumbs);
103 store->set_thumbview(this);
104
105 // setup view
106 view.set_model (store);
107 view.set_headers_visible (FALSE);
108 view.set_fixed_height_mode (TRUE);
109 view.set_rules_hint (TRUE);
110
111 // set cell renderer proprties
112 rend.property_ellipsize () = Pango::ELLIPSIZE_END;
113 rend.set_property ("ellipsize", Pango::ELLIPSIZE_END);
114 rend.property_weight () = Pango::WEIGHT_BOLD;
115
116 rend_img.set_fixed_size(105, 82);
117
118 // make treeviewcolumns
119 this->col_thumb = new Gtk::TreeViewColumn("thumbnail", this->rend_img);
120 this->col_desc = new Gtk::TreeViewColumn("description", this->rend);
121
122 col_thumb->add_attribute (rend_img, "pixbuf", record.Thumbnail);
123 col_thumb->set_sizing(Gtk::TREE_VIEW_COLUMN_FIXED);
124 col_thumb->set_fixed_width(105);
125 col_desc->add_attribute (rend, "markup", record.Description);
126 col_desc->set_sort_column (record.Filename);
127 col_desc->set_sort_indicator (true);
128 col_desc->set_sort_order (Gtk::SORT_ASCENDING);
129 col_desc->set_sizing(Gtk::TREE_VIEW_COLUMN_FIXED);
130
131 view.append_column (*col_thumb);
132 view.append_column (*col_desc);
133
134 // enable search
135 view.set_search_column (record.Description);
136 view.set_search_equal_func (sigc::mem_fun (this, &Thumbview::search_compare));
137
138 // load loading image, which not all themes seem to provide
139 try {
140 this->loading_image = Gtk::IconTheme::get_default()->load_icon("image-loading", 64, Gtk::ICON_LOOKUP_FORCE_SVG);
141 } catch (Gtk::IconThemeError e) {}
142
143 // init dispatcher
144 this->dispatch_thumb.connect(sigc::mem_fun(this, &Thumbview::handle_dispatch_thumb));
145
146 col_desc->set_expand ();
147
148 add (view);
149
150 view.show ();
151 show ();
152 }
153
154 /**
155 * Destructor
156 */
157 Thumbview::~Thumbview() {
158 g_async_queue_unref(this->aqueue_loadthumbs);
159 g_async_queue_unref(this->aqueue_createthumbs);
160 g_async_queue_unref(this->aqueue_donethumbs);
161 }
162
163 /**
164 * Adds the given file to the tree view and pushes it onto the thumbnail
165 * creation queue.
166 *
167 * @param filename The name of the file to add.
168 *
169 */
170 void Thumbview::add_file(std::string filename) {
171 Gtk::Window *window = dynamic_cast<Gtk::Window*>(get_toplevel());
172 Gtk::TreeModel::iterator iter = this->store->append ();
173 Gtk::TreeModel::Row row = *iter;
174 Glib::RefPtr<Gdk::Pixbuf> thumb = this->loading_image;
175 row[record.Thumbnail] = thumb;
176 row[record.Filename] = filename;
177 row[record.Description] = Glib::ustring(filename, filename.rfind ("/")+1);
178
179 for (std::map<Glib::ustring, Glib::ustring>::iterator i = map_setbgs.begin(); i!=map_setbgs.end(); i++)
180 {
181 if (filename == (*i).second)
182 {
183 row[record.CurBGOnDisp] = (*i).first;
184 // row[record.Description] = Util::make_current_set_string(window, filename, (*i).first);
185 }
186 }
187
188 // for modified time
189 row[record.Time] = get_file_mtime(filename);
190
191 // Util::program_log("add_file(): Adding file %s\n", filename.c_str());
192
193 // push it on the thumb queue
194 // TreePair *tp = new TreePair();
195 // tp->file = filename;
196 // tp->iter = iter;
197
198 // queue_thumbs.push(tp);
199 }
200
201
202 /**
203 * Opens the internal directory and starts reading files into the async queue.
204 */
205 void Thumbview::load_dir(std::string dir) {
206 if (!dir.length()) dir = this->dir;
207
208 std::queue<Glib::ustring> subdirs;
209 Glib::Dir *dirhandle;
210
211 // push the initial dir back onto subdirs
212 subdirs.push(dir);
213
214 // loop it
215 while ( ! subdirs.empty() ) {
216
217 // pop first
218 Glib::ustring curdir = subdirs.front();
219 subdirs.pop();
220
221 try {
222 dirhandle = new Glib::Dir(curdir);
223 // Util::program_log("load_dir(): Opening dir %s\n", curdir.c_str());
224
225 } catch (Glib::FileError e) {
226 std::cerr << "Could not open dir" << " " << this->dir << ": " << e.what() << "\n";
227 continue;
228 }
229
230 #ifdef USE_INOTIFY
231
232 // check if we're already monitoring this dir.
233 if (watches.find(curdir) == watches.end()) {
234 // this dir was successfully opened. monitor it for changes with inotify.
235 // the Watch will be cleaned up automatically if the dir is deleted.
236 Inotify::Watch * watch = Inotify::Watch::create(curdir);
237 if (watch) {
238 // no error occurred.
239
240 // emitted when a file is deleted in this dir.
241 watch->signal_deleted.connect(sigc::mem_fun(this,
242 &Thumbview::file_deleted_callback));
243 // emitted when a file is modified or created in this dir.
244 watch->signal_write_closed.connect(sigc::mem_fun(this,
245 &Thumbview::file_changed_callback));
246 // two signals that are emitted when a file is renamed in this dir.
247 // the best way to handle this IMO is to remove the file upon receiving
248 // 'moved_from', and then to add the file upon receiving 'moved_to'.
249 watch->signal_moved_from.connect(sigc::mem_fun(this,
250 &Thumbview::file_deleted_callback));
251 watch->signal_moved_to.connect(sigc::mem_fun(this,
252 &Thumbview::file_changed_callback));
253 watch->signal_created.connect(sigc::mem_fun(this,
254 &Thumbview::file_created_callback));
255
256 watches[curdir] = watch;
257 }
258 }
259
260 #endif
261
262 for (Glib::Dir::iterator i = dirhandle->begin(); i != dirhandle->end(); i++) {
263 Glib::ustring fullstr = curdir + Glib::ustring("/");
264 try {
265 fullstr += /*Glib::filename_to_utf8(*/*i;//);
266 } catch (Glib::ConvertError& error) {
267 std::cerr << "Invalid UTF-8 encountered. Skipping file" << " " << *i << std::endl;
268 continue;
269 }
270
271 if ( Glib::file_test(fullstr, Glib::FILE_TEST_IS_DIR) )
272 {
273 // if ( Config::get_instance()->get_recurse() )
274 // subdirs.push(fullstr);
275 }
276 else {
277 if ( this->is_image(fullstr) ) {
278 add_file(fullstr);
279 }
280 }
281 }
282
283 delete dirhandle;
284 }
285 }
286
287 /**
288 * Tests the file to see if it is an image
289 * TODO: come up with less sux way of doing it than extension
290 *
291 * @param file The filename to test
292 * @return If its an image or not
293 */
294 bool Thumbview::is_image(std::string file) {
295 if (file.find (".jpg") != std::string::npos ||
296 file.find (".JPG") != std::string::npos ||
297 file.find (".jpeg") != std::string::npos ||
298 file.find (".JPEG") != std::string::npos )
299 return true;
300
301 return false;
302 }
303
304 /**
305 * Determines the full path to a cache file. Does all creation of dirs.
306 * TODO: should throw exception if we cannot create the dirs!
307 *
308 * @param file The file to convert, calls cache_name on it
309 * @return The full path to
310 */
311 Glib::ustring Thumbview::cache_file(Glib::ustring file) {
312
313 Glib::ustring urifile = Glib::filename_to_uri(file);
314 /*
315 // compute md5 of file's uri
316 md5_state_t state;
317 md5_byte_t digest[16];
318 md5_init(&state);
319 md5_append(&state, (const md5_byte_t *)urifile.c_str(), strlen(urifile.c_str()));
320 md5_finish(&state, digest);
321
322 char *buf = new char[3];
323 char *full = new char[33];
324 full[0] = '\0';
325
326 for (int di = 0; di < 16; ++di) {
327 sprintf(buf, "%02x", digest[di]);
328 strcat(full, buf);
329 }
330
331 Glib::ustring md5file = Glib::ustring(full) + Glib::ustring(".png");
332 delete [] buf;
333 delete [] full;
334
335 // build dir paths
336 Glib::ustring halfref = Glib::build_filename(Glib::get_home_dir(),".thumbnails/");
337 halfref = Glib::build_filename(halfref, "normal/");
338
339 if ( !Glib::file_test(halfref, Glib::FILE_TEST_EXISTS) )
340 if ( g_mkdir_with_parents(halfref.c_str(), 0700) == -1)
341 // TODO: exception
342 return "FAIL";
343
344 // add and return
345 return Glib::build_filename(halfref, md5file);
346 */
347 return urifile;
348 }
349
350 /**
351 * Creates cache images that show up in its queue.
352 */
353 void Thumbview::load_cache_images()
354 {
355 g_async_queue_ref(this->aqueue_loadthumbs);
356 g_async_queue_ref(this->aqueue_donethumbs);
357
358 while(1)
359 {
360 // remove first item (blocks until an item occurs)
361 TreePair *p = (TreePair*)g_async_queue_pop(this->aqueue_loadthumbs);
362
363 Glib::ustring file = p->file;
364 Glib::ustring cachefile = this->cache_file(file);
365
366 // branch to see if we need to load or create cache file
367 if ( !Glib::file_test(cachefile, Glib::FILE_TEST_EXISTS) ) {
368 g_async_queue_push(this->aqueue_createthumbs,(gpointer)p);
369 } else {
370 // load thumb
371 Glib::RefPtr<Gdk::Pixbuf> pb = Gdk::Pixbuf::create_from_file(this->cache_file(file));
372
373 // resize if we need to
374 if (pb->get_width() > 100 || pb->get_height() > 80)
375 {
376 int pbwidth = pb->get_width();
377 int pbheight = pb->get_height();
378 float ratio = (float)pbwidth / (float)pbheight;
379
380 int newwidth, newheight;
381
382 if (abs(100 - pbwidth) > abs(80 - pbheight))
383 {
384 // cap to vertical
385 newheight = 80;
386 newwidth = newheight * ratio;
387 }
388 else
389 {
390 // cap to horiz
391 newwidth = 100;
392 newheight = newwidth / ratio;
393 }
394
395 pb = pb->scale_simple(newwidth, newheight, Gdk::INTERP_NEAREST);
396 }
397
398 if (get_fdo_thumbnail_mtime(pb) < get_file_mtime(file)) {
399 // the thumbnail is old. we need to make a new one.
400 pb.clear();
401 g_async_queue_push(this->aqueue_createthumbs,(gpointer)p);
402 } else {
403 // display it
404 TreePair *sendp = new TreePair();
405 sendp->file = file;
406 sendp->iter = p->iter;
407 sendp->thumb = pb;
408
409 g_async_queue_push(this->aqueue_donethumbs, (gpointer)sendp);
410
411 this->dispatch_thumb.emit();
412
413 delete p;
414 }
415 }
416 }
417
418 g_async_queue_unref(this->aqueue_loadthumbs);
419 g_async_queue_unref(this->aqueue_donethumbs);
420 throw Glib::Thread::Exit();
421 }
422
423 /**
424 * Thread function to create thumbnail cache images for those that do not exist.
425 */
426 void Thumbview::create_cache_images()
427 {
428 Glib::RefPtr<Gdk::Pixbuf> thumb;
429
430 g_async_queue_ref(this->aqueue_createthumbs);
431 g_async_queue_ref(this->aqueue_donethumbs);
432
433 // always work!
434 while (1) {
435
436 // remove first item (blocks when no items occur)
437 TreePair *p = (TreePair*)g_async_queue_pop(this->aqueue_createthumbs);
438
439 // get filenames
440 Glib::ustring file = p->file;
441 Glib::ustring cachefile = this->cache_file(file);
442
443 // Util::program_log("create_cache_images(): Caching file %s\n", file.c_str());
444
445 // open image
446 try {
447 thumb = Gdk::Pixbuf::create_from_file(file);
448 } catch (...) {
449 // forget it, move on
450 delete p;
451 continue;
452 }
453
454 // eliminate zero heights (due to really tiny images :/)
455 int height = (int)(100*((float)thumb->get_height()/(float)thumb->get_width()));
456 if (!height) height = 1;
457
458 // create thumb
459 thumb = thumb->scale_simple(100, height, Gdk::INTERP_TILES);
460
461 // create required fd.o png tags
462 std::list<Glib::ustring> opts, vals;
463 opts.push_back(Glib::ustring("tEXt::Thumb::URI"));
464 vals.push_back(Glib::filename_to_uri(file));
465
466 time_t mtime = get_file_mtime(file);
467
468 char *bufout = new char[20];
469 sprintf(bufout, "%d", mtime);
470
471 opts.push_back(Glib::ustring("tEXt::Thumb::MTime"));
472 vals.push_back(Glib::ustring(bufout));
473
474 delete [] bufout;
475
476 thumb->save(cachefile, "png", opts, vals);
477
478 // send it to the display
479 TreePair *sendp = new TreePair();
480 sendp->file = file;
481 sendp->iter = p->iter;
482 sendp->thumb = thumb;
483 g_async_queue_push(this->aqueue_donethumbs, (gpointer)sendp);
484
485 // emit dispatcher
486 this->dispatch_thumb.emit();
487
488 delete p;
489 }
490
491 g_async_queue_unref(this->aqueue_createthumbs);
492 g_async_queue_unref(this->aqueue_donethumbs);
493 throw Glib::Thread::Exit();
494 }
495
496 void Thumbview::handle_dispatch_thumb() {
497 g_async_queue_ref(this->aqueue_donethumbs);
498
499 TreePair *donep = (TreePair*)g_async_queue_pop(this->aqueue_donethumbs);
500 this->update_thumbnail(donep->file, donep->iter, donep->thumb);
501 delete donep;
502
503 g_async_queue_unref(this->aqueue_donethumbs);
504 }
505
506 /**
507 * Updates the treeview to show the passed in thumbnail.
508 *
509 */
510 void Thumbview::update_thumbnail(Glib::ustring file, Gtk::TreeModel::iterator iter, Glib::RefPtr<Gdk::Pixbuf> pb)
511 {
512 Gtk::TreeModel::Row row = *iter;
513 row[record.Thumbnail] = pb;
514 // desc
515 //row[record.Description] = Glib::ustring(file, file.rfind("/")+1);
516
517 // emit a changed signal
518 store->row_changed(store->get_path(iter), iter);
519 }
520
521 /**
522 * Used by GTK to see whether or not an iterator matches a search string.
523 */
524 bool Thumbview::search_compare (const Glib::RefPtr<Gtk::TreeModel>& model, int column, const Glib::ustring& key, const Gtk::TreeModel::iterator& iter) {
525 Glib::ustring target = (*iter)[record.Description];
526 if (target.find (key) != Glib::ustring::npos) {
527 return false;
528 }
529 return true;
530 }
531
532 /**
533 * Sets the sort mode. This tells the view how it should sort backgrounds.
534 */
535 void Thumbview::set_sort_mode (Thumbview::SortMode mode) {
536 switch (mode) {
537 case SORT_ALPHA:
538 store->set_sort_column (record.Description, Gtk::SORT_ASCENDING);
539 break;
540 case SORT_RALPHA:
541 store->set_sort_column (record.Description, Gtk::SORT_DESCENDING);
542 break;
543 case SORT_TIME:
544 store->set_sort_column (record.Time, Gtk::SORT_ASCENDING);
545 break;
546 case SORT_RTIME:
547 store->set_sort_column (record.Time, Gtk::SORT_DESCENDING);
548 break;
549 }
550 }
551
552 /**
553 * Loads the map of displays and their set bgs. Used to indicate which
554 * files are currently set as a background.
555 */
556 void Thumbview::load_map_setbgs()
557 {
558 map_setbgs.clear();
559
560 std::vector<Glib::ustring> vecgroups;
561 bool ret = false;
562 // bool ret = Config::get_instance()->get_bg_groups(vecgroups);
563 if (!ret)
564 return;
565
566 for (std::vector<Glib::ustring>::iterator i = vecgroups.begin(); i!=vecgroups.end(); i++)
567 {
568 Glib::ustring file;
569 // SetBG::SetMode mode;
570 Gdk::Color bgcolor;
571
572 // ret = Config::get_instance()->get_bg(*i, file, mode, bgcolor);
573 if (!ret)
574 {
575 std::cerr << "(load_map_setbgs) Could not get background stored info for " << *i << "\n";
576 return;
577 }
578
579 map_setbgs[*i] = file;
580 }
581 }
582
583 #ifdef USE_INOTIFY
584
585 /**
586 * Called when a file in a directory being monitored by inotify is deleted.
587 * Removes the file from the tree view.
588 *
589 * @param filename The name of the file to remove.
590 */
591 void Thumbview::file_deleted_callback(std::string filename) {
592 if (watches.find(filename) != watches.end()) {
593 watches.erase(filename);
594 return;
595 }
596 Gtk::TreeIter iter;
597 Gtk::TreeModel::Children children = store->children();
598 for (iter = children.begin(); iter != children.end(); iter++) {
599 Glib::ustring this_filename = (*iter)[record.Filename];
600 if (this_filename == filename) {
601 // remove this iter.
602 store->erase(iter);
603 break;
604 }
605 }
606 }
607
608 /**
609 * Called when a file in a directory being monitored by inotify is modified
610 * or a new file is created. Adds the file to the tree view.
611 *
612 * @param filename The name of the file modified or created.
613 */
614 void Thumbview::file_changed_callback(std::string filename) {
615 // first remove the old instance of this file.
616 file_deleted_callback(filename);
617 if ( Glib::file_test(filename, Glib::FILE_TEST_IS_DIR) ) {
618 // this is a directory; use load_dir() to set us up the bomb.
619 load_dir(filename);
620 } else if (is_image(filename)) {
621 // this is a file.
622 add_file(filename);
623 }
624 // restart the idle function
625 //Glib::signal_idle().connect(sigc::mem_fun(this, &Thumbview::load_cache_images));
626 }
627
628 /**
629 * Called when a new file or directory is created in a directory being
630 * monitored. Discards the event for non-directories, because
631 * file_changed_callback will be called for those.
632 *
633 * @param filename The name of the file modified or created.
634 */
635 void Thumbview::file_created_callback(std::string filename) {
636 if ( Glib::file_test(filename, Glib::FILE_TEST_IS_DIR) ) {
637 file_changed_callback(filename);
638 }
639 }
640
641 #endif
This page took 0.063287 seconds and 4 git commands to generate.