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