Fix two horrible memory leaks of EntangleImage
[entangle:entangle.git] / src / frontend / entangle-session-browser.c
1 /*
2  *  Entangle: Tethered Camera Control & Capture
3  *
4  *  Copyright (C) 2009-2012 Daniel P. Berrange
5  *
6  *  This program 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  *  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, see <http://www.gnu.org/licenses/>.
18  *
19  */
20
21 #include <config.h>
22
23 #include <string.h>
24
25 #include "entangle-debug.h"
26 #include "entangle-session-browser.h"
27
28 #define ENTANGLE_SESSION_BROWSER_PRIORITY_LAYOUT (GDK_PRIORITY_REDRAW + 5)
29
30 #define ENTANGLE_SESSION_BROWSER_GET_PRIVATE(obj)                           \
31     (G_TYPE_INSTANCE_GET_PRIVATE((obj), ENTANGLE_TYPE_SESSION_BROWSER, EntangleSessionBrowserPrivate))
32
33 typedef struct _EntangleSessionBrowserItem EntangleSessionBrowserItem;
34 struct _EntangleSessionBrowserItem
35 {
36     /* First member is always the rectangle so it
37      * can be cast to a rectangle. */
38     GdkRectangle cell_area;
39
40     GtkTreeIter iter;
41     gint index;
42
43     gint col;
44
45     guint selected : 1;
46     guint selected_before_rubberbanding : 1;
47 };
48
49 struct _EntangleSessionBrowserPrivate {
50     EntangleSession *session;
51     EntangleThumbnailLoader *loader;
52
53     GtkCellArea *cell_area;
54     GtkCellAreaContext *cell_area_context;
55
56     GtkCellRenderer *pixbuf_cell;
57     GtkCellRenderer *text_cell;
58
59     gulong sigImageAdded;
60     gulong sigThumbReady;
61     gulong context_changed_id;
62
63     GdkPixbuf *blank;
64
65     GtkTreeModel *model;
66     EntangleImage *selected;
67
68     GList *items;
69
70     GtkAdjustment *hadjustment;
71     GtkAdjustment *vadjustment;
72
73     GtkTreeRowReference *scroll_to_path;
74     gfloat scroll_to_row_align;
75     gfloat scroll_to_col_align;
76     guint scroll_to_use_align : 1;
77
78     /* GtkScrollablePolicy needs to be checked when
79      * driving the scrollable adjustment values */
80     guint hscroll_policy : 1;
81     guint vscroll_policy : 1;
82
83     guint width;
84     guint height;
85
86     guint layout_idle_id;
87
88     GdkWindow *bin_window;
89
90     gint margin;
91     gint item_padding;
92     gint column_spacing;
93 };
94
95
96 static void
97 entangle_session_browser_adjustment_changed(GtkAdjustment *adjustment,
98                                             EntangleSessionBrowser *browser);
99
100 static void
101 entangle_session_browser_set_hadjustment(EntangleSessionBrowser *browser,
102                                          GtkAdjustment *adjustment);
103 static void
104 entangle_session_browser_set_vadjustment(EntangleSessionBrowser *browser,
105                                          GtkAdjustment *adjustment);
106
107 static void
108 entangle_session_browser_set_hadjustment_values(EntangleSessionBrowser *browser);
109 static void
110 entangle_session_browser_set_vadjustment_values(EntangleSessionBrowser *browser);
111
112 static gboolean
113 entangle_session_browser_draw(GtkWidget *widget,
114                               cairo_t *cr);
115
116 static void
117 entangle_session_browser_queue_layout(EntangleSessionBrowser *browser);
118
119 static void
120 entangle_session_browser_cell_layout_init(GtkCellLayoutIface *iface);
121 static void
122 entangle_session_browser_select_path(EntangleSessionBrowser *browser,
123                                      GtkTreePath *path);
124
125 static void
126 entangle_session_browser_scroll_to_path(EntangleSessionBrowser *browser,
127                                         GtkTreePath *path,
128                                         gboolean     use_align,
129                                         gfloat       row_align,
130                                         gfloat       col_align);
131
132 static void
133 entangle_session_browser_layout(EntangleSessionBrowser *browser);
134
135
136 G_DEFINE_TYPE_WITH_CODE(EntangleSessionBrowser, entangle_session_browser, GTK_TYPE_DRAWING_AREA,
137                         G_IMPLEMENT_INTERFACE(GTK_TYPE_CELL_LAYOUT,
138                                               entangle_session_browser_cell_layout_init)
139                         G_IMPLEMENT_INTERFACE (GTK_TYPE_SCROLLABLE, NULL))
140
141 enum {
142     PROP_O,
143     PROP_SESSION,
144     PROP_LOADER,
145     PROP_HADJUSTMENT,
146     PROP_VADJUSTMENT,
147     PROP_HSCROLL_POLICY,
148     PROP_VSCROLL_POLICY,
149 };
150
151 enum {
152     FIELD_IMAGE,
153     FIELD_PIXMAP,
154     FIELD_LASTMOD,
155     FIELD_NAME,
156
157     FIELD_LAST,
158 };
159
160 enum {
161     SIGNAL_SELECTION_CHANGED,
162
163     SIGNAL_LAST,
164 };
165
166 static guint browser_signals[SIGNAL_LAST] = { 0 };
167
168
169 static void do_thumb_loaded(EntanglePixbufLoader *loader,
170                             EntangleImage *image,
171                             gpointer data)
172 {
173     EntangleSessionBrowser *browser = data;
174     EntangleSessionBrowserPrivate *priv = browser->priv;
175     GdkPixbuf *pixbuf;
176     GtkTreeIter iter;
177
178     ENTANGLE_DEBUG("Got pixbuf update on %p", image);
179
180     pixbuf = entangle_pixbuf_loader_get_pixbuf(loader, image);
181     if (!pixbuf)
182         return;
183
184     if (!gtk_tree_model_get_iter_first(priv->model, &iter))
185         return;
186
187     do {
188         EntangleImage *thisimage;
189         gtk_tree_model_get(priv->model, &iter, FIELD_IMAGE, &thisimage, -1);
190
191         if (image == thisimage) {
192             g_object_unref(thisimage);
193             gtk_list_store_set(GTK_LIST_STORE(priv->model),
194                                &iter, FIELD_PIXMAP, pixbuf, -1);
195             break;
196         }
197         g_object_unref(thisimage);
198     } while (gtk_tree_model_iter_next(priv->model, &iter));
199 }
200
201 static void do_image_added(EntangleSession *session G_GNUC_UNUSED,
202                            EntangleImage *img,
203                            gpointer data)
204 {
205     EntangleSessionBrowser *browser = data;
206     EntangleSessionBrowserPrivate *priv = browser->priv;
207     GtkTreeIter iter;
208     GtkTreePath *path = NULL;
209     int mod = entangle_image_get_last_modified(img);
210     gchar *name = g_path_get_basename(entangle_image_get_filename(img));
211
212     ENTANGLE_DEBUG("Request image %s for new image", entangle_image_get_filename(img));
213     entangle_pixbuf_loader_load(ENTANGLE_PIXBUF_LOADER(priv->loader), img);
214
215     gtk_list_store_append(GTK_LIST_STORE(priv->model), &iter);
216
217     gtk_list_store_set(GTK_LIST_STORE(priv->model),
218                        &iter,
219                        FIELD_IMAGE, img,
220                        FIELD_PIXMAP, priv->blank,
221                        FIELD_LASTMOD, mod,
222                        FIELD_NAME, name,
223                        -1);
224     ENTANGLE_DEBUG("ADD IMAGE EXTRA %p", img);
225     path = gtk_tree_model_get_path(priv->model, &iter);
226
227     entangle_session_browser_select_path(browser, path);
228     entangle_session_browser_scroll_to_path(browser, path, FALSE, 0, 0);
229
230     gtk_tree_path_free(path);
231
232     gtk_widget_queue_resize(GTK_WIDGET(browser));
233 }
234
235
236
237 static void do_model_unload(EntangleSessionBrowser *browser)
238 {
239     EntangleSessionBrowserPrivate *priv = browser->priv;
240     int count;
241
242     ENTANGLE_DEBUG("Unload model");
243
244     g_signal_handler_disconnect(priv->session,
245                                 priv->sigImageAdded);
246     g_signal_handler_disconnect(priv->loader,
247                                 priv->sigThumbReady);
248
249     count = entangle_session_image_count(priv->session);
250     for (int i = 0 ; i < count ; i++) {
251         EntangleImage *img = entangle_session_image_get(priv->session, i);
252         entangle_pixbuf_loader_unload(ENTANGLE_PIXBUF_LOADER(priv->loader), img);
253     }
254
255     g_object_unref(priv->blank);
256     gtk_list_store_clear(GTK_LIST_STORE(priv->model));
257 }
258
259 static void do_model_load(EntangleSessionBrowser *browser)
260 {
261     EntangleSessionBrowserPrivate *priv = browser->priv;
262     int count;
263     int width;
264     int height;
265
266     ENTANGLE_DEBUG("Load model");
267
268     g_object_get(priv->loader,
269                  "width", &width,
270                  "height", &height,
271                  NULL);
272
273     priv->blank = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, width, height);
274     gdk_pixbuf_fill(priv->blank, 0x000000FF);
275
276     priv->sigImageAdded = g_signal_connect(priv->session, "session-image-added",
277                                            G_CALLBACK(do_image_added), browser);
278     priv->sigThumbReady = g_signal_connect(priv->loader, "pixbuf-loaded",
279                                            G_CALLBACK(do_thumb_loaded), browser);
280
281     count = entangle_session_image_count(priv->session);
282     for (int i = 0 ; i < count ; i++) {
283         EntangleImage *img = entangle_session_image_get(priv->session, i);
284         int mod = entangle_image_get_last_modified(img);
285         GtkTreeIter iter;
286         gchar *name = g_path_get_basename(entangle_image_get_filename(img));
287
288         gtk_list_store_append(GTK_LIST_STORE(priv->model), &iter);
289         ENTANGLE_DEBUG("ADD IMAGE FIRST %p", img);
290         gtk_list_store_set(GTK_LIST_STORE(priv->model), &iter,
291                            FIELD_IMAGE, img,
292                            FIELD_PIXMAP, priv->blank,
293                            FIELD_LASTMOD, mod,
294                            FIELD_NAME, name,
295                            -1);
296
297         entangle_pixbuf_loader_load(ENTANGLE_PIXBUF_LOADER(priv->loader), img);
298     }
299
300     if (count) {
301         GtkTreePath *path = NULL;
302         path = gtk_tree_path_new_from_indices(count - 1, -1);
303
304         entangle_session_browser_select_path(ENTANGLE_SESSION_BROWSER(browser), path);
305         entangle_session_browser_scroll_to_path(ENTANGLE_SESSION_BROWSER(browser), path, FALSE, 0, 0);
306
307         gtk_tree_path_free(path);
308     }
309 }
310
311
312 static gint
313 do_image_sort_modified(GtkTreeModel *model,
314                        GtkTreeIter  *a,
315                        GtkTreeIter  *b,
316                        gpointer data G_GNUC_UNUSED)
317 {
318     gint ai, bi;
319
320     gtk_tree_model_get(model, a, FIELD_LASTMOD, &ai, -1);
321     gtk_tree_model_get(model, b, FIELD_LASTMOD, &bi, -1);
322
323     return ai - bi;
324 }
325
326
327 static void entangle_session_browser_get_property(GObject *object,
328                                                   guint prop_id,
329                                                   GValue *value,
330                                                   GParamSpec *pspec)
331 {
332     EntangleSessionBrowser *browser = ENTANGLE_SESSION_BROWSER(object);
333     EntangleSessionBrowserPrivate *priv = browser->priv;
334
335     switch (prop_id) {
336     case PROP_SESSION:
337         g_value_set_object(value, priv->session);
338         break;
339
340     case PROP_LOADER:
341         g_value_set_object(value, priv->loader);
342         break;
343
344     case PROP_HADJUSTMENT:
345         g_value_set_object (value, priv->hadjustment);
346         break;
347
348     case PROP_VADJUSTMENT:
349         g_value_set_object (value, priv->vadjustment);
350         break;
351
352     case PROP_HSCROLL_POLICY:
353         g_value_set_enum (value, priv->hscroll_policy);
354         break;
355
356     case PROP_VSCROLL_POLICY:
357         g_value_set_enum (value, priv->vscroll_policy);
358         break;
359
360     default:
361         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
362     }
363 }
364
365
366 static void entangle_session_browser_set_property(GObject *object,
367                                                   guint prop_id,
368                                                   const GValue *value,
369                                                   GParamSpec *pspec)
370 {
371     EntangleSessionBrowser *browser = ENTANGLE_SESSION_BROWSER(object);
372     EntangleSessionBrowserPrivate *priv = browser->priv;
373
374     ENTANGLE_DEBUG("Set prop on session browser %d", prop_id);
375
376     switch (prop_id) {
377     case PROP_SESSION:
378         entangle_session_browser_set_session(browser, g_value_get_object(value));
379         break;
380
381     case PROP_LOADER:
382         entangle_session_browser_set_thumbnail_loader(browser, g_value_get_object(value));
383         break;
384
385     case PROP_HADJUSTMENT:
386         entangle_session_browser_set_hadjustment(browser, g_value_get_object(value));
387         break;
388
389     case PROP_VADJUSTMENT:
390         entangle_session_browser_set_vadjustment(browser, g_value_get_object(value));
391         break;
392
393     case PROP_HSCROLL_POLICY:
394         priv->hscroll_policy = g_value_get_enum (value);
395         gtk_widget_queue_resize (GTK_WIDGET (browser));
396         break;
397
398     case PROP_VSCROLL_POLICY:
399         priv->vscroll_policy = g_value_get_enum (value);
400         gtk_widget_queue_resize (GTK_WIDGET (browser));
401         break;
402
403     default:
404         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
405     }
406 }
407
408
409 static void
410 verify_items(EntangleSessionBrowser *browser)
411 {
412     GList *items;
413     int i = 0;
414
415     for (items = browser->priv->items; items; items = items->next) {
416         EntangleSessionBrowserItem *item = items->data;
417
418         if (item->index != i)
419             ENTANGLE_DEBUG("List item does not match its index: "
420                            "item index %d and list index %d\n", item->index, i);
421         i++;
422     }
423 }
424
425
426 static GtkCellArea *
427 entangle_session_browser_cell_layout_get_area(GtkCellLayout *cell_layout)
428 {
429     EntangleSessionBrowser *browser = ENTANGLE_SESSION_BROWSER(cell_layout);
430     EntangleSessionBrowserPrivate *priv = browser->priv;
431
432     return priv->cell_area;
433 }
434
435
436 static void
437 entangle_session_browser_set_cell_data(EntangleSessionBrowser *browser,
438                                        EntangleSessionBrowserItem *item)
439 {
440     gboolean iters_persist;
441     GtkTreeIter iter;
442
443     iters_persist = gtk_tree_model_get_flags(browser->priv->model) & GTK_TREE_MODEL_ITERS_PERSIST;
444
445     if (!iters_persist) {
446         GtkTreePath *path;
447
448         path = gtk_tree_path_new_from_indices (item->index, -1);
449         if (!gtk_tree_model_get_iter (browser->priv->model, &iter, path))
450             return;
451         gtk_tree_path_free (path);
452     } else {
453         iter = item->iter;
454     }
455
456     gtk_cell_area_apply_attributes(browser->priv->cell_area,
457                                    browser->priv->model,
458                                    &iter, FALSE, FALSE);
459 }
460
461
462 /* This ensures that all widths have been cached in the
463  * context and we have proper alignments to go on.
464  */
465 static void
466 entangle_session_browser_cache_widths(EntangleSessionBrowser *browser)
467 {
468     GList *items;
469
470     g_signal_handler_block (browser->priv->cell_area_context,
471                             browser->priv->context_changed_id);
472
473     for (items = browser->priv->items; items; items = items->next) {
474         EntangleSessionBrowserItem *item = items->data;
475
476         /* Only fetch the width of items with invalidated sizes */
477         if (item->cell_area.width < 0) {
478             entangle_session_browser_set_cell_data(browser, item);
479             gtk_cell_area_get_preferred_width(browser->priv->cell_area,
480                                               browser->priv->cell_area_context,
481                                               GTK_WIDGET(browser), NULL, NULL);
482         }
483     }
484
485     g_signal_handler_unblock(browser->priv->cell_area_context,
486                              browser->priv->context_changed_id);
487 }
488
489
490 static void
491 entangle_session_browser_item_invalidate_size(EntangleSessionBrowserItem *item)
492 {
493     item->cell_area.width = -1;
494     item->cell_area.height = -1;
495 }
496
497
498 static void
499 entangle_session_browser_invalidate_sizes (EntangleSessionBrowser *browser)
500 {
501     /* Clear all item sizes */
502     g_list_foreach (browser->priv->items,
503                     (GFunc)entangle_session_browser_item_invalidate_size, NULL);
504
505     /* Reset the context */
506     if (browser->priv->cell_area_context) {
507         g_signal_handler_block (browser->priv->cell_area_context,
508                                 browser->priv->context_changed_id);
509         gtk_cell_area_context_reset (browser->priv->cell_area_context);
510         g_signal_handler_unblock (browser->priv->cell_area_context,
511                                   browser->priv->context_changed_id);
512     }
513
514     /* Re-layout the items */
515     entangle_session_browser_queue_layout(browser);
516 }
517
518
519 static void
520 entangle_session_browser_context_changed(GtkCellAreaContext *context G_GNUC_UNUSED,
521                                          GParamSpec *pspec,
522                                          gpointer data)
523 {
524     EntangleSessionBrowser *browser = ENTANGLE_SESSION_BROWSER(data);
525
526     if (!strcmp(pspec->name, "minimum-width") ||
527         !strcmp(pspec->name, "natural-width") ||
528         !strcmp(pspec->name, "minimum-height") ||
529         !strcmp(pspec->name, "natural-height"))
530         entangle_session_browser_invalidate_sizes(browser);
531 }
532
533
534 static void
535 entangle_session_browser_row_changed(GtkTreeModel *model G_GNUC_UNUSED,
536                                      GtkTreePath *path,
537                                      GtkTreeIter *iter G_GNUC_UNUSED,
538                                      gpointer data)
539 {
540     EntangleSessionBrowser *browser = ENTANGLE_SESSION_BROWSER(data);
541
542     /* ignore changes in branches */
543     if (gtk_tree_path_get_depth(path) > 1)
544         return;
545
546     /* An icon view subclass might add it's own model and populate
547      * things at init() time instead of waiting for the constructor()
548      * to be called
549      */
550     if (browser->priv->cell_area)
551         gtk_cell_area_stop_editing(browser->priv->cell_area, TRUE);
552
553     /* Here we can use a "grow-only" strategy for optimization
554      * and only invalidate a single item and queue a relayout
555      * instead of invalidating the whole thing.
556      *
557      * For now EntangleSessionBrowser still cant deal with huge models
558      * so just invalidate the whole thing when the model
559      * changes.
560      */
561     entangle_session_browser_invalidate_sizes(browser);
562
563     verify_items(browser);
564 }
565
566
567 static EntangleSessionBrowserItem *entangle_session_browser_item_new(void)
568 {
569     EntangleSessionBrowserItem *item;
570
571     item = g_slice_new0(EntangleSessionBrowserItem);
572
573     item->cell_area.width  = -1;
574     item->cell_area.height = -1;
575
576     return item;
577 }
578
579
580 static void entangle_session_browser_item_free(EntangleSessionBrowserItem *item)
581 {
582     g_return_if_fail (item != NULL);
583
584     g_slice_free(EntangleSessionBrowserItem, item);
585 }
586
587
588 static void
589 entangle_session_browser_row_inserted(GtkTreeModel *model G_GNUC_UNUSED,
590                                       GtkTreePath *path,
591                                       GtkTreeIter *iter,
592                                       gpointer data)
593 {
594     EntangleSessionBrowser *browser = ENTANGLE_SESSION_BROWSER(data);
595     gint index;
596     EntangleSessionBrowserItem *item;
597     gboolean iters_persist;
598     GList *list;
599
600     /* ignore changes in branches */
601     if (gtk_tree_path_get_depth(path) > 1)
602         return;
603
604     iters_persist = gtk_tree_model_get_flags(browser->priv->model) & GTK_TREE_MODEL_ITERS_PERSIST;
605
606     index = gtk_tree_path_get_indices(path)[0];
607
608     item = entangle_session_browser_item_new();
609
610     if (iters_persist)
611         item->iter = *iter;
612
613     item->index = index;
614
615     /* FIXME: We can be more efficient here,
616        we can store a tail pointer and use that when
617        appending (which is a rather common operation)
618     */
619     browser->priv->items = g_list_insert(browser->priv->items,
620                                          item, index);
621
622     list = g_list_nth (browser->priv->items, index + 1);
623     for (; list; list = list->next) {
624         item = list->data;
625         item->index++;
626     }
627
628     verify_items(browser);
629
630     entangle_session_browser_queue_layout(browser);
631 }
632
633
634 static void
635 entangle_session_browser_row_deleted(GtkTreeModel *model G_GNUC_UNUSED,
636                                      GtkTreePath *path,
637                                      gpointer data)
638 {
639     EntangleSessionBrowser *browser = ENTANGLE_SESSION_BROWSER(data);
640     gint index;
641     EntangleSessionBrowserItem *item;
642     GList *list, *next;
643     gboolean emit = FALSE;
644
645     /* ignore changes in branches */
646     if (gtk_tree_path_get_depth(path) > 1)
647         return;
648
649     index = gtk_tree_path_get_indices(path)[0];
650
651     list = g_list_nth(browser->priv->items, index);
652     item = list->data;
653
654     if (browser->priv->cell_area)
655         gtk_cell_area_stop_editing(browser->priv->cell_area, TRUE);
656
657     if (item->selected)
658         emit = TRUE;
659
660     entangle_session_browser_item_free(item);
661
662     for (next = list->next; next; next = next->next) {
663         item = next->data;
664         item->index--;
665     }
666
667     browser->priv->items = g_list_delete_link(browser->priv->items, list);
668
669     verify_items(browser);
670
671     entangle_session_browser_queue_layout(browser);
672
673     if (emit)
674         g_signal_emit(browser, browser_signals[SIGNAL_SELECTION_CHANGED], 0);
675 }
676
677 static void
678 entangle_session_browser_rows_reordered(GtkTreeModel *model,
679                                         GtkTreePath *parent G_GNUC_UNUSED,
680                                         GtkTreeIter *iter,
681                                         gint *new_order,
682                                         gpointer data)
683 {
684     EntangleSessionBrowser *browser = ENTANGLE_SESSION_BROWSER(data);
685     int i;
686     int length;
687     GList *items = NULL, *list;
688     EntangleSessionBrowserItem **item_array;
689     gint *order;
690
691     /* ignore changes in branches */
692     if (iter != NULL)
693         return;
694
695     if (browser->priv->cell_area)
696         gtk_cell_area_stop_editing(browser->priv->cell_area, TRUE);
697
698     length = gtk_tree_model_iter_n_children(model, NULL);
699
700     order = g_new(gint, length);
701     for (i = 0; i < length; i++)
702         order[new_order[i]] = i;
703
704     item_array = g_new(EntangleSessionBrowserItem *, length);
705     for (i = 0, list = browser->priv->items; list != NULL; list = list->next, i++)
706         item_array[order[i]] = list->data;
707     g_free(order);
708
709     for (i = length - 1; i >= 0; i--) {
710         item_array[i]->index = i;
711         items = g_list_prepend(items, item_array[i]);
712     }
713
714     g_free(item_array);
715     g_list_free(browser->priv->items);
716     browser->priv->items = items;
717
718     entangle_session_browser_queue_layout(browser);
719
720     verify_items(browser);
721 }
722
723
724 static void
725 entangle_session_browser_build_items(EntangleSessionBrowser *browser)
726 {
727     GtkTreeIter iter;
728     int i;
729     gboolean iters_persist;
730     GList *items = NULL;
731
732     iters_persist = gtk_tree_model_get_flags(browser->priv->model) & GTK_TREE_MODEL_ITERS_PERSIST;
733
734     if (!gtk_tree_model_get_iter_first(browser->priv->model,
735                                        &iter))
736         return;
737
738     i = 0;
739
740     do {
741         EntangleSessionBrowserItem *item = entangle_session_browser_item_new();
742
743         if (iters_persist)
744             item->iter = iter;
745
746         item->index = i;
747         i++;
748         items = g_list_prepend(items, item);
749     } while (gtk_tree_model_iter_next(browser->priv->model, &iter));
750
751     browser->priv->items = g_list_reverse(items);
752 }
753
754
755 static void
756 entangle_session_browser_realize(GtkWidget *widget)
757 {
758     EntangleSessionBrowser *browser = ENTANGLE_SESSION_BROWSER(widget);
759     EntangleSessionBrowserPrivate *priv = browser->priv;
760     GtkAllocation allocation;
761     GdkWindow *window;
762     GdkWindowAttr attributes;
763     gint attributes_mask;
764     GtkStyleContext *context;
765
766     gtk_widget_set_realized(widget, TRUE);
767
768     gtk_widget_get_allocation(widget, &allocation);
769
770     /* Make the main, clipping window */
771     attributes.window_type = GDK_WINDOW_CHILD;
772     attributes.x = allocation.x;
773     attributes.y = allocation.y;
774     attributes.width = allocation.width;
775     attributes.height = allocation.height;
776     attributes.wclass = GDK_INPUT_OUTPUT;
777     attributes.visual = gtk_widget_get_visual(widget);
778     attributes.event_mask = GDK_VISIBILITY_NOTIFY_MASK;
779
780     attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL;
781
782     window = gdk_window_new(gtk_widget_get_parent_window(widget),
783                             &attributes, attributes_mask);
784     gtk_widget_set_window(widget, window);
785     gdk_window_set_user_data(window, widget);
786
787     gtk_widget_get_allocation(widget, &allocation);
788
789     /* Make the window for the icon view */
790     attributes.x = 0;
791     attributes.y = 0;
792     attributes.width = MAX (priv->width, allocation.width);
793     attributes.height = MAX (priv->height, allocation.height);
794     attributes.event_mask = (GDK_EXPOSURE_MASK |
795                              GDK_SCROLL_MASK |
796                              GDK_POINTER_MOTION_MASK |
797                              GDK_BUTTON_PRESS_MASK |
798                              GDK_BUTTON_RELEASE_MASK |
799                              GDK_KEY_PRESS_MASK |
800                              GDK_KEY_RELEASE_MASK) |
801         gtk_widget_get_events(widget);
802
803     priv->bin_window = gdk_window_new(window,
804                                       &attributes, attributes_mask);
805     gdk_window_set_user_data(priv->bin_window, widget);
806
807     context = gtk_widget_get_style_context(widget);
808
809     gtk_style_context_save(context);
810     gtk_style_context_add_class(context, GTK_STYLE_CLASS_VIEW);
811     gtk_style_context_set_background(context, priv->bin_window);
812     gtk_style_context_restore(context);
813
814     gdk_window_show(priv->bin_window);
815 }
816
817
818 static void
819 entangle_session_browser_unrealize(GtkWidget *widget)
820 {
821     EntangleSessionBrowser *browser = ENTANGLE_SESSION_BROWSER(widget);
822     EntangleSessionBrowserPrivate *priv = browser->priv;
823
824     gdk_window_set_user_data(priv->bin_window, NULL);
825     gdk_window_destroy(priv->bin_window);
826     priv->bin_window = NULL;
827
828     GTK_WIDGET_CLASS(entangle_session_browser_parent_class)->unrealize(widget);
829 }
830
831
832 static void
833 entangle_session_browser_scroll_to_item(EntangleSessionBrowser *browser,
834                                         EntangleSessionBrowserItem *item)
835 {
836     EntangleSessionBrowserPrivate *priv = browser->priv;
837     GtkWidget *widget = GTK_WIDGET(browser);
838     GtkAdjustment *hadj, *vadj;
839     GtkAllocation allocation;
840     gint x, y;
841     GdkRectangle item_area;
842
843     item_area.x = item->cell_area.x - priv->item_padding;
844     item_area.y = item->cell_area.y - priv->item_padding;
845     item_area.width = item->cell_area.width  + priv->item_padding * 2;
846     item_area.height = item->cell_area.height + priv->item_padding * 2;
847
848     gdk_window_get_position(priv->bin_window, &x, &y);
849     gtk_widget_get_allocation(widget, &allocation);
850
851     hadj = priv->hadjustment;
852     vadj = priv->vadjustment;
853
854     if (y + item_area.y < 0)
855         gtk_adjustment_set_value(vadj,
856                                  gtk_adjustment_get_value(vadj)
857                                  + y + item_area.y);
858     else if (y + item_area.y + item_area.height > allocation.height)
859         gtk_adjustment_set_value(vadj,
860                                  gtk_adjustment_get_value(vadj)
861                                  + y + item_area.y + item_area.height - allocation.height);
862
863     if (x + item_area.x < 0)
864         gtk_adjustment_set_value(hadj,
865                                  gtk_adjustment_get_value(hadj)
866                                  + x + item_area.x);
867     else if (x + item_area.x + item_area.width > allocation.width)
868         gtk_adjustment_set_value(hadj,
869                                  gtk_adjustment_get_value(hadj)
870                                  + x + item_area.x + item_area.width - allocation.width);
871
872     gtk_adjustment_changed(hadj);
873     gtk_adjustment_changed(vadj);
874 }
875
876
877 static EntangleSessionBrowserItem *
878 entangle_session_browser_get_item_at_coords(EntangleSessionBrowser *browser,
879                                             gint x,
880                                             gint y,
881                                             gboolean only_in_cell,
882                                             GtkCellRenderer **cell_at_pos)
883 {
884     EntangleSessionBrowserPrivate *priv = browser->priv;
885     GList *items;
886
887     if (cell_at_pos)
888         *cell_at_pos = NULL;
889
890     for (items = priv->items; items; items = items->next) {
891         EntangleSessionBrowserItem *item = items->data;
892         GdkRectangle *item_area = (GdkRectangle *)item;
893
894         if (x >= item_area->x - priv->column_spacing / 2 &&
895             x <= item_area->x + item_area->width + priv->column_spacing / 2 &&
896             y >= item_area->y &&
897             y <= item_area->y + item_area->height) {
898             if (only_in_cell || cell_at_pos) {
899                 GtkCellRenderer *cell = NULL;
900
901                 entangle_session_browser_set_cell_data(browser, item);
902
903                 if (x >= item_area->x && x <= item_area->x + item_area->width &&
904                     y >= item_area->y && y <= item_area->y + item_area->height)
905                     cell = gtk_cell_area_get_cell_at_position(priv->cell_area,
906                                                               priv->cell_area_context,
907                                                               GTK_WIDGET(browser),
908                                                               item_area,
909                                                               x, y, NULL);
910
911                 if (cell_at_pos)
912                     *cell_at_pos = cell;
913
914                 if (only_in_cell)
915                     return cell != NULL ? item : NULL;
916                 else
917                     return item;
918             }
919             return item;
920         }
921     }
922     return NULL;
923 }
924
925
926 static void
927 entangle_session_browser_queue_draw_item(EntangleSessionBrowser *browser,
928                                          EntangleSessionBrowserItem *item)
929 {
930     EntangleSessionBrowserPrivate *priv = browser->priv;
931     GdkRectangle  rect;
932     GdkRectangle *item_area = (GdkRectangle *)item;
933
934     rect.x      = item_area->x - priv->item_padding;
935     rect.y      = item_area->y - priv->item_padding;
936     rect.width  = item_area->width  + priv->item_padding * 2;
937     rect.height = item_area->height + priv->item_padding * 2;
938
939     if (priv->bin_window)
940         gdk_window_invalidate_rect(priv->bin_window, &rect, TRUE);
941 }
942
943
944 static gboolean
945 entangle_session_browser_unselect_all_internal(EntangleSessionBrowser *browser)
946 {
947     EntangleSessionBrowserPrivate *priv = browser->priv;
948     gboolean dirty = FALSE;
949     GList *items;
950
951     for (items = priv->items; items; items = items->next) {
952         EntangleSessionBrowserItem *item = items->data;
953
954         if (item->selected) {
955             item->selected = FALSE;
956             dirty = TRUE;
957             entangle_session_browser_queue_draw_item(browser, item);
958         }
959     }
960
961     return dirty;
962 }
963
964
965 static void
966 entangle_session_browser_select_item(EntangleSessionBrowser *browser,
967                                      EntangleSessionBrowserItem *item)
968 {
969     if (item->selected)
970         return;
971
972     item->selected = TRUE;
973
974     g_signal_emit(browser, browser_signals[SIGNAL_SELECTION_CHANGED], 0);
975
976     entangle_session_browser_queue_draw_item(browser, item);
977 }
978
979
980 static void
981 entangle_session_browser_unselect_item(EntangleSessionBrowser *browser,
982                                        EntangleSessionBrowserItem *item)
983 {
984     if (!item->selected)
985         return;
986
987     item->selected = FALSE;
988
989     g_signal_emit(browser, browser_signals[SIGNAL_SELECTION_CHANGED], 0);
990
991     entangle_session_browser_queue_draw_item(browser, item);
992 }
993
994
995 static void
996 entangle_session_browser_select_path(EntangleSessionBrowser *browser,
997                                      GtkTreePath *path)
998 {
999     EntangleSessionBrowserPrivate *priv = browser->priv;
1000     EntangleSessionBrowserItem *item = NULL;
1001
1002     if (gtk_tree_path_get_depth (path) > 0)
1003         item = g_list_nth_data(priv->items,
1004                                gtk_tree_path_get_indices(path)[0]);
1005
1006     if (item) {
1007         entangle_session_browser_unselect_all_internal(browser);
1008         entangle_session_browser_select_item(browser, item);
1009     }
1010 }
1011
1012
1013 static void
1014 entangle_session_browser_scroll_to_path(EntangleSessionBrowser *browser,
1015                                         GtkTreePath *path,
1016                                         gboolean     use_align,
1017                                         gfloat       row_align,
1018                                         gfloat       col_align)
1019 {
1020     EntangleSessionBrowserPrivate *priv = browser->priv;
1021     EntangleSessionBrowserItem *item = NULL;
1022     GtkWidget *widget = GTK_WIDGET(browser);
1023
1024     if (gtk_tree_path_get_depth(path) > 0)
1025         item = g_list_nth_data(priv->items,
1026                                gtk_tree_path_get_indices(path)[0]);
1027
1028     if (!item || item->cell_area.width < 0 ||
1029         !gtk_widget_get_realized (widget)) {
1030         if (priv->scroll_to_path)
1031             gtk_tree_row_reference_free(priv->scroll_to_path);
1032
1033         priv->scroll_to_path = NULL;
1034
1035         if (path)
1036             priv->scroll_to_path = gtk_tree_row_reference_new_proxy(G_OBJECT(browser),
1037                                                                     priv->model, path);
1038
1039         priv->scroll_to_use_align = use_align;
1040         priv->scroll_to_row_align = row_align;
1041         priv->scroll_to_col_align = col_align;
1042
1043         return;
1044     }
1045
1046     if (use_align) {
1047         GtkAllocation allocation;
1048         gint x, y;
1049         gfloat offset;
1050         GdkRectangle item_area = {
1051             item->cell_area.x - priv->item_padding,
1052             item->cell_area.y - priv->item_padding,
1053             item->cell_area.width  + priv->item_padding * 2,
1054             item->cell_area.height + priv->item_padding * 2
1055         };
1056
1057         gdk_window_get_position(priv->bin_window, &x, &y);
1058
1059         gtk_widget_get_allocation(widget, &allocation);
1060
1061         offset = y + item_area.y - row_align * (allocation.height - item_area.height);
1062
1063         gtk_adjustment_set_value(priv->vadjustment,
1064                                  gtk_adjustment_get_value(priv->vadjustment) + offset);
1065
1066         offset = x + item_area.x - col_align * (allocation.width - item_area.width);
1067
1068         gtk_adjustment_set_value(priv->hadjustment,
1069                                  gtk_adjustment_get_value(priv->hadjustment) + offset);
1070
1071         gtk_adjustment_changed(priv->hadjustment);
1072         gtk_adjustment_changed(priv->vadjustment);
1073     } else {
1074         entangle_session_browser_scroll_to_item(browser, item);
1075     }
1076 }
1077
1078
1079 static gboolean
1080 entangle_session_browser_button_press(GtkWidget *widget,
1081                                       GdkEventButton *event)
1082 {
1083     EntangleSessionBrowser *browser = ENTANGLE_SESSION_BROWSER(widget);
1084     EntangleSessionBrowserPrivate *priv = browser->priv;
1085     EntangleSessionBrowserItem *item;
1086     gboolean dirty = FALSE;
1087     GtkCellRenderer *cell = NULL;
1088
1089     if (event->window != priv->bin_window)
1090         return FALSE;
1091
1092     if (!gtk_widget_has_focus(widget))
1093         gtk_widget_grab_focus(widget);
1094
1095     if (event->button == 1 && event->type == GDK_BUTTON_PRESS) {
1096         item = entangle_session_browser_get_item_at_coords(browser,
1097                                                            event->x, event->y,
1098                                                            FALSE,
1099                                                            &cell);
1100
1101         /*
1102          * We consider only the cells' area as the item area if the
1103          * item is not selected, but if it *is* selected, the complete
1104          * selection rectangle is considered to be part of the item.
1105          */
1106         if (item != NULL && !item->selected) {
1107             entangle_session_browser_unselect_all_internal(browser);
1108             dirty = TRUE;
1109             item->selected = TRUE;
1110             entangle_session_browser_queue_draw_item(browser, item);
1111             entangle_session_browser_scroll_to_item(browser, item);
1112         }
1113     }
1114
1115     if (dirty)
1116         g_signal_emit(browser, browser_signals[SIGNAL_SELECTION_CHANGED], 0);
1117
1118     return event->button == 1;
1119 }
1120
1121
1122 static gboolean
1123 entangle_session_browser_key_release(GtkWidget *widget,
1124                                      GdkEventKey *event)
1125 {
1126     EntangleSessionBrowser *browser = ENTANGLE_SESSION_BROWSER(widget);
1127     EntangleSessionBrowserPrivate *priv = browser->priv;
1128     GList *list, *prev = NULL;
1129
1130     switch (event->keyval) {
1131     case GDK_KEY_Right:
1132        for (list = priv->items; list != NULL; list = list->next) {
1133             EntangleSessionBrowserItem *item = list->data;
1134
1135             if (item->selected && list->next) {
1136                 EntangleSessionBrowserItem *next = list->next->data;
1137                 entangle_session_browser_unselect_item(browser, item);
1138                 entangle_session_browser_select_item(browser, next);
1139                 entangle_session_browser_scroll_to_item(browser, next);
1140                 break;
1141             }
1142         }
1143         return TRUE;
1144
1145     case GDK_KEY_Left:
1146         for (list = priv->items; list != NULL; list = list->next) {
1147             EntangleSessionBrowserItem *item = list->data;
1148
1149             if (item->selected && prev) {
1150                 EntangleSessionBrowserItem *prior = prev->data;
1151                 entangle_session_browser_unselect_item(browser, item);
1152                 entangle_session_browser_select_item(browser, prior);
1153                 entangle_session_browser_scroll_to_item(browser, prior);
1154                 break;
1155             }
1156             prev = list;
1157         }
1158         return TRUE;
1159
1160     default:
1161         return GTK_WIDGET_CLASS(entangle_session_browser_parent_class)->key_press_event(widget, event);
1162     }
1163 }
1164
1165
1166 static void
1167 entangle_session_browser_size_allocate(GtkWidget *widget,
1168                                        GtkAllocation *allocation)
1169 {
1170     EntangleSessionBrowser *browser = ENTANGLE_SESSION_BROWSER(widget);
1171     EntangleSessionBrowserPrivate *priv = browser->priv;
1172
1173     gtk_widget_set_allocation(widget, allocation);
1174
1175     if (gtk_widget_get_realized(widget)) {
1176         gdk_window_move_resize(gtk_widget_get_window(widget),
1177                                allocation->x, allocation->y,
1178                                allocation->width, allocation->height);
1179         gdk_window_resize(priv->bin_window,
1180                           MAX(priv->width, allocation->width),
1181                           MAX(priv->height, allocation->height));
1182     }
1183
1184     entangle_session_browser_layout(browser);
1185
1186     /* Delay signal emission */
1187     g_object_freeze_notify(G_OBJECT(priv->hadjustment));
1188     g_object_freeze_notify(G_OBJECT(priv->vadjustment));
1189
1190     entangle_session_browser_set_hadjustment_values(browser);
1191     entangle_session_browser_set_vadjustment_values(browser);
1192
1193     if (gtk_widget_get_realized(widget) &&
1194         priv->scroll_to_path) {
1195         GtkTreePath *path;
1196         path = gtk_tree_row_reference_get_path(priv->scroll_to_path);
1197         gtk_tree_row_reference_free(priv->scroll_to_path);
1198         priv->scroll_to_path = NULL;
1199
1200         entangle_session_browser_scroll_to_path(browser, path,
1201                                                 priv->scroll_to_use_align,
1202                                                 priv->scroll_to_row_align,
1203                                                 priv->scroll_to_col_align);
1204         gtk_tree_path_free(path);
1205     }
1206
1207     /* Emit any pending signals now */
1208     g_object_thaw_notify(G_OBJECT(priv->hadjustment));
1209     g_object_thaw_notify(G_OBJECT(priv->vadjustment));
1210 }
1211
1212
1213 static void entangle_session_browser_destroy(GtkWidget *widget)
1214 {
1215     EntangleSessionBrowser *browser = ENTANGLE_SESSION_BROWSER(widget);
1216     EntangleSessionBrowserPrivate *priv = browser->priv;
1217
1218     if (priv->layout_idle_id != 0) {
1219         g_source_remove(priv->layout_idle_id);
1220         priv->layout_idle_id = 0;
1221     }
1222
1223     if (priv->scroll_to_path != NULL) {
1224         gtk_tree_row_reference_free(priv->scroll_to_path);
1225         priv->scroll_to_path = NULL;
1226     }
1227
1228     if (priv->hadjustment != NULL) {
1229         g_object_unref(priv->hadjustment);
1230         priv->hadjustment = NULL;
1231     }
1232
1233     if (priv->vadjustment != NULL) {
1234         g_object_unref(priv->vadjustment);
1235         priv->vadjustment = NULL;
1236     }
1237
1238     GTK_WIDGET_CLASS(entangle_session_browser_parent_class)->destroy(widget);
1239 }
1240
1241
1242 static void entangle_session_browser_finalize(GObject *object)
1243 {
1244     EntangleSessionBrowser *browser = ENTANGLE_SESSION_BROWSER(object);
1245     EntangleSessionBrowserPrivate *priv = browser->priv;
1246
1247     if (priv->session && priv->loader)
1248         do_model_unload(browser);
1249
1250     if (priv->cell_area_context) {
1251         g_signal_handler_disconnect(priv->cell_area_context, priv->context_changed_id);
1252         priv->context_changed_id = 0;
1253
1254         g_object_unref(priv->cell_area_context);
1255         priv->cell_area_context = NULL;
1256     }
1257
1258     if (priv->session)
1259         g_object_unref(priv->session);
1260     if (priv->loader)
1261         g_object_unref(priv->loader);
1262
1263     G_OBJECT_CLASS (entangle_session_browser_parent_class)->finalize (object);
1264 }
1265
1266
1267 static void
1268 entangle_session_browser_cell_layout_init(GtkCellLayoutIface *iface)
1269 {
1270     iface->get_area = entangle_session_browser_cell_layout_get_area;
1271 }
1272
1273 static void entangle_session_browser_class_init(EntangleSessionBrowserClass *klass)
1274 {
1275     GObjectClass *object_class = G_OBJECT_CLASS(klass);
1276     GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
1277
1278     object_class->finalize = entangle_session_browser_finalize;
1279     object_class->get_property = entangle_session_browser_get_property;
1280     object_class->set_property = entangle_session_browser_set_property;
1281
1282     widget_class->destroy = entangle_session_browser_destroy;
1283     widget_class->realize = entangle_session_browser_realize;
1284     widget_class->unrealize = entangle_session_browser_unrealize;
1285     widget_class->draw = entangle_session_browser_draw;
1286     widget_class->button_press_event = entangle_session_browser_button_press;
1287     widget_class->key_release_event = entangle_session_browser_key_release;
1288     widget_class->size_allocate = entangle_session_browser_size_allocate;
1289
1290     g_object_class_install_property(object_class,
1291                                     PROP_SESSION,
1292                                     g_param_spec_object("session",
1293                                                         "Session",
1294                                                         "Session to be displayed",
1295                                                         ENTANGLE_TYPE_SESSION,
1296                                                         G_PARAM_READWRITE |
1297                                                         G_PARAM_STATIC_NAME |
1298                                                         G_PARAM_STATIC_NICK |
1299                                                         G_PARAM_STATIC_BLURB));
1300
1301     g_object_class_install_property(object_class,
1302                                     PROP_LOADER,
1303                                     g_param_spec_object("thumbnail-loader",
1304                                                         "Thumbnail loader",
1305                                                         "Thumbnail loader",
1306                                                         ENTANGLE_TYPE_THUMBNAIL_LOADER,
1307                                                         G_PARAM_READWRITE |
1308                                                         G_PARAM_STATIC_NAME |
1309                                                         G_PARAM_STATIC_NICK |
1310                                                         G_PARAM_STATIC_BLURB));
1311
1312     /* Scrollable interface properties */
1313     g_object_class_override_property(object_class, PROP_HADJUSTMENT,    "hadjustment");
1314     g_object_class_override_property(object_class, PROP_VADJUSTMENT,    "vadjustment");
1315     g_object_class_override_property(object_class, PROP_HSCROLL_POLICY, "hscroll-policy");
1316     g_object_class_override_property(object_class, PROP_VSCROLL_POLICY, "vscroll-policy");
1317
1318     browser_signals[SIGNAL_SELECTION_CHANGED] =
1319         g_signal_new("selection-changed",
1320                      G_TYPE_FROM_CLASS(object_class),
1321                      G_SIGNAL_RUN_FIRST,
1322                      G_STRUCT_OFFSET(EntangleSessionBrowserClass, selection_changed),
1323                      NULL, NULL,
1324                      g_cclosure_marshal_VOID__VOID,
1325                      G_TYPE_NONE, 0);
1326
1327     g_type_class_add_private(klass, sizeof(EntangleSessionBrowserPrivate));
1328 }
1329
1330
1331 EntangleSessionBrowser *entangle_session_browser_new(void)
1332 {
1333     return ENTANGLE_SESSION_BROWSER(g_object_new(ENTANGLE_TYPE_SESSION_BROWSER, NULL));
1334 }
1335
1336
1337 static void entangle_session_browser_init(EntangleSessionBrowser *browser)
1338 {
1339     EntangleSessionBrowserPrivate *priv;
1340     GdkRGBA fg;
1341
1342     priv = browser->priv = ENTANGLE_SESSION_BROWSER_GET_PRIVATE(browser);
1343
1344     priv->model = GTK_TREE_MODEL(gtk_list_store_new(FIELD_LAST,
1345                                                     ENTANGLE_TYPE_IMAGE,
1346                                                     GDK_TYPE_PIXBUF,
1347                                                     G_TYPE_INT,
1348                                                     G_TYPE_STRING));
1349
1350     gtk_tree_sortable_set_default_sort_func(GTK_TREE_SORTABLE(priv->model),
1351                                             do_image_sort_modified, NULL, NULL);
1352     gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(priv->model),
1353                                          GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID,
1354                                          GTK_SORT_ASCENDING);
1355
1356     priv->cell_area = gtk_cell_area_box_new ();
1357     g_object_ref_sink(priv->cell_area);
1358     gtk_orientable_set_orientation(GTK_ORIENTABLE (priv->cell_area), GTK_ORIENTATION_VERTICAL);
1359
1360     priv->cell_area_context = gtk_cell_area_create_context(priv->cell_area);
1361     priv->context_changed_id =
1362         g_signal_connect(priv->cell_area_context, "notify",
1363                          G_CALLBACK(entangle_session_browser_context_changed), browser);
1364
1365     priv->pixbuf_cell = gtk_cell_renderer_pixbuf_new();
1366     gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(browser), priv->pixbuf_cell, FALSE);
1367
1368     gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(browser),
1369                                    priv->pixbuf_cell,
1370                                    "pixbuf", FIELD_PIXMAP,
1371                                    NULL);
1372
1373     g_object_set(priv->pixbuf_cell,
1374                  "xalign", 0.5,
1375                  "yalign", 1.0,
1376                  NULL);
1377
1378
1379     priv->text_cell = gtk_cell_renderer_text_new();
1380     gtk_cell_layout_pack_end(GTK_CELL_LAYOUT(browser), priv->text_cell, FALSE);
1381     gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(browser),
1382                                    priv->text_cell,
1383                                    "text", FIELD_NAME,
1384                                    NULL);
1385
1386     fg.red = 1;
1387     fg.blue = 1;
1388     fg.green = 1;
1389     fg.alpha = 1;
1390
1391     g_object_set(priv->text_cell,
1392                  "alignment", PANGO_ALIGN_CENTER,
1393                  "wrap-mode", PANGO_WRAP_WORD_CHAR,
1394                  "xalign", 0.5,
1395                  "yalign", 0.0,
1396                  "foreground-set", TRUE,
1397                  "foreground-rgba", &fg,
1398                  NULL);
1399
1400     g_signal_connect(priv->model,
1401                      "row-changed",
1402                      G_CALLBACK (entangle_session_browser_row_changed),
1403                      browser);
1404     g_signal_connect(priv->model,
1405                      "row-inserted",
1406                      G_CALLBACK (entangle_session_browser_row_inserted),
1407                      browser);
1408     g_signal_connect(priv->model,
1409                      "row-deleted",
1410                      G_CALLBACK (entangle_session_browser_row_deleted),
1411                      browser);
1412     g_signal_connect(priv->model,
1413                      "rows-reordered",
1414                      G_CALLBACK (entangle_session_browser_rows_reordered),
1415                      browser);
1416
1417     entangle_session_browser_build_items(browser);
1418     entangle_session_browser_queue_layout(browser);
1419
1420     priv->margin = 6;
1421     priv->item_padding = 0;
1422     priv->column_spacing = 6;
1423
1424     gtk_widget_set_can_focus(GTK_WIDGET(browser), TRUE);
1425 }
1426
1427
1428 static GList *
1429 entangle_session_browser_get_selected_items(EntangleSessionBrowser *browser)
1430 {
1431     EntangleSessionBrowserPrivate *priv = browser->priv;
1432     GList *list;
1433     GList *selected = NULL;
1434
1435     for (list = priv->items; list != NULL; list = list->next) {
1436         EntangleSessionBrowserItem *item = list->data;
1437
1438         if (item->selected) {
1439             GtkTreePath *path = gtk_tree_path_new_from_indices(item->index, -1);
1440
1441             selected = g_list_prepend(selected, path);
1442         }
1443     }
1444
1445     return selected;
1446 }
1447
1448
1449 EntangleImage *entangle_session_browser_selected_image(EntangleSessionBrowser *browser)
1450 {
1451     EntangleSessionBrowserPrivate *priv = browser->priv;
1452     GList *items;
1453     EntangleImage *img = NULL;
1454     GtkTreePath *path;
1455     GtkTreeIter iter;
1456     GValue val;
1457
1458     items = entangle_session_browser_get_selected_items(browser);
1459
1460     if (!items)
1461         return NULL;
1462
1463     path = g_list_nth_data(items, 0);
1464     if (!path)
1465         goto cleanup;
1466
1467     if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(priv->model), &iter, path))
1468         goto cleanup;
1469
1470     memset(&val, 0, sizeof val);
1471     gtk_tree_model_get_value(GTK_TREE_MODEL(priv->model), &iter, 0, &val);
1472
1473     img = g_value_get_object(&val);
1474
1475  cleanup:
1476     g_list_foreach(items, (GFunc)(gtk_tree_path_free), NULL);
1477     g_list_free(items);
1478     return img;
1479 }
1480
1481
1482 void entangle_session_browser_set_thumbnail_loader(EntangleSessionBrowser *browser,
1483                                                    EntangleThumbnailLoader *loader)
1484 {
1485     EntangleSessionBrowserPrivate *priv = browser->priv;
1486
1487     if (priv->loader) {
1488         if (priv->session)
1489             do_model_unload(browser);
1490
1491         g_object_unref(priv->loader);
1492     }
1493     priv->loader = loader;
1494     if (priv->loader) {
1495         g_object_ref(priv->loader);
1496
1497         if (priv->session)
1498             do_model_load(browser);
1499     }
1500 }
1501
1502
1503 EntangleThumbnailLoader *entangle_session_browser_get_thumbnail_loader(EntangleSessionBrowser *browser)
1504 {
1505     EntangleSessionBrowserPrivate *priv = browser->priv;
1506
1507     return priv->loader;
1508 }
1509
1510
1511 void entangle_session_browser_set_session(EntangleSessionBrowser *browser,
1512                                           EntangleSession *session)
1513 {
1514     EntangleSessionBrowserPrivate *priv = browser->priv;
1515
1516     if (priv->session) {
1517         if (priv->loader)
1518             do_model_unload(browser);
1519         g_object_unref(priv->session);
1520     }
1521     priv->session = session;
1522     if (priv->session) {
1523         g_object_ref(priv->session);
1524
1525         if (priv->loader)
1526             do_model_load(browser);
1527     }
1528 }
1529
1530
1531 EntangleSession *entangle_session_browser_get_session(EntangleSessionBrowser *browser)
1532 {
1533     EntangleSessionBrowserPrivate *priv = browser->priv;
1534
1535     return priv->session;
1536 }
1537
1538
1539 static void
1540 entangle_session_browser_paint_item(EntangleSessionBrowser *browser,
1541                                     cairo_t *cr,
1542                                     EntangleSessionBrowserItem *item,
1543                                     gint x,
1544                                     gint y)
1545 {
1546     GdkRectangle cell_area;
1547     GtkStateFlags state = 0;
1548     GtkCellRendererState flags = 0;
1549     GtkStyleContext *style_context;
1550     GtkWidget *widget = GTK_WIDGET (browser);
1551     EntangleSessionBrowserPrivate *priv = browser->priv;
1552
1553     entangle_session_browser_set_cell_data(browser, item);
1554
1555     style_context = gtk_widget_get_style_context(widget);
1556
1557     gtk_style_context_save(style_context);
1558     gtk_style_context_add_class(style_context, GTK_STYLE_CLASS_VIEW);
1559     gtk_style_context_add_class(style_context, GTK_STYLE_CLASS_CELL);
1560
1561     if (item->selected) {
1562         state |= GTK_STATE_FLAG_SELECTED;
1563         flags |= GTK_CELL_RENDERER_SELECTED;
1564
1565         gtk_style_context_set_state(style_context, state);
1566         gtk_render_background(style_context, cr,
1567                               x - browser->priv->item_padding,
1568                               y - browser->priv->item_padding,
1569                               item->cell_area.width  + browser->priv->item_padding * 2,
1570                               item->cell_area.height + browser->priv->item_padding * 2);
1571     }
1572
1573     cell_area.x      = x;
1574     cell_area.y      = y;
1575     cell_area.width  = item->cell_area.width;
1576     cell_area.height = item->cell_area.height;
1577
1578     gtk_cell_area_render(priv->cell_area,
1579                          priv->cell_area_context,
1580                          widget, cr, &cell_area, &cell_area, flags,
1581                          FALSE);
1582
1583     gtk_style_context_restore(style_context);
1584 }
1585
1586
1587 static gboolean
1588 entangle_session_browser_draw(GtkWidget *widget,
1589                               cairo_t *cr)
1590 {
1591     EntangleSessionBrowser *browser = ENTANGLE_SESSION_BROWSER(widget);
1592     EntangleSessionBrowserPrivate *priv = browser->priv;
1593     GList *icons;
1594     int ww, wh; /* Available drawing area extents */
1595
1596     ww = gdk_window_get_width(gtk_widget_get_window(widget));
1597     wh = gdk_window_get_height(gtk_widget_get_window(widget));
1598
1599     cairo_set_source_rgb(cr, 0, 0, 0);
1600     cairo_rectangle(cr, 0, 0, ww, wh);
1601     cairo_fill(cr);
1602
1603     if (!gtk_cairo_should_draw_window (cr, priv->bin_window))
1604         return FALSE;
1605
1606     cairo_save(cr);
1607     gtk_cairo_transform_to_window(cr, widget, priv->bin_window);
1608     cairo_set_line_width(cr, 1.);
1609
1610     for (icons = priv->items; icons; icons = icons->next) {
1611         EntangleSessionBrowserItem *item = icons->data;
1612         GdkRectangle paint_area;
1613
1614         paint_area.x      = ((GdkRectangle *)item)->x      - priv->item_padding;
1615         paint_area.y      = ((GdkRectangle *)item)->y      - priv->item_padding;
1616         paint_area.width  = ((GdkRectangle *)item)->width  + priv->item_padding * 2;
1617         paint_area.height = ((GdkRectangle *)item)->height + priv->item_padding * 2;
1618
1619 #if 1
1620         cairo_save(cr);
1621         cairo_set_source_rgba(cr, 1, 1, 1, 0.1);
1622         cairo_rectangle(cr, paint_area.x, paint_area.y, paint_area.width, paint_area.height);
1623         cairo_fill(cr);
1624         cairo_restore(cr);
1625 #endif
1626
1627         cairo_save(cr);
1628         cairo_rectangle(cr, paint_area.x, paint_area.y, paint_area.width, paint_area.height);
1629         cairo_clip(cr);
1630
1631         if (gdk_cairo_get_clip_rectangle(cr, NULL))
1632             entangle_session_browser_paint_item(browser, cr, item,
1633                                                 ((GdkRectangle *)item)->x, ((GdkRectangle *)item)->y);
1634
1635         cairo_restore(cr);
1636     }
1637
1638     cairo_restore(cr);
1639
1640     return TRUE;
1641 }
1642
1643
1644 static void
1645 entangle_session_browser_layout_row(EntangleSessionBrowser *browser,
1646                                     gint item_width,
1647                                     gint *y,
1648                                     gint *maximum_width)
1649 {
1650     EntangleSessionBrowserPrivate *priv = browser->priv;
1651     GtkWidget *widget = GTK_WIDGET(browser);
1652     GtkAllocation allocation;
1653     gint x, current_width;
1654     GList *items;
1655     gint col;
1656     gint max_height = 0;
1657
1658     x = 0;
1659     col = 0;
1660     current_width = 0;
1661
1662     x += priv->margin;
1663     current_width += 2 * priv->margin;
1664
1665     gtk_widget_get_allocation(widget, &allocation);
1666
1667     /* In the first loop we iterate horizontally until we hit allocation width
1668      * and collect the aligned height-for-width */
1669     items = priv->items;
1670     while (items) {
1671         EntangleSessionBrowserItem *item = items->data;
1672         GdkRectangle *item_area = (GdkRectangle *)item;
1673
1674         item_area->width = item_width;
1675
1676         current_width += item_area->width + priv->item_padding * 2;
1677
1678         /* Get this item's particular width & height (all alignments are cached by now) */
1679         entangle_session_browser_set_cell_data(browser, item);
1680         gtk_cell_area_get_preferred_height_for_width(priv->cell_area,
1681                                                      priv->cell_area_context,
1682                                                      widget, item_width,
1683                                                      NULL, NULL);
1684
1685         current_width += priv->column_spacing;
1686
1687         item_area->y = *y + priv->item_padding;
1688         item_area->x = x  + priv->item_padding;
1689
1690         x = current_width - priv->margin;
1691
1692         if (current_width > *maximum_width)
1693             *maximum_width = current_width;
1694
1695         item->col = col;
1696
1697         col++;
1698         items = items->next;
1699     }
1700
1701     gtk_cell_area_context_get_preferred_height_for_width(priv->cell_area_context, item_width, &max_height, NULL);
1702     gtk_cell_area_context_allocate(priv->cell_area_context, item_width, max_height);
1703
1704     /* In the second loop the item height has been aligned and derived and
1705      * we just set the height and handle rtl layout */
1706     for (items = priv->items; items != NULL; items = items->next) {
1707         EntangleSessionBrowserItem *item = items->data;
1708         GdkRectangle *item_area = (GdkRectangle *)item;
1709
1710         /* All items in the same row get the same height */
1711         item_area->height = max_height;
1712     }
1713
1714     /* Adjust the new y coordinate. */
1715     *y += max_height + priv->item_padding * 2;
1716 }
1717
1718
1719 static void
1720 adjust_wrap_width(EntangleSessionBrowser *browser)
1721 {
1722     gint wrap_width = 50;
1723
1724     /* Here we go with the same old guess, try the icon size and set double
1725      * the size of the first icon found in the list, naive but works much
1726      * of the time */
1727     if (browser->priv->items) {
1728         entangle_session_browser_set_cell_data(browser, browser->priv->items->data);
1729         gtk_cell_renderer_get_preferred_width(browser->priv->pixbuf_cell,
1730                                               GTK_WIDGET(browser),
1731                                               &wrap_width, NULL);
1732
1733         wrap_width = MAX(wrap_width * 2, 50);
1734     }
1735
1736     g_object_set(browser->priv->text_cell, "wrap-width", wrap_width, NULL);
1737     g_object_set(browser->priv->text_cell, "width", wrap_width, NULL);
1738 }
1739
1740
1741 static void
1742 entangle_session_browser_layout(EntangleSessionBrowser *browser)
1743 {
1744     EntangleSessionBrowserPrivate *priv = browser->priv;
1745     GtkAllocation allocation;
1746     GtkWidget *widget = GTK_WIDGET(browser);
1747     gint y = 0, maximum_width = 0;
1748     gint item_width;
1749     gboolean size_changed = FALSE;
1750
1751     if (priv->layout_idle_id != 0) {
1752         g_source_remove (priv->layout_idle_id);
1753         priv->layout_idle_id = 0;
1754     }
1755
1756     /* Update the wrap width for the text cell before going and requesting sizes */
1757     adjust_wrap_width (browser);
1758
1759     /* Update the context widths for any invalidated items */
1760     entangle_session_browser_cache_widths(browser);
1761
1762     /* Fetch the new item width if needed */
1763     gtk_cell_area_context_get_preferred_width(priv->cell_area_context,
1764                                               &item_width, NULL);
1765
1766     gtk_cell_area_context_allocate(priv->cell_area_context, item_width, -1);
1767
1768     y += priv->margin;
1769
1770     entangle_session_browser_layout_row(browser,
1771                                         item_width,
1772                                         &y, &maximum_width);
1773
1774     if (maximum_width != priv->width) {
1775         priv->width = maximum_width;
1776         size_changed = TRUE;
1777     }
1778
1779     y += priv->margin;
1780
1781     if (y != priv->height) {
1782         priv->height = y;
1783         size_changed = TRUE;
1784     }
1785
1786     entangle_session_browser_set_hadjustment_values(browser);
1787     entangle_session_browser_set_vadjustment_values(browser);
1788
1789     if (size_changed)
1790         gtk_widget_queue_resize_no_redraw (widget);
1791
1792     gtk_widget_get_allocation(widget, &allocation);
1793     if (gtk_widget_get_realized(widget))
1794         gdk_window_resize(priv->bin_window,
1795                           MAX(priv->width, allocation.width),
1796                           MAX(priv->height, allocation.height));
1797
1798     gtk_widget_queue_draw(widget);
1799 }
1800
1801
1802 static void
1803 entangle_session_browser_process_updates(EntangleSessionBrowser *browser)
1804 {
1805     EntangleSessionBrowserPrivate *priv = browser->priv;
1806
1807     /* Prior to drawing, we check if a layout has been scheduled.  If so,
1808      * do it now that all cell view items have valid sizes before we proceeed
1809      * (and resize the bin_window if required).
1810      */
1811     if (priv->layout_idle_id != 0)
1812         entangle_session_browser_layout(browser);
1813
1814     gdk_window_process_updates(priv->bin_window, TRUE);
1815 }
1816
1817
1818 static gboolean
1819 layout_callback(gpointer opaque)
1820 {
1821     EntangleSessionBrowser *browser = ENTANGLE_SESSION_BROWSER(opaque);
1822     EntangleSessionBrowserPrivate *priv = browser->priv;
1823
1824     priv->layout_idle_id = 0;
1825
1826     entangle_session_browser_layout(browser);
1827
1828     return FALSE;
1829 }
1830
1831
1832 static void
1833 entangle_session_browser_queue_layout(EntangleSessionBrowser *browser)
1834 {
1835     EntangleSessionBrowserPrivate *priv = browser->priv;
1836
1837     if (priv->layout_idle_id != 0)
1838         return;
1839
1840     priv->layout_idle_id =
1841         gdk_threads_add_idle_full(ENTANGLE_SESSION_BROWSER_PRIORITY_LAYOUT,
1842                                   layout_callback, browser, NULL);
1843 }
1844
1845
1846 static void
1847 entangle_session_browser_set_hadjustment_values(EntangleSessionBrowser *browser)
1848 {
1849     EntangleSessionBrowserPrivate *priv = browser->priv;
1850     GtkAllocation allocation;
1851     GtkAdjustment *adj = priv->hadjustment;
1852
1853     gtk_widget_get_allocation(GTK_WIDGET(browser), &allocation);
1854
1855     gtk_adjustment_configure(adj,
1856                              gtk_adjustment_get_value(adj),
1857                              0.0,
1858                              MAX(allocation.width, priv->width),
1859                              allocation.width * 0.1,
1860                              allocation.width * 0.9,
1861                              allocation.width);
1862 }
1863
1864
1865 static void
1866 entangle_session_browser_set_vadjustment_values(EntangleSessionBrowser *browser)
1867 {
1868     EntangleSessionBrowserPrivate *priv = browser->priv;
1869     GtkAllocation allocation;
1870     GtkAdjustment *adj = priv->vadjustment;
1871
1872     gtk_widget_get_allocation(GTK_WIDGET(browser), &allocation);
1873
1874     gtk_adjustment_configure(adj,
1875                              gtk_adjustment_get_value(adj),
1876                              0.0,
1877                              MAX(allocation.height, priv->height),
1878                              allocation.height * 0.1,
1879                              allocation.height * 0.9,
1880                              allocation.height);
1881 }
1882
1883
1884 static void
1885 entangle_session_browser_set_hadjustment(EntangleSessionBrowser *browser,
1886                                          GtkAdjustment *adjustment)
1887 {
1888     EntangleSessionBrowserPrivate *priv = browser->priv;
1889
1890     if (adjustment && priv->hadjustment == adjustment)
1891         return;
1892
1893     if (priv->hadjustment != NULL) {
1894         g_signal_handlers_disconnect_matched(priv->hadjustment,
1895                                              G_SIGNAL_MATCH_DATA,
1896                                              0, 0, NULL, NULL, browser);
1897         g_object_unref(priv->hadjustment);
1898     }
1899
1900     if (!adjustment)
1901         adjustment = gtk_adjustment_new(0.0, 0.0, 0.0,
1902                                         0.0, 0.0, 0.0);
1903
1904     g_signal_connect(adjustment, "value-changed",
1905                      G_CALLBACK(entangle_session_browser_adjustment_changed), browser);
1906     priv->hadjustment = g_object_ref_sink(adjustment);
1907     entangle_session_browser_set_hadjustment_values(browser);
1908
1909     g_object_notify(G_OBJECT(browser), "hadjustment");
1910 }
1911
1912
1913 static void
1914 entangle_session_browser_set_vadjustment(EntangleSessionBrowser *browser,
1915                                          GtkAdjustment *adjustment)
1916 {
1917     EntangleSessionBrowserPrivate *priv = browser->priv;
1918
1919     if (adjustment && priv->vadjustment == adjustment)
1920         return;
1921
1922     if (priv->vadjustment != NULL) {
1923         g_signal_handlers_disconnect_matched(priv->vadjustment,
1924                                              G_SIGNAL_MATCH_DATA,
1925                                              0, 0, NULL, NULL, browser);
1926         g_object_unref (priv->vadjustment);
1927     }
1928
1929     if (!adjustment)
1930         adjustment = gtk_adjustment_new(0.0, 0.0, 0.0,
1931                                         0.0, 0.0, 0.0);
1932
1933     g_signal_connect(adjustment, "value-changed",
1934                      G_CALLBACK(entangle_session_browser_adjustment_changed), browser);
1935     priv->vadjustment = g_object_ref_sink (adjustment);
1936     entangle_session_browser_set_vadjustment_values (browser);
1937
1938     g_object_notify(G_OBJECT(browser), "vadjustment");
1939 }
1940
1941
1942 static void
1943 entangle_session_browser_adjustment_changed(GtkAdjustment *adjustment G_GNUC_UNUSED,
1944                                             EntangleSessionBrowser *browser)
1945 {
1946     EntangleSessionBrowserPrivate *priv = browser->priv;
1947
1948     if (gtk_widget_get_realized(GTK_WIDGET(browser))) {
1949         gdk_window_move(priv->bin_window,
1950                         - gtk_adjustment_get_value(priv->hadjustment),
1951                         - gtk_adjustment_get_value(priv->vadjustment));
1952
1953         entangle_session_browser_process_updates(browser);
1954     }
1955 }
1956
1957
1958 /*
1959  * Local variables:
1960  *  c-indent-level: 4
1961  *  c-basic-offset: 4
1962  *  indent-tabs-mode: nil
1963  *  tab-width: 8
1964  * End:
1965  */