1 /*
2  * Photos - access, organize and share your photos on GNOME
3  * Copyright © 2012 – 2020 Red Hat, Inc.
4  * Copyright © 2009 Yorba Foundation
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 /* Based on code from:
21  *   + Documents
22  *   + Eye of GNOME
23  *   + Shotwell
24  */
25 
26 
27 #include "config.h"
28 
29 #include <math.h>
30 
31 #include <gdk/gdk.h>
32 #include <glib.h>
33 #include <tracker-sparql.h>
34 #include <libgd/gd.h>
35 
36 #include "photos-application.h"
37 #include "photos-device-item.h"
38 #include "photos-enums.h"
39 #include "photos-error.h"
40 #include "photos-facebook-item.h"
41 #include "photos-flickr-item.h"
42 #include "photos-gegl.h"
43 #include "photos-google-item.h"
44 #include "photos-local-item.h"
45 #include "photos-media-server-item.h"
46 #include "photos-offset-collection-view-controller.h"
47 #include "photos-offset-collections-controller.h"
48 #include "photos-offset-favorites-controller.h"
49 #include "photos-offset-import-controller.h"
50 #include "photos-offset-overview-controller.h"
51 #include "photos-offset-search-controller.h"
52 #include "photos-query.h"
53 #include "photos-share-point.h"
54 #include "photos-share-point-email.h"
55 #include "photos-share-point-google.h"
56 #include "photos-share-point-online.h"
57 #include "photos-source.h"
58 #include "photos-thumbnail-factory.h"
59 #include "photos-tool.h"
60 #include "photos-tool-colors.h"
61 #include "photos-tool-crop.h"
62 #include "photos-tool-enhance.h"
63 #include "photos-tool-filters.h"
64 #include "photos-tracker-collection-view-controller.h"
65 #include "photos-tracker-collections-controller.h"
66 #include "photos-tracker-favorites-controller.h"
67 #include "photos-tracker-import-controller.h"
68 #include "photos-tracker-overview-controller.h"
69 #include "photos-tracker-queue.h"
70 #include "photos-tracker-search-controller.h"
71 #include "photos-utils.h"
72 
73 
74 GdkPixbuf *
photos_utils_center_pixbuf(GdkPixbuf * pixbuf,gint size)75 photos_utils_center_pixbuf (GdkPixbuf *pixbuf, gint size)
76 {
77   GdkPixbuf *ret_val;
78   gint height;
79   gint pixbuf_size;
80   gint width;
81 
82   height = gdk_pixbuf_get_height (pixbuf);
83   width = gdk_pixbuf_get_width (pixbuf);
84 
85   pixbuf_size = MAX (height, width);
86   if (pixbuf_size >= size)
87     {
88       ret_val = g_object_ref (pixbuf);
89       goto out;
90     }
91 
92   ret_val = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, size, size);
93   gdk_pixbuf_fill (ret_val, 0x00000000);
94   gdk_pixbuf_copy_area (pixbuf, 0, 0, width, height, ret_val, (size - width) / 2, (size - height) / 2);
95 
96  out:
97   return ret_val;
98 }
99 
100 
101 gchar *
photos_utils_convert_path_to_uri(const gchar * path)102 photos_utils_convert_path_to_uri (const gchar *path)
103 {
104   g_autoptr (GFile) file = NULL;
105   gchar *uri = NULL;
106 
107   if (path == NULL)
108     {
109       uri = g_strdup ("");
110       goto out;
111     }
112 
113   file = g_file_new_for_path (path);
114   uri = g_file_get_uri (file);
115 
116  out:
117   g_return_val_if_fail (uri != NULL, NULL);
118   return uri;
119 }
120 
121 
122 GStrv
photos_utils_convert_paths_to_uris(const gchar * const * paths)123 photos_utils_convert_paths_to_uris (const gchar *const *paths)
124 {
125   GStrv uris = NULL;
126   guint i;
127   guint n_paths;
128 
129   if (paths == NULL)
130     goto out;
131 
132   n_paths = g_strv_length ((GStrv) paths);
133   uris = (GStrv) g_malloc0_n (n_paths + 1, sizeof (gchar *));
134 
135   for (i = 0; paths[i] != NULL; i++)
136     {
137       g_autofree gchar *uri = NULL;
138 
139       uri = photos_utils_convert_path_to_uri (paths[i]);
140       uris[i] = g_steal_pointer (&uri);
141     }
142 
143  out:
144   return uris;
145 }
146 
147 
148 GIcon *
photos_utils_create_collection_icon(gint base_size,GList * pixbufs)149 photos_utils_create_collection_icon (gint base_size, GList *pixbufs)
150 {
151   cairo_surface_t *surface; /* TODO: use g_autoptr */
152   cairo_t *cr; /* TODO: use g_autoptr */
153   GdkPixbuf *pix;
154   GIcon *ret_val;
155   GList *l;
156   g_autoptr (GtkStyleContext) context = NULL;
157   g_autoptr (GtkWidgetPath) path = NULL;
158   gint cur_x;
159   gint cur_y;
160   gint padding;
161   gint pix_height;
162   gint pix_width;
163   gint scale_size;
164   gint tile_size;
165   guint idx;
166   guint n_grid;
167   guint n_pixbufs;
168   guint n_tiles;
169 
170   n_pixbufs = g_list_length (pixbufs);
171   if (n_pixbufs < 3)
172     {
173       n_grid = 1;
174       n_tiles = 1;
175     }
176   else
177     {
178       n_grid = 2;
179       n_tiles = 4;
180     }
181 
182   padding = MAX (base_size / 10, 4);
183   tile_size = (base_size - ((n_grid + 1) * padding)) / n_grid;
184 
185   context = gtk_style_context_new ();
186   gtk_style_context_add_class (context, "photos-collection-icon");
187 
188   path = gtk_widget_path_new ();
189   gtk_widget_path_append_type (path, GTK_TYPE_ICON_VIEW);
190   gtk_style_context_set_path (context, path);
191 
192   surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, base_size, base_size);
193   cr = cairo_create (surface);
194 
195   gtk_render_background (context, cr, 0, 0, base_size, base_size);
196 
197   l = pixbufs;
198   idx = 0;
199   cur_x = padding;
200   cur_y = padding;
201 
202   while (l != NULL && idx < n_tiles)
203     {
204       pix = l->data;
205       pix_width = gdk_pixbuf_get_width (pix);
206       pix_height = gdk_pixbuf_get_height (pix);
207 
208       scale_size = MIN (pix_width, pix_height);
209 
210       cairo_save (cr);
211 
212       cairo_translate (cr, cur_x, cur_y);
213 
214       cairo_rectangle (cr, 0, 0,
215                        tile_size, tile_size);
216       cairo_clip (cr);
217 
218       cairo_scale (cr, (gdouble) tile_size / (gdouble) scale_size, (gdouble) tile_size / (gdouble) scale_size);
219       gdk_cairo_set_source_pixbuf (cr, pix, 0, 0);
220 
221       cairo_paint (cr);
222       cairo_restore (cr);
223 
224       idx++;
225       l = l->next;
226 
227       if ((idx % n_grid) == 0)
228         {
229           cur_x = padding;
230           cur_y += tile_size + padding;
231         }
232       else
233         {
234           cur_x += tile_size + padding;
235         }
236     }
237 
238   ret_val = G_ICON (gdk_pixbuf_get_from_surface (surface, 0, 0, base_size, base_size));
239 
240   cairo_surface_destroy (surface);
241   cairo_destroy (cr);
242 
243   return ret_val;
244 }
245 
246 
247 GdkPixbuf *
photos_utils_create_placeholder_icon_for_scale(const gchar * name,gint size,gint scale)248 photos_utils_create_placeholder_icon_for_scale (const gchar *name, gint size, gint scale)
249 {
250   GApplication *app;
251   g_autoptr (GdkPixbuf) centered_pixbuf = NULL;
252   g_autoptr (GdkPixbuf) pixbuf = NULL;
253   GdkPixbuf *ret_val = NULL;
254   g_autoptr (GIcon) icon = NULL;
255   GList *windows;
256   g_autoptr (GtkIconInfo) info = NULL;
257   GtkIconTheme *theme;
258   GtkStyleContext *context;
259   gint size_scaled;
260 
261   app = g_application_get_default ();
262   windows = gtk_application_get_windows (GTK_APPLICATION (app));
263   if (windows == NULL)
264     goto out;
265 
266   icon = g_themed_icon_new (name);
267   theme = gtk_icon_theme_get_default ();
268   info = gtk_icon_theme_lookup_by_gicon_for_scale (theme,
269                                                    icon,
270                                                    16,
271                                                    scale,
272                                                    GTK_ICON_LOOKUP_FORCE_SIZE | GTK_ICON_LOOKUP_FORCE_SYMBOLIC);
273   if (info == NULL)
274     goto out;
275 
276   context = gtk_widget_get_style_context (GTK_WIDGET (windows->data));
277 
278   {
279     g_autoptr (GError) error = NULL;
280 
281     pixbuf = gtk_icon_info_load_symbolic_for_context (info, context, NULL, &error);
282     if (error != NULL)
283       {
284         g_warning ("Unable to load icon '%s': %s", name, error->message);
285         goto out;
286       }
287   }
288 
289   size_scaled = size * scale;
290   centered_pixbuf = photos_utils_center_pixbuf (pixbuf, size_scaled);
291 
292   ret_val = centered_pixbuf;
293   centered_pixbuf = NULL;
294 
295  out:
296   return ret_val;
297 }
298 
299 
300 GIcon *
photos_utils_create_symbolic_icon_for_scale(const gchar * name,gint base_size,gint scale)301 photos_utils_create_symbolic_icon_for_scale (const gchar *name, gint base_size, gint scale)
302 {
303   g_autoptr (GIcon) icon = NULL;
304   GIcon *ret_val = NULL;
305   g_autoptr (GdkPixbuf) pixbuf = NULL;
306   g_autoptr (GtkIconInfo) info = NULL;
307   GtkIconTheme *theme;
308   g_autoptr (GtkStyleContext) style = NULL;
309   g_autoptr (GtkWidgetPath) path = NULL;
310   cairo_surface_t *icon_surface = NULL; /* TODO: use g_autoptr */
311   cairo_surface_t *surface; /* TODO: use g_autoptr */
312   cairo_t *cr; /* TODO: use g_autoptr */
313   g_autofree gchar *symbolic_name = NULL;
314   const gint bg_size = 24;
315   const gint emblem_margin = 4;
316   gint emblem_pos;
317   gint emblem_size;
318   gint total_size;
319   gint total_size_scaled;
320 
321   total_size = base_size / 2;
322   total_size_scaled = total_size * scale;
323   emblem_size = bg_size - emblem_margin * 2;
324 
325   surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, total_size_scaled, total_size_scaled);
326   cairo_surface_set_device_scale (surface, (gdouble) scale, (gdouble) scale);
327   cr = cairo_create (surface);
328 
329   style = gtk_style_context_new ();
330 
331   path = gtk_widget_path_new ();
332   gtk_widget_path_append_type (path, GTK_TYPE_ICON_VIEW);
333   gtk_style_context_set_path (style, path);
334 
335   gtk_style_context_add_class (style, "photos-icon-bg");
336 
337   gtk_render_background (style, cr, total_size - bg_size, total_size - bg_size, bg_size, bg_size);
338 
339   symbolic_name = g_strconcat (name, "-symbolic", NULL);
340   icon = g_themed_icon_new_with_default_fallbacks (symbolic_name);
341 
342   theme = gtk_icon_theme_get_default();
343   info = gtk_icon_theme_lookup_by_gicon_for_scale (theme, icon, emblem_size, scale, GTK_ICON_LOOKUP_FORCE_SIZE);
344   if (info == NULL)
345     goto out;
346 
347   pixbuf = gtk_icon_info_load_symbolic_for_context (info, style, NULL, NULL);
348   if (pixbuf == NULL)
349     goto out;
350 
351   icon_surface = gdk_cairo_surface_create_from_pixbuf (pixbuf, scale, NULL);
352 
353   emblem_pos = total_size - emblem_size - emblem_margin;
354   gtk_render_icon_surface (style, cr, icon_surface, emblem_pos, emblem_pos);
355 
356   ret_val = G_ICON (gdk_pixbuf_get_from_surface (surface, 0, 0, total_size_scaled, total_size_scaled));
357 
358  out:
359   cairo_surface_destroy (icon_surface);
360   cairo_surface_destroy (surface);
361   cairo_destroy (cr);
362 
363   return ret_val;
364 }
365 
366 
367 gboolean
photos_utils_create_thumbnail(GFile * file,const gchar * mime_type,gint64 mtime,GQuark orientation,gint64 original_height,gint64 original_width,const gchar * const * pipeline_uris,const gchar * thumbnail_path,GCancellable * cancellable,GError ** error)368 photos_utils_create_thumbnail (GFile *file,
369                                const gchar *mime_type,
370                                gint64 mtime,
371                                GQuark orientation,
372                                gint64 original_height,
373                                gint64 original_width,
374                                const gchar *const *pipeline_uris,
375                                const gchar *thumbnail_path,
376                                GCancellable *cancellable,
377                                GError **error)
378 {
379   g_autoptr (PhotosThumbnailFactory) factory = NULL;
380   gboolean ret_val = FALSE;
381 
382   factory = photos_thumbnail_factory_dup_singleton (NULL, NULL);
383   if (!photos_thumbnail_factory_generate_thumbnail (factory,
384                                                     file,
385                                                     mime_type,
386                                                     orientation,
387                                                     original_height,
388                                                     original_width,
389                                                     pipeline_uris,
390                                                     thumbnail_path,
391                                                     cancellable,
392                                                     error))
393     goto out;
394 
395   ret_val = TRUE;
396 
397  out:
398   return ret_val;
399 }
400 
401 
402 GVariant *
photos_utils_create_zoom_target_value(gdouble delta,PhotosZoomEvent event)403 photos_utils_create_zoom_target_value (gdouble delta, PhotosZoomEvent event)
404 {
405   GEnumClass *zoom_event_class = NULL; /* TODO: use g_autoptr */
406   GEnumValue *event_value;
407   GVariant *delta_value;
408   GVariant *event_nick_value;
409   GVariant *ret_val = NULL;
410   g_auto (GVariantBuilder) builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT);
411   const gchar *event_nick = "none";
412 
413   g_return_val_if_fail (delta >= 0.0, NULL);
414   g_return_val_if_fail (event != PHOTOS_ZOOM_EVENT_NONE, NULL);
415 
416   delta_value = g_variant_new_double (delta);
417   g_variant_builder_add (&builder, "{sv}", "delta", delta_value);
418 
419   zoom_event_class = G_ENUM_CLASS (g_type_class_ref (PHOTOS_TYPE_ZOOM_EVENT));
420 
421   event_value = g_enum_get_value (zoom_event_class, (gint) event);
422   if (event_value != NULL)
423     event_nick = event_value->value_nick;
424 
425   event_nick_value = g_variant_new_string (event_nick);
426   g_variant_builder_add (&builder, "{sv}", "event", event_nick_value);
427 
428   ret_val = g_variant_builder_end (&builder);
429 
430   g_type_class_unref (zoom_event_class);
431   g_return_val_if_fail (g_variant_is_floating (ret_val), ret_val);
432   return ret_val;
433 }
434 
435 
436 static GIcon *
photos_utils_get_thumbnail_icon(PhotosBaseItem * item)437 photos_utils_get_thumbnail_icon (PhotosBaseItem *item)
438 {
439   g_autoptr (GFile) thumb_file = NULL;
440   g_autoptr (GFileInfo) info = NULL;
441   GIcon *icon = NULL;
442   const gchar *thumb_path;
443   const gchar *uri;
444 
445   uri = photos_base_item_get_uri (item);
446   if (uri == NULL || uri[0] == '\0')
447     goto out;
448 
449   {
450     g_autoptr (GError) error = NULL;
451 
452     info = photos_base_item_query_info (item,
453                                         G_FILE_ATTRIBUTE_THUMBNAIL_PATH,
454                                         G_FILE_QUERY_INFO_NONE,
455                                         NULL,
456                                         &error);
457     if (error != NULL)
458       {
459         g_warning ("Unable to fetch thumbnail path for %s: %s", uri, error->message);
460         goto out;
461       }
462   }
463 
464   thumb_path = g_file_info_get_attribute_byte_string (info, G_FILE_ATTRIBUTE_THUMBNAIL_PATH);
465   if (thumb_path == NULL)
466     goto out;
467 
468   thumb_file = g_file_new_for_path (thumb_path);
469   icon = g_file_icon_new (thumb_file);
470 
471  out:
472   return icon;
473 }
474 
475 
476 GIcon *
photos_utils_get_icon_from_item(PhotosBaseItem * item)477 photos_utils_get_icon_from_item (PhotosBaseItem *item)
478 {
479   GIcon *icon = NULL;
480   gboolean is_remote = FALSE;
481   const gchar *identifier;
482   const gchar *mime_type;
483 
484   identifier = photos_base_item_get_identifier (item);
485   if (identifier != NULL)
486     {
487       if (g_str_has_prefix (identifier, "facebook:") ||
488           g_str_has_prefix (identifier, "flickr:") ||
489           g_str_has_prefix (identifier, "google:"))
490         is_remote = TRUE;
491     }
492 
493   if (!is_remote)
494     icon = photos_utils_get_thumbnail_icon (item);
495 
496   if (icon != NULL)
497     goto out;
498 
499   mime_type = photos_base_item_get_mime_type (item);
500   if (mime_type != NULL)
501     icon = g_content_type_get_icon (mime_type);
502 
503   if (icon != NULL)
504     goto out;
505 
506   if (photos_base_item_is_collection (item))
507     {
508       gint size;
509 
510       size = photos_utils_get_icon_size ();
511       icon = photos_utils_create_collection_icon (size, NULL);
512     }
513 
514   if (icon != NULL)
515     goto out;
516 
517   icon = g_themed_icon_new ("image-x-generic");
518 
519  out:
520   return icon;
521 }
522 
523 
524 gdouble
photos_utils_get_zoom_delta(GVariant * dictionary)525 photos_utils_get_zoom_delta (GVariant *dictionary)
526 {
527   gdouble delta;
528   gdouble ret_val = -1.0;
529 
530   g_return_val_if_fail (dictionary != NULL, -1.0);
531   g_return_val_if_fail (g_variant_is_of_type (dictionary, G_VARIANT_TYPE_VARDICT), -1.0);
532 
533   if (!g_variant_lookup (dictionary, "delta", "d", &delta))
534     goto out;
535 
536   ret_val = delta;
537 
538  out:
539   g_return_val_if_fail (ret_val >= 0.0, -1.0);
540   return ret_val;
541 }
542 
543 
544 PhotosZoomEvent
photos_utils_get_zoom_event(GVariant * dictionary)545 photos_utils_get_zoom_event (GVariant *dictionary)
546 {
547   GEnumClass *zoom_event_class = NULL;
548   GEnumValue *event_value;
549   PhotosZoomEvent ret_val = PHOTOS_ZOOM_EVENT_NONE;
550   const gchar *event_str;
551 
552   g_return_val_if_fail (dictionary != NULL, PHOTOS_ZOOM_EVENT_NONE);
553   g_return_val_if_fail (g_variant_is_of_type (dictionary, G_VARIANT_TYPE_VARDICT), PHOTOS_ZOOM_EVENT_NONE);
554 
555   if (!g_variant_lookup (dictionary, "event", "&s", &event_str))
556     goto out;
557 
558   zoom_event_class = G_ENUM_CLASS (g_type_class_ref (PHOTOS_TYPE_ZOOM_EVENT));
559 
560   event_value = g_enum_get_value_by_nick (zoom_event_class, event_str);
561   if (event_value == NULL)
562     event_value = g_enum_get_value_by_name (zoom_event_class, event_str);
563   if (event_value == NULL)
564     goto out;
565 
566   ret_val = (PhotosZoomEvent) event_value->value;
567 
568  out:
569   g_clear_pointer (&zoom_event_class, g_type_class_unref);
570   g_return_val_if_fail (ret_val != PHOTOS_ZOOM_EVENT_NONE, PHOTOS_ZOOM_EVENT_NONE);
571   return ret_val;
572 }
573 
574 
575 GdkPixbuf *
photos_utils_downscale_pixbuf_for_scale(GdkPixbuf * pixbuf,gint size,gint scale)576 photos_utils_downscale_pixbuf_for_scale (GdkPixbuf *pixbuf, gint size, gint scale)
577 {
578   GdkPixbuf *ret_val;
579   gint height;
580   gint pixbuf_size;
581   gint scaled_size;
582   gint width;
583 
584   height = gdk_pixbuf_get_height (pixbuf);
585   width = gdk_pixbuf_get_width (pixbuf);
586   pixbuf_size = MAX (height, width);
587 
588   scaled_size = size * scale;
589 
590   /* On Hi-Dpi displays, a pixbuf should never appear smaller than on
591    * Lo-Dpi.
592    *
593    * Sometimes, a pixbuf can be slightly smaller than size. eg.,
594    * server-generated thumbnails for remote tems. Scaling them up
595    * won't cause any discernible loss of quality and will make our
596    * letterboxed grid look nicer. 75% of 'scale' has been chosen as
597    * the arbitrary definition of 'slightly smaller'.
598    *
599    * Therefore, if a pixbuf lies between (3 * size / 4, size * scale)
600    * we scale it up to size * scale, so that it doesn't look smaller.
601    * Similarly, if a pixbuf is smaller than size, then we increase its
602    * dimensions by the scale factor.
603    */
604 
605   if (pixbuf_size == scaled_size)
606     {
607       ret_val = g_object_ref (pixbuf);
608     }
609   else if (pixbuf_size > 3 * size / 4)
610     {
611       if (height == width)
612         {
613           height = scaled_size;
614           width = scaled_size;
615         }
616       else if (height > width)
617         {
618           width = (gint) (0.5 + (gdouble) (width * scaled_size) / (gdouble) height);
619           height = scaled_size;
620         }
621       else
622         {
623           height = (gint) (0.5 + (gdouble) (height * scaled_size) / (gdouble) width);
624           width = scaled_size;
625         }
626 
627       height = MAX (height, 1);
628       width = MAX (width, 1);
629       ret_val = gdk_pixbuf_scale_simple (pixbuf, width, height, GDK_INTERP_BILINEAR);
630     }
631   else /* pixbuf_size <= size */
632     {
633       if (scale == 1)
634         {
635           ret_val = g_object_ref (pixbuf);
636         }
637       else
638         {
639           height *= scale;
640           width *= scale;
641 
642           height = MAX (height, 1);
643           width = MAX (width, 1);
644           ret_val = gdk_pixbuf_scale_simple (pixbuf, width, height, GDK_INTERP_BILINEAR);
645         }
646     }
647 
648   return ret_val;
649 }
650 
651 
652 void
photos_utils_draw_rectangle_handles(cairo_t * cr,gdouble x,gdouble y,gdouble width,gdouble height,gdouble offset,gdouble radius)653 photos_utils_draw_rectangle_handles (cairo_t *cr,
654                                      gdouble x,
655                                      gdouble y,
656                                      gdouble width,
657                                      gdouble height,
658                                      gdouble offset,
659                                      gdouble radius)
660 {
661   cairo_save (cr);
662 
663   cairo_new_sub_path (cr);
664   cairo_arc (cr, x - offset, y - offset, radius, 0.0, 2.0 * M_PI);
665   cairo_fill (cr);
666 
667   cairo_new_sub_path (cr);
668   cairo_arc (cr, x + width + offset, y - offset, radius, 0.0, 2.0 * M_PI);
669   cairo_fill (cr);
670 
671   cairo_new_sub_path (cr);
672   cairo_arc (cr, x + width + offset, y + height + offset, radius, 0.0, 2.0 * M_PI);
673   cairo_fill (cr);
674 
675   cairo_new_sub_path (cr);
676   cairo_arc (cr, x - offset, y + height + offset, radius, 0.0, 2.0 * M_PI);
677   cairo_fill (cr);
678 
679   cairo_restore (cr);
680 }
681 
682 
683 void
photos_utils_draw_rectangle_thirds(cairo_t * cr,gdouble x,gdouble y,gdouble width,gdouble height)684 photos_utils_draw_rectangle_thirds (cairo_t *cr, gdouble x, gdouble y, gdouble width, gdouble height)
685 {
686   const gdouble one_third_x = width / 3.0;
687   const gdouble one_third_y = height / 3.0;
688 
689   cairo_save (cr);
690 
691   cairo_move_to (cr, x + one_third_x, y);
692   cairo_line_to (cr, x + one_third_x, y + height);
693   cairo_stroke (cr);
694 
695   cairo_move_to (cr, x + 2.0 * one_third_x, y);
696   cairo_line_to (cr, x + 2.0 * one_third_x, y + height);
697   cairo_stroke (cr);
698 
699   cairo_move_to (cr, x, y + one_third_y);
700   cairo_line_to (cr, x + width, y + one_third_y);
701   cairo_stroke (cr);
702 
703   cairo_move_to (cr, x, y + 2.0 * one_third_y);
704   cairo_line_to (cr, x + width, y + 2.0 * one_third_y);
705   cairo_stroke (cr);
706 
707   cairo_restore (cr);
708 }
709 
710 
711 void
photos_utils_ensure_builtins(void)712 photos_utils_ensure_builtins (void)
713 {
714   static gsize once_init_value = 0;
715 
716   photos_utils_ensure_extension_points ();
717   photos_gegl_ensure_builtins ();
718 
719   if (g_once_init_enter (&once_init_value))
720     {
721       g_type_ensure (PHOTOS_TYPE_DEVICE_ITEM);
722       g_type_ensure (PHOTOS_TYPE_FACEBOOK_ITEM);
723       g_type_ensure (PHOTOS_TYPE_FLICKR_ITEM);
724       g_type_ensure (PHOTOS_TYPE_GOOGLE_ITEM);
725       g_type_ensure (PHOTOS_TYPE_LOCAL_ITEM);
726       g_type_ensure (PHOTOS_TYPE_MEDIA_SERVER_ITEM);
727 
728       g_type_ensure (PHOTOS_TYPE_SHARE_POINT_EMAIL);
729       g_type_ensure (PHOTOS_TYPE_SHARE_POINT_GOOGLE);
730 
731       g_type_ensure (PHOTOS_TYPE_TOOL_COLORS);
732       g_type_ensure (PHOTOS_TYPE_TOOL_CROP);
733       g_type_ensure (PHOTOS_TYPE_TOOL_ENHANCE);
734       g_type_ensure (PHOTOS_TYPE_TOOL_FILTERS);
735 
736       g_type_ensure (PHOTOS_TYPE_TRACKER_COLLECTION_VIEW_CONTROLLER);
737       g_type_ensure (PHOTOS_TYPE_TRACKER_COLLECTIONS_CONTROLLER);
738       g_type_ensure (PHOTOS_TYPE_TRACKER_FAVORITES_CONTROLLER);
739       g_type_ensure (PHOTOS_TYPE_TRACKER_IMPORT_CONTROLLER);
740       g_type_ensure (PHOTOS_TYPE_TRACKER_OVERVIEW_CONTROLLER);
741       g_type_ensure (PHOTOS_TYPE_TRACKER_SEARCH_CONTROLLER);
742 
743       g_once_init_leave (&once_init_value, 1);
744     }
745 }
746 
747 
748 void
photos_utils_ensure_extension_points(void)749 photos_utils_ensure_extension_points (void)
750 {
751   static gsize once_init_value = 0;
752 
753   if (g_once_init_enter (&once_init_value))
754     {
755       GIOExtensionPoint *extension_point;
756 
757       extension_point = g_io_extension_point_register (PHOTOS_BASE_ITEM_EXTENSION_POINT_NAME);
758       g_io_extension_point_set_required_type (extension_point, PHOTOS_TYPE_BASE_ITEM);
759 
760       extension_point = g_io_extension_point_register (PHOTOS_SHARE_POINT_EXTENSION_POINT_NAME);
761       g_io_extension_point_set_required_type (extension_point, PHOTOS_TYPE_SHARE_POINT);
762 
763       extension_point = g_io_extension_point_register (PHOTOS_SHARE_POINT_ONLINE_EXTENSION_POINT_NAME);
764       g_io_extension_point_set_required_type (extension_point, PHOTOS_TYPE_SHARE_POINT_ONLINE);
765 
766       extension_point = g_io_extension_point_register (PHOTOS_TOOL_EXTENSION_POINT_NAME);
767       g_io_extension_point_set_required_type (extension_point, PHOTOS_TYPE_TOOL);
768 
769       extension_point = g_io_extension_point_register (PHOTOS_TRACKER_CONTROLLER_EXTENSION_POINT_NAME);
770       g_io_extension_point_set_required_type (extension_point, PHOTOS_TYPE_TRACKER_CONTROLLER);
771 
772       g_once_init_leave (&once_init_value, 1);
773     }
774 }
775 
776 
777 gdouble
photos_utils_eval_radial_line(gdouble crop_center_x,gdouble crop_center_y,gdouble corner_x,gdouble corner_y,gdouble event_x)778 photos_utils_eval_radial_line (gdouble crop_center_x,
779                                gdouble crop_center_y,
780                                gdouble corner_x,
781                                gdouble corner_y,
782                                gdouble event_x)
783 {
784   gdouble decision_intercept;
785   gdouble decision_slope;
786   gdouble projected_y;
787 
788   decision_slope = (corner_y - crop_center_y) / (corner_x - crop_center_x);
789   decision_intercept = corner_y - (decision_slope * corner_x);
790   projected_y = decision_slope * event_x + decision_intercept;
791 
792   return projected_y;
793 }
794 
795 
796 gboolean
photos_utils_file_copy_as_thumbnail(GFile * source,GFile * destination,const gchar * original_uri,gint64 original_height,gint64 original_width,GCancellable * cancellable,GError ** error)797 photos_utils_file_copy_as_thumbnail (GFile *source,
798                                      GFile *destination,
799                                      const gchar *original_uri,
800                                      gint64 original_height,
801                                      gint64 original_width,
802                                      GCancellable *cancellable,
803                                      GError **error)
804 {
805   g_autoptr (GFileInputStream) istream = NULL;
806   g_autoptr (GFileOutputStream) ostream = NULL;
807   g_autoptr (GdkPixbuf) pixbuf = NULL;
808   gboolean ret_val = FALSE;
809   const gchar *prgname;
810   g_autofree gchar *original_height_str = NULL;
811   g_autofree gchar *original_width_str = NULL;
812 
813   g_return_val_if_fail (G_IS_FILE (source), FALSE);
814   g_return_val_if_fail (G_IS_FILE (destination), FALSE);
815   g_return_val_if_fail (original_uri != NULL && original_uri[0] != '\0', FALSE);
816   g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE);
817   g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
818 
819   istream = g_file_read (source, cancellable, error);
820   if (istream == NULL)
821     goto out;
822 
823   pixbuf = gdk_pixbuf_new_from_stream (G_INPUT_STREAM (istream), cancellable, error);
824   if (pixbuf == NULL)
825     goto out;
826 
827   ostream = g_file_replace (destination,
828                             NULL,
829                             FALSE,
830                             G_FILE_CREATE_PRIVATE | G_FILE_CREATE_REPLACE_DESTINATION,
831                             cancellable,
832                             error);
833   if (ostream == NULL)
834     goto out;
835 
836   original_height_str = g_strdup_printf ("%" G_GINT64_FORMAT, original_height);
837   original_width_str = g_strdup_printf ("%" G_GINT64_FORMAT, original_width);
838   prgname = g_get_prgname ();
839   if (!gdk_pixbuf_save_to_stream (pixbuf,
840                                   G_OUTPUT_STREAM (ostream),
841                                   "png",
842                                   cancellable,
843                                   error,
844                                   "tEXt::Software", prgname,
845                                   "tEXt::Thumb::URI", original_uri,
846                                   "tEXt::Thumb::Image::Height", original_height_str,
847                                   "tEXt::Thumb::Image::Width", original_width_str,
848                                   NULL))
849     {
850       goto out;
851     }
852 
853   ret_val = TRUE;
854 
855  out:
856   return ret_val;
857 }
858 
859 
860 void
photos_utils_get_controller(PhotosWindowMode mode,PhotosOffsetController ** out_offset_cntrlr,PhotosTrackerController ** out_trk_cntrlr)861 photos_utils_get_controller (PhotosWindowMode mode,
862                              PhotosOffsetController **out_offset_cntrlr,
863                              PhotosTrackerController **out_trk_cntrlr)
864 {
865   g_autoptr (PhotosOffsetController) offset_cntrlr = NULL;
866   g_autoptr (PhotosTrackerController) trk_cntrlr = NULL;
867 
868   g_return_if_fail (mode != PHOTOS_WINDOW_MODE_NONE);
869   g_return_if_fail (mode != PHOTOS_WINDOW_MODE_EDIT);
870   g_return_if_fail (mode != PHOTOS_WINDOW_MODE_PREVIEW);
871 
872   switch (mode)
873     {
874     case PHOTOS_WINDOW_MODE_COLLECTION_VIEW:
875       offset_cntrlr = photos_offset_collection_view_controller_dup_singleton ();
876       trk_cntrlr = photos_tracker_collection_view_controller_dup_singleton ();
877       break;
878 
879     case PHOTOS_WINDOW_MODE_COLLECTIONS:
880       offset_cntrlr = photos_offset_collections_controller_dup_singleton ();
881       trk_cntrlr = photos_tracker_collections_controller_dup_singleton ();
882       break;
883 
884     case PHOTOS_WINDOW_MODE_FAVORITES:
885       offset_cntrlr = photos_offset_favorites_controller_dup_singleton ();
886       trk_cntrlr = photos_tracker_favorites_controller_dup_singleton ();
887       break;
888 
889     case PHOTOS_WINDOW_MODE_IMPORT:
890       offset_cntrlr = photos_offset_import_controller_dup_singleton ();
891       trk_cntrlr = photos_tracker_import_controller_dup_singleton ();
892       break;
893 
894     case PHOTOS_WINDOW_MODE_OVERVIEW:
895       offset_cntrlr = photos_offset_overview_controller_dup_singleton ();
896       trk_cntrlr = photos_tracker_overview_controller_dup_singleton ();
897       break;
898 
899     case PHOTOS_WINDOW_MODE_SEARCH:
900       offset_cntrlr = photos_offset_search_controller_dup_singleton ();
901       trk_cntrlr = photos_tracker_search_controller_dup_singleton ();
902       break;
903 
904     case PHOTOS_WINDOW_MODE_NONE:
905     case PHOTOS_WINDOW_MODE_EDIT:
906     case PHOTOS_WINDOW_MODE_PREVIEW:
907     default:
908       g_assert_not_reached ();
909       break;
910     }
911 
912   if (out_offset_cntrlr != NULL)
913     g_set_object (out_offset_cntrlr, offset_cntrlr);
914 
915   if (out_trk_cntrlr != NULL)
916     g_set_object (out_trk_cntrlr, trk_cntrlr);
917 }
918 
919 
920 gdouble
photos_utils_get_double_from_sparql_cursor_with_default(TrackerSparqlCursor * cursor,PhotosQueryColumns column,gdouble default_value)921 photos_utils_get_double_from_sparql_cursor_with_default (TrackerSparqlCursor *cursor,
922                                                          PhotosQueryColumns column,
923                                                          gdouble default_value)
924 {
925   TrackerSparqlValueType value_type;
926   gdouble ret_val = default_value;
927 
928   value_type = tracker_sparql_cursor_get_value_type (cursor, column);
929   if (value_type == TRACKER_SPARQL_VALUE_TYPE_UNBOUND)
930     goto out;
931 
932   ret_val = tracker_sparql_cursor_get_double (cursor, column);
933 
934  out:
935   return ret_val;
936 }
937 
938 
939 gchar *
photos_utils_get_extension_from_mime_type(const gchar * mime_type)940 photos_utils_get_extension_from_mime_type (const gchar *mime_type)
941 {
942   g_autoptr (GSList) formats = NULL;
943   GSList *l;
944   gchar *ret_val = NULL;
945 
946   formats = gdk_pixbuf_get_formats ();
947 
948   for (l = formats; l != NULL; l = l->next)
949     {
950       GdkPixbufFormat *format = (GdkPixbufFormat*) l->data;
951       g_auto (GStrv) supported_mime_types = NULL;
952       guint i;
953 
954       supported_mime_types = gdk_pixbuf_format_get_mime_types (format);
955       for (i = 0; supported_mime_types[i] != NULL; i++)
956         {
957           if (g_strcmp0 (mime_type, supported_mime_types[i]) == 0)
958             {
959               ret_val = photos_utils_get_pixbuf_common_suffix (format);
960               break;
961             }
962         }
963 
964       if (ret_val != NULL)
965         break;
966     }
967 
968   return ret_val;
969 }
970 
971 
972 gint
photos_utils_get_icon_size(void)973 photos_utils_get_icon_size (void)
974 {
975   GApplication *app;
976   gint scale;
977   gint size;
978 
979   app = g_application_get_default ();
980   scale = photos_application_get_scale_factor (PHOTOS_APPLICATION (app));
981   size = photos_utils_get_icon_size_unscaled ();
982   return scale * size;
983 }
984 
985 
986 gint
photos_utils_get_icon_size_unscaled(void)987 photos_utils_get_icon_size_unscaled (void)
988 {
989   return 256;
990 }
991 
992 
993 gint64
photos_utils_get_integer_from_sparql_cursor_with_default(TrackerSparqlCursor * cursor,PhotosQueryColumns column,gint64 default_value)994 photos_utils_get_integer_from_sparql_cursor_with_default (TrackerSparqlCursor *cursor,
995                                                           PhotosQueryColumns column,
996                                                           gint64 default_value)
997 {
998   TrackerSparqlValueType value_type;
999   gint64 ret_val = default_value;
1000 
1001   value_type = tracker_sparql_cursor_get_value_type (cursor, column);
1002   if (value_type == TRACKER_SPARQL_VALUE_TYPE_UNBOUND)
1003     goto out;
1004 
1005   ret_val = tracker_sparql_cursor_get_integer (cursor, column);
1006 
1007  out:
1008   return ret_val;
1009 }
1010 
1011 
1012 gint64
photos_utils_get_mtime_from_sparql_cursor(TrackerSparqlCursor * cursor)1013 photos_utils_get_mtime_from_sparql_cursor (TrackerSparqlCursor *cursor)
1014 {
1015   const gchar *mtime_str;
1016   gint64 mtime = -1;
1017 
1018   mtime_str = tracker_sparql_cursor_get_string (cursor, PHOTOS_QUERY_COLUMNS_MTIME, NULL);
1019   if (mtime_str != NULL)
1020     {
1021       g_autoptr (GDateTime) date_modified = NULL;
1022 
1023       date_modified = g_date_time_new_from_iso8601 (mtime_str, NULL);
1024       if (date_modified != NULL)
1025         mtime = g_date_time_to_unix (date_modified);
1026     }
1027 
1028   if (mtime == -1)
1029     mtime = g_get_real_time () / 1000000;
1030 
1031   return mtime;
1032 }
1033 
1034 
1035 gchar *
photos_utils_get_pixbuf_common_suffix(GdkPixbufFormat * format)1036 photos_utils_get_pixbuf_common_suffix (GdkPixbufFormat *format)
1037 {
1038   g_auto (GStrv) extensions = NULL;
1039   gchar *result = NULL;
1040   gint i;
1041 
1042   if (format == NULL)
1043     return NULL;
1044 
1045   extensions = gdk_pixbuf_format_get_extensions (format);
1046   if (extensions[0] == NULL)
1047     return NULL;
1048 
1049   /* try to find 3-char suffix first, use the last occurence */
1050   for (i = 0; extensions [i] != NULL; i++)
1051     {
1052       if (strlen (extensions[i]) <= 3)
1053         {
1054           g_free (result);
1055           result = g_ascii_strdown (extensions[i], -1);
1056         }
1057     }
1058 
1059   /* otherwise take the first one */
1060   if (result == NULL)
1061     result = g_ascii_strdown (extensions[0], -1);
1062 
1063   return result;
1064 }
1065 
1066 
1067 const gchar *
photos_utils_get_provider_name(PhotosBaseManager * src_mngr,PhotosBaseItem * item)1068 photos_utils_get_provider_name (PhotosBaseManager *src_mngr, PhotosBaseItem *item)
1069 {
1070   PhotosSource *source;
1071   const gchar *name;
1072   const gchar *resource_urn;
1073 
1074   resource_urn = photos_base_item_get_resource_urn (item);
1075   source = PHOTOS_SOURCE (photos_base_manager_get_object_by_id (src_mngr, resource_urn));
1076   name = photos_source_get_name (source);
1077   return name;
1078 }
1079 
1080 
1081 gboolean
photos_utils_get_selection_mode(void)1082 photos_utils_get_selection_mode (void)
1083 {
1084   GAction *action;
1085   GApplication *app;
1086   g_autoptr (GVariant) state = NULL;
1087   gboolean selection_mode;
1088 
1089   app = g_application_get_default ();
1090   action = g_action_map_lookup_action (G_ACTION_MAP (app), "selection-mode");
1091 
1092   state = g_action_get_state (action);
1093   g_return_val_if_fail (state != NULL, FALSE);
1094 
1095   selection_mode = g_variant_get_boolean (state);
1096 
1097   return selection_mode;
1098 }
1099 
1100 
1101 GList *
photos_utils_get_urns_from_items(GList * items)1102 photos_utils_get_urns_from_items (GList *items)
1103 {
1104   GList *l;
1105   GList *urns = NULL;
1106 
1107   for (l = items; l != NULL; l = l->next)
1108     {
1109       GdMainBoxItem *box_item = GD_MAIN_BOX_ITEM (l->data);
1110       const gchar *id;
1111 
1112       id = gd_main_box_item_get_id (box_item);
1113       urns = g_list_prepend (urns, g_strdup (id));
1114     }
1115 
1116   return g_list_reverse (urns);
1117 }
1118 
1119 
1120 const gchar *
photos_utils_get_version(void)1121 photos_utils_get_version (void)
1122 {
1123   const gchar *ret_val = NULL;
1124 
1125 #ifdef PACKAGE_COMMIT_ID
1126   ret_val = PACKAGE_COMMIT_ID;
1127 #else
1128   ret_val = PACKAGE_VERSION;
1129 #endif
1130 
1131   return ret_val;
1132 }
1133 
1134 
1135 gboolean
photos_utils_is_flatpak(void)1136 photos_utils_is_flatpak (void)
1137 {
1138   gboolean ret_val;
1139 
1140   ret_val = g_file_test ("/.flatpak-info", G_FILE_TEST_EXISTS);
1141   return ret_val;
1142 }
1143 
1144 
1145 void
photos_utils_launch_online_accounts(const gchar * account_id)1146 photos_utils_launch_online_accounts (const gchar *account_id)
1147 {
1148   GApplication *app;
1149   g_autoptr (GDBusActionGroup) control_center = NULL;
1150   GDBusConnection *connection;
1151   GVariant *parameters;
1152   g_auto (GVariantBuilder) panel_parameters = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("av"));
1153 
1154   app = g_application_get_default ();
1155   connection = g_application_get_dbus_connection (app);
1156   control_center = g_dbus_action_group_get (connection, "org.gnome.ControlCenter", "/org/gnome/ControlCenter");
1157 
1158   if (account_id != NULL && account_id[0] != '\0')
1159     {
1160       GVariant *account_id_variant;
1161 
1162       account_id_variant = g_variant_new_string (account_id);
1163       g_variant_builder_add (&panel_parameters, "v", account_id_variant);
1164     }
1165 
1166   parameters = g_variant_new ("(s@av)", "online-accounts", g_variant_builder_end (&panel_parameters));
1167   g_action_group_activate_action (G_ACTION_GROUP (control_center), "launch-panel", parameters);
1168 }
1169 
1170 
1171 void
photos_utils_list_box_header_func(GtkListBoxRow * row,GtkListBoxRow * before,gpointer user_data)1172 photos_utils_list_box_header_func (GtkListBoxRow *row, GtkListBoxRow *before, gpointer user_data)
1173 {
1174   GtkWidget *header;
1175 
1176   if (before == NULL)
1177     {
1178       gtk_list_box_row_set_header (row, NULL);
1179       return;
1180     }
1181 
1182   header = gtk_list_box_row_get_header (row);
1183   if (header == NULL)
1184     {
1185       header = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
1186       gtk_widget_show (header);
1187       gtk_list_box_row_set_header (row, header);
1188     }
1189 }
1190 
1191 
1192 GAppLaunchContext *
photos_utils_new_app_launch_context_from_widget(GtkWidget * widget)1193 photos_utils_new_app_launch_context_from_widget (GtkWidget *widget)
1194 {
1195   GAppLaunchContext *ret_val = NULL;
1196   g_autoptr (GdkAppLaunchContext) ctx = NULL;
1197   GdkDisplay *display = NULL;
1198   GdkScreen *screen = NULL;
1199 
1200   if (widget != NULL)
1201     {
1202       screen = gtk_widget_get_screen (widget);
1203       display = gdk_screen_get_display (screen);
1204     }
1205 
1206   if (display == NULL)
1207     display = gdk_display_get_default ();
1208 
1209   ctx = gdk_display_get_app_launch_context (display);
1210   if (screen != NULL)
1211     gdk_app_launch_context_set_screen (ctx, screen);
1212 
1213   ret_val = G_APP_LAUNCH_CONTEXT (g_steal_pointer (&ctx));
1214   return ret_val;
1215 }
1216 
1217 
1218 void
photos_utils_object_list_free_full(GList * objects)1219 photos_utils_object_list_free_full (GList *objects)
1220 {
1221   g_list_free_full (objects, g_object_unref);
1222 }
1223 
1224 
1225 gchar *
photos_utils_print_zoom_action_detailed_name(const gchar * action_name,gdouble delta,PhotosZoomEvent event)1226 photos_utils_print_zoom_action_detailed_name (const gchar *action_name, gdouble delta, PhotosZoomEvent event)
1227 {
1228   g_autoptr (GVariant) target_value = NULL;
1229   gchar *ret_val = NULL;
1230 
1231   g_return_val_if_fail (action_name != NULL && action_name[0] != '\0', NULL);
1232   g_return_val_if_fail (delta >= 0.0, NULL);
1233   g_return_val_if_fail (event != PHOTOS_ZOOM_EVENT_NONE, NULL);
1234 
1235   target_value = photos_utils_create_zoom_target_value (delta, event);
1236   target_value = g_variant_ref_sink (target_value);
1237 
1238   ret_val = g_action_print_detailed_name (action_name, target_value);
1239 
1240   return ret_val;
1241 }
1242 
1243 
1244 static gboolean
photos_utils_adjustment_can_scroll(GtkAdjustment * adjustment)1245 photos_utils_adjustment_can_scroll (GtkAdjustment *adjustment)
1246 {
1247   gdouble lower;
1248   gdouble page_size;
1249   gdouble upper;
1250 
1251   g_return_val_if_fail (GTK_IS_ADJUSTMENT (adjustment), FALSE);
1252 
1253   lower = gtk_adjustment_get_lower (adjustment);
1254   page_size = gtk_adjustment_get_page_size (adjustment);
1255   upper = gtk_adjustment_get_upper (adjustment);
1256 
1257   return upper - lower > page_size;
1258 }
1259 
1260 
1261 gboolean
photos_utils_scrolled_window_can_scroll(GtkScrolledWindow * scrolled_window)1262 photos_utils_scrolled_window_can_scroll (GtkScrolledWindow *scrolled_window)
1263 {
1264   GtkAdjustment *adjustment;
1265   gboolean ret_val = TRUE;
1266 
1267   g_return_val_if_fail (GTK_IS_SCROLLED_WINDOW (scrolled_window), FALSE);
1268 
1269   adjustment = gtk_scrolled_window_get_hadjustment (scrolled_window);
1270   if (photos_utils_adjustment_can_scroll (adjustment))
1271     goto out;
1272 
1273   adjustment = gtk_scrolled_window_get_vadjustment (scrolled_window);
1274   if (photos_utils_adjustment_can_scroll (adjustment))
1275     goto out;
1276 
1277   ret_val = FALSE;
1278 
1279  out:
1280   return ret_val;
1281 }
1282 
1283 
1284 static void
photos_utils_adjustment_scroll(GtkAdjustment * adjustment,gdouble delta)1285 photos_utils_adjustment_scroll (GtkAdjustment *adjustment, gdouble delta)
1286 {
1287   gdouble value;
1288 
1289   g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment));
1290 
1291   value = gtk_adjustment_get_value (adjustment);
1292   value += delta;
1293   gtk_adjustment_set_value (adjustment, value);
1294 }
1295 
1296 
1297 void
photos_utils_scrolled_window_scroll(GtkScrolledWindow * scrolled_window,gdouble delta_x,gdouble delta_y)1298 photos_utils_scrolled_window_scroll (GtkScrolledWindow *scrolled_window, gdouble delta_x, gdouble delta_y)
1299 {
1300   GtkAdjustment *adjustment;
1301 
1302   g_return_if_fail (GTK_IS_SCROLLED_WINDOW (scrolled_window));
1303   g_return_if_fail (photos_utils_scrolled_window_can_scroll (scrolled_window));
1304 
1305   adjustment = gtk_scrolled_window_get_hadjustment (scrolled_window);
1306   photos_utils_adjustment_scroll (adjustment, delta_x);
1307 
1308   adjustment = gtk_scrolled_window_get_vadjustment (scrolled_window);
1309   photos_utils_adjustment_scroll (adjustment, delta_y);
1310 }
1311 
1312 
1313 static void
photos_utils_update_executed(GObject * source_object,GAsyncResult * res,gpointer user_data)1314 photos_utils_update_executed (GObject *source_object, GAsyncResult *res, gpointer user_data)
1315 {
1316   TrackerSparqlConnection *connection = TRACKER_SPARQL_CONNECTION (source_object);
1317   const gchar *urn = (gchar *) user_data;
1318 
1319   {
1320     g_autoptr (GError) error = NULL;
1321 
1322     tracker_sparql_connection_update_finish (connection, res, &error);
1323     if (error != NULL)
1324       g_warning ("Unable to update %s: %s", urn, error->message);
1325   }
1326 }
1327 
1328 
1329 void
photos_utils_set_edited_name(const gchar * urn,const gchar * title)1330 photos_utils_set_edited_name (const gchar *urn, const gchar *title)
1331 {
1332   g_autoptr (PhotosQuery) query = NULL;
1333   g_autoptr (PhotosTrackerQueue) queue = NULL;
1334   g_autofree gchar *sparql = NULL;
1335 
1336   sparql = g_strdup_printf ("WITH tracker:Pictures "
1337                             "DELETE { <%s> nie:title ?title } "
1338                             "INSERT { "
1339                             "  <%s> a nmm:Photo ; nie:title \"%s\" . "
1340                             "}"
1341                             "WHERE { <%s> nie:title ?title }", urn, urn, title, urn);
1342 
1343   query = photos_query_new (NULL, sparql);
1344 
1345   {
1346     g_autoptr (GError) error = NULL;
1347 
1348     queue = photos_tracker_queue_dup_singleton (NULL, &error);
1349     if (G_UNLIKELY (error != NULL))
1350       {
1351         g_warning ("Unable to set edited name %s: %s", urn, error->message);
1352         goto out;
1353       }
1354   }
1355 
1356   photos_tracker_queue_update (queue, query, NULL, photos_utils_update_executed, g_strdup (urn), g_free);
1357 
1358  out:
1359   return;
1360 }
1361 
1362 
1363 void
photos_utils_set_favorite(const gchar * urn,gboolean is_favorite)1364 photos_utils_set_favorite (const gchar *urn, gboolean is_favorite)
1365 {
1366   g_autoptr (PhotosQuery) query = NULL;
1367   g_autoptr (PhotosTrackerQueue) queue = NULL;
1368   g_autofree gchar *sparql = NULL;
1369 
1370   if (is_favorite)
1371     {
1372       sparql = g_strdup_printf ("INSERT DATA { "
1373                                 "  GRAPH tracker:Pictures {"
1374                                 "    <%s> a nmm:Photo ; nao:hasTag nao:predefined-tag-favorite ."
1375                                 "  } "
1376                                 "}", urn);
1377     }
1378   else
1379     {
1380       sparql = g_strdup_printf ("DELETE DATA {"
1381                                 "  GRAPH tracker:Pictures {"
1382                                 "    <%s> nao:hasTag nao:predefined-tag-favorite "
1383                                 "  } "
1384                                 "}", urn);
1385     }
1386 
1387   query = photos_query_new (NULL, sparql);
1388 
1389   {
1390     g_autoptr (GError) error = NULL;
1391 
1392     queue = photos_tracker_queue_dup_singleton (NULL, &error);
1393     if (G_UNLIKELY (error != NULL))
1394       {
1395         g_warning ("Unable to set favorite %s: %s", urn, error->message);
1396         goto out;
1397       }
1398   }
1399 
1400   photos_tracker_queue_update (queue, query, NULL, photos_utils_update_executed, g_strdup (urn), g_free);
1401 
1402  out:
1403   return;
1404 }
1405 
1406 
1407 gboolean
photos_utils_set_string(gchar ** string_ptr,const gchar * new_string)1408 photos_utils_set_string (gchar **string_ptr, const gchar *new_string)
1409 {
1410   gboolean ret_val = FALSE;
1411 
1412   g_return_val_if_fail (string_ptr != NULL, FALSE);
1413 
1414   if (*string_ptr == new_string)
1415     goto out;
1416 
1417   if (g_strcmp0 (*string_ptr, new_string) == 0)
1418     goto out;
1419 
1420   g_free (*string_ptr);
1421   *string_ptr = g_strdup (new_string);
1422 
1423   ret_val = TRUE;
1424 
1425  out:
1426   return ret_val;
1427 }
1428 
1429 
1430 gboolean
photos_utils_take_string(gchar ** string_ptr,gchar * new_string)1431 photos_utils_take_string (gchar **string_ptr, gchar *new_string)
1432 {
1433   gboolean ret_val = FALSE;
1434 
1435   g_return_val_if_fail (string_ptr != NULL, FALSE);
1436 
1437   if (*string_ptr == new_string)
1438     {
1439       new_string = NULL;
1440       goto out;
1441     }
1442 
1443   if (g_strcmp0 (*string_ptr, new_string) == 0)
1444     goto out;
1445 
1446   g_free (*string_ptr);
1447   *string_ptr = new_string;
1448   new_string = NULL;
1449 
1450   ret_val = TRUE;
1451 
1452  out:
1453   g_free (new_string);
1454   return ret_val;
1455 }
1456