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