Refactor image loading code to allow easier access to pixbuf
[entangle:entangle.git] / src / frontend / entangle-session-browser.c
1 /*
2  *  Entangle: Entangle Assists Photograph Aquisition
3  *
4  *  Copyright (C) 2009 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_GET_PRIVATE(obj)                           \
29     (G_TYPE_INSTANCE_GET_PRIVATE((obj), ENTANGLE_TYPE_SESSION_BROWSER, EntangleSessionBrowserPrivate))
30
31 struct _EntangleSessionBrowserPrivate {
32     EntangleSession *session;
33     EntangleThumbnailLoader *loader;
34
35     gulong sigImageAdded;
36     gulong sigThumbReady;
37
38     GdkPixbuf *blank;
39
40     GtkListStore *model;
41 };
42
43 G_DEFINE_TYPE(EntangleSessionBrowser, entangle_session_browser, GTK_TYPE_ICON_VIEW);
44
45 enum {
46     PROP_O,
47     PROP_SESSION,
48     PROP_LOADER,
49 };
50
51 enum {
52     FIELD_IMAGE,
53     FIELD_PIXMAP,
54     FIELD_LASTMOD,
55     FIELD_NAME,
56
57     FIELD_LAST,
58 };
59
60 static void do_thumb_loaded(EntanglePixbufLoader *loader,
61                             EntangleImage *image,
62                             gpointer data)
63 {
64     EntangleSessionBrowser *browser = data;
65     EntangleSessionBrowserPrivate *priv = browser->priv;
66     GdkPixbuf *pixbuf;
67     GtkTreeIter iter;
68
69     ENTANGLE_DEBUG("Got pixbuf update on %p", image);
70
71     pixbuf = entangle_pixbuf_loader_get_pixbuf(loader, image);
72     if (!pixbuf)
73         return;
74
75     if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(priv->model), &iter))
76         return;
77
78     do {
79         EntangleImage *thisimage;
80         gtk_tree_model_get(GTK_TREE_MODEL(priv->model), &iter, FIELD_IMAGE, &thisimage, -1);
81
82         if (image == thisimage) {
83             gtk_list_store_set(priv->model, &iter, FIELD_PIXMAP, pixbuf, -1);
84             break;
85         }
86
87     } while (gtk_tree_model_iter_next(GTK_TREE_MODEL(priv->model), &iter));
88 }
89
90 static void do_image_added(EntangleSession *session G_GNUC_UNUSED,
91                            EntangleImage *img,
92                            gpointer data)
93 {
94     EntangleSessionBrowser *browser = data;
95     EntangleSessionBrowserPrivate *priv = browser->priv;
96     GtkTreeIter iter;
97     GtkTreePath *path = NULL;
98     int mod = entangle_image_get_last_modified(img);
99     gchar *name = g_path_get_basename(entangle_image_get_filename(img));
100
101     ENTANGLE_DEBUG("Request image %s for new image", entangle_image_get_filename(img));
102     entangle_pixbuf_loader_load(ENTANGLE_PIXBUF_LOADER(priv->loader), img);
103
104     gtk_list_store_append(priv->model, &iter);
105
106     /* XXX what's our refcount policy going to be for pixbuf.... */
107     gtk_list_store_set(priv->model, &iter,
108                        FIELD_IMAGE, img,
109                        FIELD_PIXMAP, priv->blank,
110                        FIELD_LASTMOD, mod,
111                        FIELD_NAME, name,
112                        -1);
113     ENTANGLE_DEBUG("ADD IMAGE EXTRA %p", img);
114     path = gtk_tree_model_get_path(GTK_TREE_MODEL(priv->model), &iter);
115
116     gtk_icon_view_select_path(GTK_ICON_VIEW(browser), path);
117     gtk_icon_view_scroll_to_path(GTK_ICON_VIEW(browser), path, FALSE, 0, 0);
118
119     gtk_tree_path_free(path);
120
121     gtk_widget_queue_resize(GTK_WIDGET(browser));
122 }
123
124
125
126 static void do_model_unload(EntangleSessionBrowser *browser)
127 {
128     EntangleSessionBrowserPrivate *priv = browser->priv;
129     int count;
130
131     ENTANGLE_DEBUG("Unload model");
132
133     g_signal_handler_disconnect(priv->session,
134                                 priv->sigImageAdded);
135     g_signal_handler_disconnect(priv->loader,
136                                 priv->sigThumbReady);
137
138     count = entangle_session_image_count(priv->session);
139     for (int i = 0 ; i < count ; i++) {
140         EntangleImage *img = entangle_session_image_get(priv->session, i);
141         entangle_pixbuf_loader_unload(ENTANGLE_PIXBUF_LOADER(priv->loader), img);
142     }
143
144     g_object_unref(priv->blank);
145     gtk_list_store_clear(priv->model);
146 }
147
148 static void do_model_load(EntangleSessionBrowser *browser)
149 {
150     EntangleSessionBrowserPrivate *priv = browser->priv;
151     int count;
152     int width;
153     int height;
154
155     ENTANGLE_DEBUG("Load model");
156
157     g_object_get(priv->loader,
158                  "width", &width,
159                  "height", &height,
160                  NULL);
161
162     priv->blank = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, width, height);
163     gdk_pixbuf_fill(priv->blank, 0x000000FF);
164
165     priv->sigImageAdded = g_signal_connect(priv->session, "session-image-added",
166                                            G_CALLBACK(do_image_added), browser);
167     priv->sigThumbReady = g_signal_connect(priv->loader, "pixbuf-loaded",
168                                            G_CALLBACK(do_thumb_loaded), browser);
169
170     count = entangle_session_image_count(priv->session);
171     for (int i = 0 ; i < count ; i++) {
172         EntangleImage *img = entangle_session_image_get(priv->session, i);
173         int mod = entangle_image_get_last_modified(img);
174         GtkTreeIter iter;
175         gchar *name = g_path_get_basename(entangle_image_get_filename(img));
176
177         gtk_list_store_append(priv->model, &iter);
178         ENTANGLE_DEBUG("ADD IMAGE FIRST %p", img);
179         /* XXX what's our refcount policy going to be for pixbuf.... */
180         gtk_list_store_set(priv->model, &iter,
181                            FIELD_IMAGE, img,
182                            FIELD_PIXMAP, priv->blank,
183                            FIELD_LASTMOD, mod,
184                            FIELD_NAME, name,
185                            -1);
186
187         entangle_pixbuf_loader_load(ENTANGLE_PIXBUF_LOADER(priv->loader), img);
188         //g_object_unref(cam);
189     }
190
191     if (count) {
192         GtkTreePath *path = NULL;
193         path = gtk_tree_path_new_from_indices(count - 1, -1);
194
195         gtk_icon_view_select_path(GTK_ICON_VIEW(browser), path);
196         gtk_icon_view_scroll_to_path(GTK_ICON_VIEW(browser), path, FALSE, 0, 0);
197
198         gtk_tree_path_free(path);
199     }
200 }
201
202
203 static gint
204 do_image_sort_modified(GtkTreeModel *model,
205                        GtkTreeIter  *a,
206                        GtkTreeIter  *b,
207                        gpointer data G_GNUC_UNUSED)
208 {
209     gint ai, bi;
210
211     gtk_tree_model_get(model, a, FIELD_LASTMOD, &ai, -1);
212     gtk_tree_model_get(model, b, FIELD_LASTMOD, &bi, -1);
213
214     return ai - bi;
215 }
216
217
218
219 static void entangle_session_browser_get_property(GObject *object,
220                                               guint prop_id,
221                                               GValue *value,
222                                               GParamSpec *pspec)
223 {
224     EntangleSessionBrowser *browser = ENTANGLE_SESSION_BROWSER(object);
225     EntangleSessionBrowserPrivate *priv = browser->priv;
226
227     switch (prop_id)
228         {
229         case PROP_SESSION:
230             g_value_set_object(value, priv->session);
231             break;
232
233         case PROP_LOADER:
234             g_value_set_object(value, priv->loader);
235             break;
236
237         default:
238             G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
239         }
240 }
241
242 static void entangle_session_browser_set_property(GObject *object,
243                                               guint prop_id,
244                                               const GValue *value,
245                                               GParamSpec *pspec)
246 {
247     EntangleSessionBrowser *browser = ENTANGLE_SESSION_BROWSER(object);
248
249     ENTANGLE_DEBUG("Set prop on session browser %d", prop_id);
250
251     switch (prop_id)
252         {
253         case PROP_SESSION:
254             entangle_session_browser_set_session(browser, g_value_get_object(value));
255             break;
256
257         case PROP_LOADER:
258             entangle_session_browser_set_thumbnail_loader(browser, g_value_get_object(value));
259             break;
260
261         default:
262             G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
263         }
264 }
265
266 static void entangle_session_browser_finalize (GObject *object)
267 {
268     EntangleSessionBrowser *browser = ENTANGLE_SESSION_BROWSER(object);
269     EntangleSessionBrowserPrivate *priv = browser->priv;
270
271     if (priv->session && priv->loader)
272         do_model_unload(browser);
273
274     if (priv->session)
275         g_object_unref(priv->session);
276     if (priv->loader)
277         g_object_unref(priv->loader);
278
279     G_OBJECT_CLASS (entangle_session_browser_parent_class)->finalize (object);
280 }
281
282
283 static void entangle_session_browser_class_init(EntangleSessionBrowserClass *klass)
284 {
285     GObjectClass *object_class = G_OBJECT_CLASS(klass);
286
287     object_class->finalize = entangle_session_browser_finalize;
288     object_class->get_property = entangle_session_browser_get_property;
289     object_class->set_property = entangle_session_browser_set_property;
290
291     g_object_class_install_property(object_class,
292                                     PROP_SESSION,
293                                     g_param_spec_object("session",
294                                                         "Session",
295                                                         "Session to be displayed",
296                                                         ENTANGLE_TYPE_SESSION,
297                                                         G_PARAM_READWRITE |
298                                                         G_PARAM_STATIC_NAME |
299                                                         G_PARAM_STATIC_NICK |
300                                                         G_PARAM_STATIC_BLURB));
301
302     g_object_class_install_property(object_class,
303                                     PROP_LOADER,
304                                     g_param_spec_object("thumbnail-loader",
305                                                         "Thumbnail loader",
306                                                         "Thumbnail loader",
307                                                         ENTANGLE_TYPE_THUMBNAIL_LOADER,
308                                                         G_PARAM_READWRITE |
309                                                         G_PARAM_STATIC_NAME |
310                                                         G_PARAM_STATIC_NICK |
311                                                         G_PARAM_STATIC_BLURB));
312
313     g_type_class_add_private(klass, sizeof(EntangleSessionBrowserPrivate));
314 }
315
316 EntangleSessionBrowser *entangle_session_browser_new(void)
317 {
318     return ENTANGLE_SESSION_BROWSER(g_object_new(ENTANGLE_TYPE_SESSION_BROWSER, NULL));
319 }
320
321
322 static void entangle_session_browser_init(EntangleSessionBrowser *browser)
323 {
324     EntangleSessionBrowserPrivate *priv;
325     const GtkTargetEntry const targets[] = {
326         { g_strdup("demo"), GTK_TARGET_OTHER_APP, 1 },
327     };
328     int ntargets = 1;
329
330     priv = browser->priv = ENTANGLE_SESSION_BROWSER_GET_PRIVATE(browser);
331     memset(priv, 0, sizeof *priv);
332
333     priv->model = gtk_list_store_new(FIELD_LAST, ENTANGLE_TYPE_IMAGE, GDK_TYPE_PIXBUF, G_TYPE_INT, G_TYPE_STRING);
334
335     gtk_icon_view_set_text_column(GTK_ICON_VIEW(browser), FIELD_NAME);
336     gtk_icon_view_set_pixbuf_column(GTK_ICON_VIEW(browser), FIELD_PIXMAP);
337     gtk_icon_view_set_selection_mode(GTK_ICON_VIEW(browser), GTK_SELECTION_SINGLE);
338     gtk_icon_view_set_model(GTK_ICON_VIEW(browser), GTK_TREE_MODEL(priv->model));
339
340     gtk_tree_sortable_set_default_sort_func(GTK_TREE_SORTABLE(priv->model),
341                                             do_image_sort_modified, NULL, NULL);
342     gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(priv->model),
343                                          GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID,
344                                          GTK_SORT_ASCENDING);
345
346     gtk_icon_view_enable_model_drag_source(GTK_ICON_VIEW(browser),
347                                            GDK_BUTTON1_MASK,
348                                            targets,
349                                            ntargets,
350                                            GDK_ACTION_PRIVATE);
351
352     gtk_icon_view_set_orientation(GTK_ICON_VIEW(browser), GTK_ORIENTATION_VERTICAL);
353     /* XXX gross hack - GtkIconView doesn't seem to have a better
354      * way to force everything into a single row. Perhaps we should
355      * just right a new widget for our needs */
356     gtk_icon_view_set_columns(GTK_ICON_VIEW(browser), 10000);
357 }
358
359
360 EntangleImage *entangle_session_browser_selected_image(EntangleSessionBrowser *browser)
361 {
362     EntangleSessionBrowserPrivate *priv = browser->priv;
363     GList *items;
364     EntangleImage *img = NULL;
365     GtkTreePath *path;
366     GtkTreeIter iter;
367     GValue val;
368
369     items = gtk_icon_view_get_selected_items(GTK_ICON_VIEW(browser));
370
371     if (!items)
372         return NULL;
373
374     path = g_list_nth_data(items, 0);
375     if (!path)
376         goto cleanup;
377
378     if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(priv->model), &iter, path))
379         goto cleanup;
380
381     memset(&val, 0, sizeof val);
382     gtk_tree_model_get_value(GTK_TREE_MODEL(priv->model), &iter, 0, &val);
383
384     img = g_value_get_object(&val);
385
386  cleanup:
387     g_list_foreach(items, (GFunc)(gtk_tree_path_free), NULL);
388     g_list_free(items);
389     return img;
390 }
391
392
393 void entangle_session_browser_set_thumbnail_loader(EntangleSessionBrowser *browser,
394                                                EntangleThumbnailLoader *loader)
395 {
396     EntangleSessionBrowserPrivate *priv = browser->priv;
397
398     if (priv->loader) {
399         if (priv->session)
400             do_model_unload(browser);
401
402         g_object_unref(priv->loader);
403     }
404     priv->loader = loader;
405     if (priv->loader) {
406         g_object_ref(priv->loader);
407
408         if (priv->session)
409             do_model_load(browser);
410     }
411 }
412
413
414 EntangleThumbnailLoader *entangle_session_browser_get_thumbnail_loader(EntangleSessionBrowser *browser)
415 {
416     EntangleSessionBrowserPrivate *priv = browser->priv;
417
418     return priv->loader;
419 }
420
421
422 void entangle_session_browser_set_session(EntangleSessionBrowser *browser,
423                                       EntangleSession *session)
424 {
425     EntangleSessionBrowserPrivate *priv = browser->priv;
426
427     if (priv->session) {
428         if (priv->loader)
429             do_model_unload(browser);
430         g_object_unref(priv->session);
431     }
432     priv->session = session;
433     if (priv->session) {
434         g_object_ref(priv->session);
435
436         if (priv->loader)
437             do_model_load(browser);
438     }
439 }
440
441
442 EntangleSession *entangle_session_browser_get_session(EntangleSessionBrowser *browser)
443 {
444     EntangleSessionBrowserPrivate *priv = browser->priv;
445
446     return priv->session;
447 }
448
449 /*
450  * Local variables:
451  *  c-indent-level: 4
452  *  c-basic-offset: 4
453  *  indent-tabs-mode: nil
454  *  tab-width: 8
455  * End:
456  */