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