1 /*
2  * frogr-add-to-set-dialog.c -- 'Add to set' dialog
3  *
4  * Copyright (C) 2010-2018 Mario Sanchez Prada
5  * Authors: Mario Sanchez Prada <msanchez@gnome.org>
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of version 3 of the GNU General Public
9  * License as published by the Free Software Foundation.
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 GNU
14  * 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 "frogr-add-to-set-dialog.h"
22 
23 #include "frogr-controller.h"
24 #include "frogr-model.h"
25 #include "frogr-photoset.h"
26 #include "frogr-picture.h"
27 
28 #include <config.h>
29 #include <glib/gi18n.h>
30 
31 #define MINIMUM_WINDOW_WIDTH 540
32 #define MINIMUM_WINDOW_HEIGHT 420
33 
34 
35 struct _FrogrAddToSetDialog {
36   GtkDialog parent;
37 
38   GtkWidget *treeview;
39   GtkTreeModel *treemodel;
40 
41   GtkTreeViewColumn *checkbox_col;
42   GtkTreeViewColumn *title_col;
43   GtkTreeViewColumn *n_elements_col;
44 
45   GSList *pictures;
46   GSList *photosets;
47 };
48 
49 G_DEFINE_TYPE (FrogrAddToSetDialog, frogr_add_to_set_dialog, GTK_TYPE_DIALOG)
50 
51 
52 /* Properties */
53 enum  {
54   PROP_0,
55   PROP_PICTURES,
56   PROP_PHOTOSETS
57 };
58 
59 
60 /* Tree view columns */
61 enum {
62   CHECKBOX_COL,
63   TITLE_COL,
64   N_ELEMENTS_COL,
65   SET_COL,
66   N_COLS
67 };
68 
69 
70 /* Prototypes */
71 
72 static void _set_pictures (FrogrAddToSetDialog *self, const GSList *pictures);
73 
74 static void _set_photosets (FrogrAddToSetDialog *self, const GSList *photosets);
75 
76 static GtkWidget *_create_tree_view (FrogrAddToSetDialog *self);
77 
78 static void _column_clicked_cb (GtkTreeViewColumn *col, gpointer data);
79 
80 static void _toggle_column_sort_order (GtkTreeSortable *sortable,
81                                        GtkTreeViewColumn *col,
82                                        gint col_id);
83 
84 static gint _tree_iter_compare_n_elements_func (GtkTreeModel *model,
85                                                 GtkTreeIter *a,
86                                                 GtkTreeIter *b,
87                                                 gpointer data);
88 
89 static void _populate_treemodel_with_photosets (FrogrAddToSetDialog *self);
90 
91 static void _fill_dialog_with_data (FrogrAddToSetDialog *self);
92 
93 static void _set_toggled_cb (GtkCellRendererToggle *celltoggle,
94                              gchar *path_string,
95                              GtkTreeView *treeview);
96 
97 static GSList *_get_selected_photosets (FrogrAddToSetDialog *self);
98 
99 static void _update_pictures (FrogrAddToSetDialog *self);
100 
101 static void _dialog_response_cb (GtkDialog *dialog, gint response, gpointer data);
102 
103 /* Private API */
104 
105 static void
_set_pictures(FrogrAddToSetDialog * self,const GSList * pictures)106 _set_pictures (FrogrAddToSetDialog *self, const GSList *pictures)
107 {
108   self->pictures = g_slist_copy ((GSList*) pictures);
109   g_slist_foreach (self->pictures, (GFunc)g_object_ref, NULL);
110 }
111 
112 static void
_set_photosets(FrogrAddToSetDialog * self,const GSList * photosets)113 _set_photosets (FrogrAddToSetDialog *self, const GSList *photosets)
114 {
115   self->photosets = g_slist_copy ((GSList*)photosets);
116   g_slist_foreach (self->photosets, (GFunc)g_object_ref, NULL);
117 }
118 
119 static GtkWidget *
_create_tree_view(FrogrAddToSetDialog * self)120 _create_tree_view (FrogrAddToSetDialog *self)
121 {
122   GtkWidget *treeview = NULL;
123   GtkTreeViewColumn *col = NULL;
124   GtkCellRenderer *rend = NULL;
125 
126   treeview = gtk_tree_view_new();
127 
128   /* Checkbox */
129   rend = gtk_cell_renderer_toggle_new ();
130   col = gtk_tree_view_column_new_with_attributes (NULL,
131                                                   rend,
132                                                   "active", CHECKBOX_COL,
133                                                   NULL);
134   gtk_tree_view_column_set_clickable (col, TRUE);
135   gtk_tree_view_column_set_sort_order (col, GTK_SORT_ASCENDING);
136   gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), col);
137 
138   g_signal_connect (rend, "toggled",
139                     G_CALLBACK (_set_toggled_cb), treeview);
140 
141   g_signal_connect (col, "clicked",
142                     G_CALLBACK (_column_clicked_cb), self);
143 
144   self->checkbox_col = col;
145 
146   /* Title */
147   rend = gtk_cell_renderer_text_new ();
148   col = gtk_tree_view_column_new_with_attributes (_("Title"),
149                                                   rend,
150                                                   "text", TITLE_COL,
151                                                   NULL);
152   gtk_tree_view_column_set_clickable (col, TRUE);
153   gtk_tree_view_column_set_sort_order (col, GTK_SORT_DESCENDING);
154   gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), col);
155 
156   g_signal_connect (col, "clicked",
157                     G_CALLBACK (_column_clicked_cb), self);
158 
159   self->title_col = col;
160 
161   /* Description */
162   rend = gtk_cell_renderer_text_new ();
163   col = gtk_tree_view_column_new_with_attributes (_("Elements"),
164                                                   rend,
165                                                   "text", N_ELEMENTS_COL,
166                                                   NULL);
167   gtk_tree_view_column_set_clickable (col, TRUE);
168   gtk_tree_view_column_set_sort_order (col, GTK_SORT_DESCENDING);
169   gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), col);
170 
171   g_signal_connect (col, "clicked",
172                     G_CALLBACK (_column_clicked_cb), self);
173 
174   self->n_elements_col = col;
175 
176   return treeview;
177 }
178 
179 static void
_column_clicked_cb(GtkTreeViewColumn * col,gpointer data)180 _column_clicked_cb (GtkTreeViewColumn *col, gpointer data)
181 {
182   FrogrAddToSetDialog *self = NULL;
183   GtkTreeModel *model = NULL;
184 
185   self = FROGR_ADD_TO_SET_DIALOG (data);
186 
187   model = gtk_tree_view_get_model (GTK_TREE_VIEW (self->treeview));
188   if (!GTK_IS_TREE_SORTABLE (model))
189     return;
190 
191   if (col == self->checkbox_col)
192     _toggle_column_sort_order (GTK_TREE_SORTABLE (model), col, CHECKBOX_COL);
193   else if (col == self->title_col)
194     _toggle_column_sort_order (GTK_TREE_SORTABLE (model), col, TITLE_COL);
195   else if (col == self->n_elements_col)
196     _toggle_column_sort_order (GTK_TREE_SORTABLE (model), col, N_ELEMENTS_COL);
197   else
198     g_assert_not_reached ();
199 }
200 
201 static void
_toggle_column_sort_order(GtkTreeSortable * sortable,GtkTreeViewColumn * col,gint col_id)202 _toggle_column_sort_order (GtkTreeSortable *sortable,
203                            GtkTreeViewColumn *col,
204                            gint col_id)
205 {
206   GtkSortType current;
207   GtkSortType new;
208 
209   g_return_if_fail (GTK_IS_TREE_SORTABLE (sortable));
210   g_return_if_fail (GTK_IS_TREE_VIEW_COLUMN (col));
211   g_return_if_fail (col_id >= 0 && col_id < N_COLS);
212 
213   current = gtk_tree_view_column_get_sort_order (col);
214   if (current == GTK_SORT_ASCENDING)
215     new = GTK_SORT_DESCENDING;
216   else
217     new = GTK_SORT_ASCENDING;
218 
219   gtk_tree_view_column_set_sort_order (col, new);
220   gtk_tree_sortable_set_sort_column_id (sortable, col_id, new);
221 }
222 
223 static gint
_tree_iter_compare_n_elements_func(GtkTreeModel * model,GtkTreeIter * a,GtkTreeIter * b,gpointer data)224 _tree_iter_compare_n_elements_func (GtkTreeModel *model,
225                                     GtkTreeIter *a,
226                                     GtkTreeIter *b,
227                                     gpointer data)
228 {
229   g_autofree gchar *a_str = NULL;
230   g_autofree gchar *b_str = NULL;
231   gint a_value = 0;
232   gint b_value = 0;
233 
234   g_return_val_if_fail (GTK_IS_TREE_MODEL (model), 0);
235 
236   gtk_tree_model_get (GTK_TREE_MODEL (model), a, N_ELEMENTS_COL, &a_str, -1);
237   gtk_tree_model_get (GTK_TREE_MODEL (model), b, N_ELEMENTS_COL, &b_str, -1);
238 
239   a_value = g_ascii_strtoll (a_str, NULL, 10);
240   b_value = g_ascii_strtoll (b_str, NULL, 10);
241 
242   return (a_value - b_value);
243 }
244 
245 static void
_populate_treemodel_with_photosets(FrogrAddToSetDialog * self)246 _populate_treemodel_with_photosets (FrogrAddToSetDialog *self)
247 {
248   FrogrPhotoSet *set = NULL;
249   GtkTreeIter iter;
250   GSList *current = NULL;
251   gchar *n_elements_str = NULL;
252 
253   for (current = self->photosets; current; current = g_slist_next (current))
254     {
255       if (!FROGR_IS_PHOTOSET (current->data))
256         continue;
257 
258       set = FROGR_PHOTOSET (current->data);
259       n_elements_str = g_strdup_printf ("%d", frogr_photoset_get_n_photos (set));
260 
261       gtk_list_store_append (GTK_LIST_STORE (self->treemodel), &iter);
262       gtk_list_store_set (GTK_LIST_STORE (self->treemodel), &iter,
263                           CHECKBOX_COL, FALSE,
264                           TITLE_COL, frogr_photoset_get_title (set),
265                           N_ELEMENTS_COL, n_elements_str,
266                           SET_COL, set,
267                           -1);
268       g_free (n_elements_str);
269     }
270 }
271 
272 static void
_fill_dialog_with_data(FrogrAddToSetDialog * self)273 _fill_dialog_with_data (FrogrAddToSetDialog *self)
274 {
275   GtkTreeIter iter;
276   gint n_sets;
277   gint n_pictures;
278 
279   n_sets = g_slist_length (self->photosets);
280   n_pictures = g_slist_length (self->pictures);
281 
282   /* No sets, nothing to do */
283   if (n_sets == 0 || n_pictures == 0)
284     return;
285 
286   /* Iterate over all the items */
287   gtk_tree_model_get_iter_first (self->treemodel, &iter);
288   do
289     {
290       g_autoptr(FrogrPhotoSet) set = NULL;
291       GSList *p_item = NULL;
292       gboolean do_check = TRUE;
293 
294       gtk_tree_model_get (GTK_TREE_MODEL (self->treemodel), &iter,
295                           SET_COL, &set, -1);
296 
297       for (p_item = self->pictures; p_item; p_item = g_slist_next (p_item))
298         {
299           FrogrPicture *picture = NULL;
300 
301           picture = FROGR_PICTURE (p_item->data);
302           if (!frogr_picture_in_photoset (picture, set))
303             {
304               do_check = FALSE;
305               break;
306             }
307         }
308 
309       gtk_list_store_set (GTK_LIST_STORE (self->treemodel), &iter,
310                           CHECKBOX_COL, do_check, -1);
311     }
312   while (gtk_tree_model_iter_next (self->treemodel, &iter));
313 }
314 
315 static void
_set_toggled_cb(GtkCellRendererToggle * celltoggle,gchar * path_string,GtkTreeView * treeview)316 _set_toggled_cb (GtkCellRendererToggle *celltoggle,
317                  gchar *path_string,
318                  GtkTreeView *treeview)
319 {
320   GtkTreeModel *model = NULL;
321   GtkTreePath *path;
322   GtkTreeIter iter;
323   gboolean active = FALSE;
324 
325   g_return_if_fail (GTK_IS_TREE_VIEW (treeview));
326 
327   model = gtk_tree_view_get_model (treeview);
328   path = gtk_tree_path_new_from_string (path_string);
329 
330   gtk_tree_model_get_iter (model, &iter, path);
331   gtk_tree_path_free (path);
332 
333   gtk_tree_model_get (GTK_TREE_MODEL (model), &iter,
334                       CHECKBOX_COL, &active, -1);
335 
336   gtk_list_store_set (GTK_LIST_STORE (model), &iter,
337                       CHECKBOX_COL, !active, -1);
338 }
339 
340 static GSList *
_get_selected_photosets(FrogrAddToSetDialog * self)341 _get_selected_photosets (FrogrAddToSetDialog *self)
342 {
343   GtkTreeIter iter;
344   gboolean selected = FALSE;
345   FrogrPhotoSet *set = NULL;
346   GSList *selected_sets = NULL;
347 
348   /* No sets, nothing to do */
349   if (g_slist_length (self->photosets) == 0)
350     return NULL;
351 
352   /* Iterate over all the items */
353   gtk_tree_model_get_iter_first (self->treemodel, &iter);
354   do
355     {
356       gtk_tree_model_get (GTK_TREE_MODEL (self->treemodel), &iter,
357                           CHECKBOX_COL, &selected, -1);
358       if (!selected)
359         continue;
360 
361       gtk_tree_model_get (GTK_TREE_MODEL (self->treemodel), &iter,
362                           SET_COL, &set, -1);
363 
364       if (FROGR_IS_PHOTOSET (set))
365         selected_sets = g_slist_append (selected_sets, set);
366     }
367   while (gtk_tree_model_iter_next (self->treemodel, &iter));
368 
369   return selected_sets;
370 }
371 
372 static void
_update_pictures(FrogrAddToSetDialog * self)373 _update_pictures (FrogrAddToSetDialog *self)
374 {
375   FrogrPicture *picture = NULL;
376   GSList *selected_sets = NULL;
377   GSList *item = NULL;
378 
379   selected_sets = _get_selected_photosets (self);
380   if (selected_sets)
381     {
382       FrogrModel *model = NULL;
383 
384       for (item = self->pictures; item; item = g_slist_next (item))
385         {
386           picture = FROGR_PICTURE (item->data);
387 
388           /* frogr_picture_set_photosets expects transfer-full for photosets */
389           g_slist_foreach (selected_sets, (GFunc) g_object_ref, NULL);
390           frogr_picture_set_photosets (picture, g_slist_copy (selected_sets));
391         }
392 
393       model = frogr_controller_get_model (frogr_controller_get_instance ());
394       frogr_model_notify_changes_in_pictures (model);
395     }
396   g_slist_free (selected_sets);
397 }
398 
399 static void
_dialog_response_cb(GtkDialog * dialog,gint response,gpointer data)400 _dialog_response_cb (GtkDialog *dialog, gint response, gpointer data)
401 {
402   if (response == GTK_RESPONSE_ACCEPT)
403     {
404       FrogrAddToSetDialog *self = NULL;
405 
406       self = FROGR_ADD_TO_SET_DIALOG (dialog);
407       _update_pictures (self);
408     }
409 
410   /* Destroy the dialog */
411   gtk_widget_destroy (GTK_WIDGET (dialog));
412 }
413 
414 static void
_frogr_add_to_set_dialog_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)415 _frogr_add_to_set_dialog_set_property (GObject *object,
416                                        guint prop_id,
417                                        const GValue *value,
418                                        GParamSpec *pspec)
419 {
420   switch (prop_id)
421     {
422     case PROP_PICTURES:
423       _set_pictures (FROGR_ADD_TO_SET_DIALOG (object), g_value_get_pointer (value));
424       break;
425     case PROP_PHOTOSETS:
426       _set_photosets (FROGR_ADD_TO_SET_DIALOG (object), g_value_get_pointer (value));
427       break;
428     default:
429       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
430       break;
431     }
432 }
433 
434 static void
_frogr_add_to_set_dialog_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)435 _frogr_add_to_set_dialog_get_property (GObject *object,
436                                        guint prop_id,
437                                        GValue *value,
438                                        GParamSpec *pspec)
439 {
440   FrogrAddToSetDialog *dialog = FROGR_ADD_TO_SET_DIALOG (object);
441 
442   switch (prop_id)
443     {
444     case PROP_PICTURES:
445       g_value_set_pointer (value, dialog->pictures);
446       break;
447     case PROP_PHOTOSETS:
448       g_value_set_pointer (value, dialog->photosets);
449       break;
450     default:
451       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
452       break;
453     }
454 }
455 
456 static void
_frogr_add_to_set_dialog_dispose(GObject * object)457 _frogr_add_to_set_dialog_dispose (GObject *object)
458 {
459   FrogrAddToSetDialog *dialog = FROGR_ADD_TO_SET_DIALOG (object);
460 
461   if (dialog->pictures)
462     {
463       g_slist_free_full (dialog->pictures, g_object_unref);
464       dialog->pictures = NULL;
465     }
466 
467    if (dialog->photosets)
468     {
469       g_slist_free_full (dialog->photosets, g_object_unref);
470       dialog->photosets = NULL;
471     }
472 
473   if (dialog->treemodel)
474     {
475       gtk_list_store_clear (GTK_LIST_STORE (dialog->treemodel));
476       g_object_unref (dialog->treemodel);
477       dialog->treemodel = NULL;
478     }
479 
480   G_OBJECT_CLASS(frogr_add_to_set_dialog_parent_class)->dispose (object);
481 }
482 
483 static void
frogr_add_to_set_dialog_class_init(FrogrAddToSetDialogClass * klass)484 frogr_add_to_set_dialog_class_init (FrogrAddToSetDialogClass *klass)
485 {
486   GObjectClass *obj_class = (GObjectClass *)klass;
487   GParamSpec *pspec;
488 
489   /* GObject signals */
490   obj_class->set_property = _frogr_add_to_set_dialog_set_property;
491   obj_class->get_property = _frogr_add_to_set_dialog_get_property;
492   obj_class->dispose = _frogr_add_to_set_dialog_dispose;
493 
494   /* Install properties */
495   pspec = g_param_spec_pointer ("pictures",
496                                 "pictures",
497                                 "List of pictures for "
498                                 "the 'add to set' dialog",
499                                 G_PARAM_READWRITE
500                                 | G_PARAM_CONSTRUCT_ONLY);
501   g_object_class_install_property (obj_class, PROP_PICTURES, pspec);
502 
503   pspec = g_param_spec_pointer ("photosets",
504                                 "photosets",
505                                 "List of sets currently available "
506                                 "for the 'add to set' dialog",
507                                 G_PARAM_READWRITE
508                                 | G_PARAM_CONSTRUCT_ONLY);
509   g_object_class_install_property (obj_class, PROP_PHOTOSETS, pspec);
510 }
511 
512 static void
frogr_add_to_set_dialog_init(FrogrAddToSetDialog * self)513 frogr_add_to_set_dialog_init (FrogrAddToSetDialog *self)
514 {
515   GtkWidget *vbox = NULL;
516   GtkWidget *widget = NULL;
517 
518   self->pictures = NULL;
519   self->photosets = NULL;
520 
521   /* Create widgets */
522   gtk_dialog_add_buttons (GTK_DIALOG (self),
523                           _("_Cancel"),
524                           GTK_RESPONSE_CANCEL,
525                           _("_Add"),
526                           GTK_RESPONSE_ACCEPT,
527                           NULL);
528   gtk_container_set_border_width (GTK_CONTAINER (self), 6);
529 
530   vbox = gtk_dialog_get_content_area (GTK_DIALOG (self));
531 
532   widget = gtk_scrolled_window_new (NULL, NULL);
533   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (widget),
534                                   GTK_POLICY_AUTOMATIC,
535                                   GTK_POLICY_AUTOMATIC);
536   gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (widget),
537                                        GTK_SHADOW_ETCHED_IN);
538   gtk_box_pack_start (GTK_BOX (vbox), widget, TRUE, TRUE, 6);
539   gtk_widget_show (widget);
540 
541   self->treeview = _create_tree_view (self);
542   gtk_container_add (GTK_CONTAINER (widget), self->treeview);
543   gtk_widget_show (self->treeview);
544 
545   self->treemodel =
546     GTK_TREE_MODEL (gtk_list_store_new (4, G_TYPE_BOOLEAN,
547                                         G_TYPE_STRING, G_TYPE_STRING,
548                                         G_TYPE_OBJECT));
549   gtk_tree_view_set_model (GTK_TREE_VIEW (self->treeview), self->treemodel);
550 
551   /* Sorting function for the number of elements column */
552   gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (self->treemodel),
553                                    N_ELEMENTS_COL,
554                                    _tree_iter_compare_n_elements_func,
555                                    self, NULL);
556 
557   g_signal_connect (G_OBJECT (self), "response",
558                     G_CALLBACK (_dialog_response_cb), NULL);
559 
560   gtk_dialog_set_default_response (GTK_DIALOG (self), GTK_RESPONSE_ACCEPT);
561 
562   gtk_window_set_default_size (GTK_WINDOW (self),
563                                MINIMUM_WINDOW_WIDTH,
564                                MINIMUM_WINDOW_HEIGHT);
565 }
566 
567 /* Public API */
568 
569 void
frogr_add_to_set_dialog_show(GtkWindow * parent,const GSList * pictures,const GSList * photosets)570 frogr_add_to_set_dialog_show (GtkWindow *parent,
571                               const GSList *pictures,
572                               const GSList *photosets)
573 {
574   FrogrAddToSetDialog *self = NULL;
575   GObject *new = NULL;
576 
577   new = g_object_new (FROGR_TYPE_ADD_TO_SET_DIALOG,
578                       "title", _("Add to Sets"),
579                       "modal", TRUE,
580                       "pictures", pictures,
581                       "photosets", photosets,
582                       "transient-for", parent,
583                       "resizable", TRUE,
584 #if GTK_CHECK_VERSION (3, 12, 0)
585                       "use-header-bar", USE_HEADER_BAR,
586 #endif
587                       NULL);
588 
589   self = FROGR_ADD_TO_SET_DIALOG (new);
590 
591   _populate_treemodel_with_photosets (self);
592   _fill_dialog_with_data (self);
593 
594   gtk_widget_show (GTK_WIDGET (self));
595 }
596