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