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