1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2
3 /*
4 * GThumb
5 *
6 * Copyright (C) 2009 The Free Software Foundation, Inc.
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see <http://www.gnu.org/licenses/>.
20 */
21
22 #include <config.h>
23 #include <glib/gi18n.h>
24 #include "glib-utils.h"
25 #include "gth-main.h"
26 #include "gth-metadata-chooser.h"
27
28
29 #define ORDER_CHANGED_DELAY 250
30 #define CATEGORY_SIZE 1000
31
32
33 enum {
34 WEIGHT_COLUMN,
35 NAME_COLUMN,
36 ID_COLUMN,
37 SORT_ORDER_COLUMN,
38 USED_COLUMN,
39 SEPARATOR_COLUMN,
40 IS_METADATA_COLUMN,
41 N_COLUMNS
42 };
43
44
45 /* Properties */
46 enum {
47 PROP_0,
48 PROP_REORDERABLE
49 };
50
51 /* Signals */
52 enum {
53 CHANGED,
54 LAST_SIGNAL
55 };
56
57
58 struct _GthMetadataChooserPrivate {
59 GthMetadataFlags allowed_flags;
60 gboolean reorderable;
61 gulong row_inserted_event;
62 gulong row_deleted_event;
63 guint changed_id;
64 };
65
66
67 static guint gth_metadata_chooser_signals[LAST_SIGNAL] = { 0 };
68
69
G_DEFINE_TYPE_WITH_CODE(GthMetadataChooser,gth_metadata_chooser,GTK_TYPE_TREE_VIEW,G_ADD_PRIVATE (GthMetadataChooser))70 G_DEFINE_TYPE_WITH_CODE (GthMetadataChooser,
71 gth_metadata_chooser,
72 GTK_TYPE_TREE_VIEW,
73 G_ADD_PRIVATE (GthMetadataChooser))
74
75
76
77 static void
78 ggth_metadata_chooser_set_property (GObject *object,
79 guint property_id,
80 const GValue *value,
81 GParamSpec *pspec)
82 {
83 GthMetadataChooser *self;
84
85 self = GTH_METADATA_CHOOSER (object);
86
87 switch (property_id) {
88 case PROP_REORDERABLE:
89 self->priv->reorderable = g_value_get_boolean (value);
90 gtk_tree_view_set_reorderable (GTK_TREE_VIEW (self), self->priv->reorderable);
91 break;
92 default:
93 break;
94 }
95 }
96
97
98 static void
gth_metadata_chooser_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)99 gth_metadata_chooser_get_property (GObject *object,
100 guint property_id,
101 GValue *value,
102 GParamSpec *pspec)
103 {
104 GthMetadataChooser *self;
105
106 self = GTH_METADATA_CHOOSER (object);
107
108 switch (property_id) {
109 case PROP_REORDERABLE:
110 g_value_set_boolean (value, self->priv->reorderable);
111 break;
112 default:
113 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
114 break;
115 }
116 }
117
118
119 static void
gth_metadata_chooser_class_init(GthMetadataChooserClass * klass)120 gth_metadata_chooser_class_init (GthMetadataChooserClass *klass)
121 {
122 GObjectClass *object_class;
123
124 object_class = (GObjectClass*) klass;
125 object_class->set_property = ggth_metadata_chooser_set_property;
126 object_class->get_property = gth_metadata_chooser_get_property;
127
128 /* properties */
129
130 g_object_class_install_property (object_class,
131 PROP_REORDERABLE,
132 g_param_spec_boolean ("reorderable",
133 "Reorderable",
134 "Whether the user can reorder the list",
135 FALSE,
136 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
137
138 /* signals */
139
140 gth_metadata_chooser_signals[CHANGED] =
141 g_signal_new ("changed",
142 G_TYPE_FROM_CLASS (klass),
143 G_SIGNAL_RUN_LAST,
144 G_STRUCT_OFFSET (GthMetadataChooserClass, changed),
145 NULL, NULL,
146 g_cclosure_marshal_VOID__VOID,
147 G_TYPE_NONE,
148 0);
149 }
150
151
152 /* -- gth_metadata_chooser_reorder_list -- */
153
154
155 typedef struct {
156 int pos;
157 int sort_order;
158 char *name;
159 char *id;
160 gboolean used;
161 gboolean separator;
162 } ItemData;
163
164
165 static void
item_data_free(ItemData * item_data)166 item_data_free (ItemData *item_data)
167 {
168 g_free (item_data->name);
169 g_free (item_data->id);
170 g_free (item_data);
171 }
172
173
174 static int
item_data_compare_func(gconstpointer a,gconstpointer b,gpointer user_data)175 item_data_compare_func (gconstpointer a,
176 gconstpointer b,
177 gpointer user_data)
178 {
179 GthMetadataChooser *self = user_data;
180 ItemData *item_a = (ItemData *) a;
181 ItemData *item_b = (ItemData *) b;
182
183 if (! self->priv->reorderable) {
184 if (item_a->sort_order < item_b->sort_order)
185 return -1;
186 else if (item_a->sort_order > item_b->sort_order)
187 return 1;
188 else
189 return g_strcmp0 (item_a->id, item_b->id);
190 }
191
192 /* self->priv->reorderable == TRUE */
193
194 if (item_a->separator) {
195 if (item_b->used)
196 return 1;
197 else
198 return -1;
199 }
200
201 if (item_b->separator) {
202 if (item_a->used)
203 return -1;
204 else
205 return 1;
206 }
207
208 if (item_a->used == item_b->used) {
209 if (item_a->used) {
210 /* keep the user defined order for the used items */
211 if (item_a->pos < item_b->pos)
212 return -1;
213 else if (item_a->pos > item_b->pos)
214 return 1;
215 else
216 return 0;
217 }
218 else {
219 if (item_a->sort_order < item_b->sort_order)
220 return -1;
221 else if (item_a->sort_order > item_b->sort_order)
222 return 1;
223 else
224 return 0;
225 }
226 }
227 else if (item_a->used)
228 return -1;
229 else
230 return 1;
231 }
232
233
234 static gboolean
gth_metadata_chooser_reorder_list(GthMetadataChooser * self)235 gth_metadata_chooser_reorder_list (GthMetadataChooser *self)
236 {
237 gboolean changed = FALSE;
238 GtkTreeModel *model;
239 GtkTreeIter iter;
240 GList *list;
241 int pos;
242 int *new_order;
243 GList *scan;
244
245 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
246 if (! gtk_tree_model_get_iter_first (model, &iter))
247 return FALSE;
248
249 list = NULL;
250 pos = 0;
251 do {
252 ItemData *item_data;
253
254 item_data = g_new0 (ItemData, 1);
255 item_data->pos = pos;
256 gtk_tree_model_get (model, &iter,
257 NAME_COLUMN, &item_data->name,
258 ID_COLUMN, &item_data->id,
259 SORT_ORDER_COLUMN, &item_data->sort_order,
260 USED_COLUMN, &item_data->used,
261 SEPARATOR_COLUMN, &item_data->separator,
262 -1);
263 list = g_list_prepend (list, item_data);
264 pos++;
265 }
266 while (gtk_tree_model_iter_next (model, &iter));
267
268 list = g_list_sort_with_data (list, item_data_compare_func, self);
269 new_order = g_new (int, g_list_length (list));
270 for (pos = 0, scan = list; scan; pos++, scan = scan->next) {
271 ItemData *item_data = scan->data;
272
273 if (pos != item_data->pos)
274 changed = TRUE;
275 new_order[pos] = item_data->pos;
276 }
277 gtk_list_store_reorder (GTK_LIST_STORE (model), new_order);
278
279 g_free (new_order);
280 g_list_foreach (list, (GFunc) item_data_free, NULL);
281 g_list_free (list);
282
283 return changed;
284 }
285
286
287 static void
cell_renderer_toggle_toggled_cb(GtkCellRendererToggle * cell_renderer,char * path,gpointer user_data)288 cell_renderer_toggle_toggled_cb (GtkCellRendererToggle *cell_renderer,
289 char *path,
290 gpointer user_data)
291 {
292 GthMetadataChooser *self = user_data;
293 GtkTreePath *tree_path;
294 GtkTreeModel *tree_model;
295 GtkTreeIter iter;
296
297 tree_path = gtk_tree_path_new_from_string (path);
298 if (tree_path == NULL)
299 return;
300
301 tree_model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
302 if (gtk_tree_model_get_iter (tree_model, &iter, tree_path)) {
303 gboolean used;
304
305 gtk_tree_model_get (tree_model, &iter,
306 USED_COLUMN, &used,
307 -1);
308 gtk_list_store_set (GTK_LIST_STORE (tree_model), &iter,
309 USED_COLUMN, ! used,
310 -1);
311 gth_metadata_chooser_reorder_list (self);
312 g_signal_emit (self, gth_metadata_chooser_signals[CHANGED], 0);
313 }
314
315 gtk_tree_path_free (tree_path);
316 }
317
318
319 static gboolean
row_separator_func(GtkTreeModel * model,GtkTreeIter * iter,gpointer data)320 row_separator_func (GtkTreeModel *model,
321 GtkTreeIter *iter,
322 gpointer data)
323 {
324 gboolean separator;
325
326 gtk_tree_model_get (model, iter, SEPARATOR_COLUMN, &separator, -1);
327
328 return separator;
329 }
330
331
332
333 static gboolean
order_changed(gpointer user_data)334 order_changed (gpointer user_data)
335 {
336 GthMetadataChooser *self = user_data;
337
338 if (self->priv->changed_id != 0)
339 g_source_remove (self->priv->changed_id);
340 self->priv->changed_id = 0;
341
342 gth_metadata_chooser_reorder_list (self);
343 g_signal_emit (self, gth_metadata_chooser_signals[CHANGED], 0);
344
345 return FALSE;
346 }
347
348
349 static void
row_deleted_cb(GtkTreeModel * tree_model,GtkTreePath * path,gpointer user_data)350 row_deleted_cb (GtkTreeModel *tree_model,
351 GtkTreePath *path,
352 gpointer user_data)
353 {
354 GthMetadataChooser *self = user_data;
355
356 if (self->priv->changed_id != 0)
357 g_source_remove (self->priv->changed_id);
358 self->priv->changed_id = gdk_threads_add_timeout (ORDER_CHANGED_DELAY, order_changed, self);
359 }
360
361
362 static void
row_inserted_cb(GtkTreeModel * tree_model,GtkTreePath * path,GtkTreeIter * iter,gpointer user_data)363 row_inserted_cb (GtkTreeModel *tree_model,
364 GtkTreePath *path,
365 GtkTreeIter *iter,
366 gpointer user_data)
367 {
368 GthMetadataChooser *self = user_data;
369
370 if (self->priv->changed_id != 0)
371 g_source_remove (self->priv->changed_id);
372 self->priv->changed_id = gdk_threads_add_timeout (ORDER_CHANGED_DELAY, order_changed, self);
373 }
374
375
376 static void
gth_metadata_chooser_init(GthMetadataChooser * self)377 gth_metadata_chooser_init (GthMetadataChooser *self)
378 {
379 GtkListStore *store;
380 GtkTreeViewColumn *column;
381 GtkCellRenderer *renderer;
382
383 self->priv = gth_metadata_chooser_get_instance_private (self);
384
385 /* the list view */
386
387 store = gtk_list_store_new (N_COLUMNS,
388 PANGO_TYPE_WEIGHT,
389 G_TYPE_STRING,
390 G_TYPE_STRING,
391 G_TYPE_INT,
392 G_TYPE_BOOLEAN,
393 G_TYPE_BOOLEAN,
394 G_TYPE_BOOLEAN);
395 gtk_tree_view_set_model (GTK_TREE_VIEW (self), GTK_TREE_MODEL (store));
396 g_object_unref (store);
397 gtk_tree_view_set_reorderable (GTK_TREE_VIEW (self), self->priv->reorderable);
398 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (self), FALSE);
399 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (self),
400 row_separator_func,
401 self,
402 NULL);
403
404 self->priv->row_inserted_event = g_signal_connect (store,
405 "row-inserted",
406 G_CALLBACK (row_inserted_cb),
407 self);
408 self->priv->row_deleted_event = g_signal_connect (store,
409 "row-deleted",
410 G_CALLBACK (row_deleted_cb),
411 self);
412
413 /* the checkbox column */
414
415 column = gtk_tree_view_column_new ();
416 renderer = gtk_cell_renderer_toggle_new ();
417 g_signal_connect (renderer,
418 "toggled",
419 G_CALLBACK (cell_renderer_toggle_toggled_cb),
420 self);
421 gtk_tree_view_column_pack_start (column, renderer, FALSE);
422 gtk_tree_view_column_set_attributes (column, renderer,
423 "active", USED_COLUMN,
424 "visible", IS_METADATA_COLUMN,
425 NULL);
426 gtk_tree_view_append_column (GTK_TREE_VIEW (self), column);
427
428 /* the name column. */
429
430 column = gtk_tree_view_column_new ();
431 renderer = gtk_cell_renderer_text_new ();
432 gtk_tree_view_column_pack_start (column, renderer, TRUE);
433 gtk_tree_view_column_set_attributes (column, renderer,
434 "text", NAME_COLUMN,
435 "weight", WEIGHT_COLUMN,
436 NULL);
437 gtk_tree_view_column_set_expand (column, TRUE);
438 gtk_tree_view_append_column (GTK_TREE_VIEW (self), column);
439 }
440
441
442 GtkWidget *
gth_metadata_chooser_new(GthMetadataFlags allowed_flags,gboolean reorderable)443 gth_metadata_chooser_new (GthMetadataFlags allowed_flags,
444 gboolean reorderable)
445 {
446 GthMetadataChooser *self;
447
448 self = g_object_new (GTH_TYPE_METADATA_CHOOSER, "reorderable", reorderable, NULL);
449 self->priv->allowed_flags = allowed_flags;
450 gth_metadata_chooser_set_selection (self, "");
451
452 return (GtkWidget *) self;
453 }
454
455
456 void
gth_metadata_chooser_set_selection(GthMetadataChooser * self,char * ids)457 gth_metadata_chooser_set_selection (GthMetadataChooser *self,
458 char *ids)
459 {
460 GtkListStore *store;
461 char **attributes_v;
462 char **ids_v;
463 int i;
464 GtkTreeIter iter;
465 GHashTable *category_root;
466
467 store = (GtkListStore *) gtk_tree_view_get_model (GTK_TREE_VIEW (self));
468
469 g_signal_handler_block (store, self->priv->row_inserted_event);
470 g_signal_handler_block (store, self->priv->row_deleted_event);
471
472 gtk_list_store_clear (store);
473
474 attributes_v = gth_main_get_metadata_attributes ("*");
475 ids_v = g_strsplit (ids, ",", -1);
476
477 if (self->priv->reorderable) {
478 for (i = 0; ids_v[i] != NULL; i++) {
479 int idx;
480 GthMetadataInfo *info;
481 const char *name;
482 GthMetadataCategory *category;
483
484 idx = _g_strv_find (attributes_v, ids_v[i]);
485 if (idx < 0)
486 continue;
487
488 info = gth_main_get_metadata_info (attributes_v[idx]);
489 if ((info == NULL) || ((info->flags & self->priv->allowed_flags) == 0))
490 continue;
491
492 if (info->display_name != NULL)
493 name = _(info->display_name);
494 else
495 name = info->id;
496
497 category = gth_main_get_metadata_category (info->category);
498
499 gtk_list_store_append (store, &iter);
500 gtk_list_store_set (store, &iter,
501 WEIGHT_COLUMN, PANGO_WEIGHT_NORMAL,
502 NAME_COLUMN, name,
503 ID_COLUMN, info->id,
504 SORT_ORDER_COLUMN, (category->sort_order * CATEGORY_SIZE) + info->sort_order,
505 USED_COLUMN, TRUE,
506 SEPARATOR_COLUMN, FALSE,
507 IS_METADATA_COLUMN, TRUE,
508 -1);
509 }
510
511 gtk_list_store_append (store, &iter);
512 gtk_list_store_set (store, &iter,
513 SEPARATOR_COLUMN, TRUE,
514 -1);
515 }
516
517 category_root = g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify) g_free, NULL);
518 for (i = 0; attributes_v[i] != NULL; i++) {
519 gboolean used;
520 GtkTreeIter iter;
521 GthMetadataInfo *info;
522 const char *name;
523 GthMetadataCategory *category;
524
525 used = _g_strv_find (ids_v, attributes_v[i]) >= 0;
526 if (self->priv->reorderable && used)
527 continue;
528
529 info = gth_main_get_metadata_info (attributes_v[i]);
530 if ((info == NULL) || ((info->flags & self->priv->allowed_flags) == 0))
531 continue;
532
533 name = info->display_name;
534 if (name == NULL)
535 name = info->id;
536
537 category = gth_main_get_metadata_category (info->category);
538
539 if (g_hash_table_lookup (category_root, category->id) == NULL) {
540 gtk_list_store_append (store, &iter);
541 gtk_list_store_set (store, &iter,
542 WEIGHT_COLUMN, PANGO_WEIGHT_BOLD,
543 NAME_COLUMN, _(category->display_name),
544 ID_COLUMN, category->id,
545 SORT_ORDER_COLUMN, category->sort_order * CATEGORY_SIZE,
546 USED_COLUMN, FALSE,
547 SEPARATOR_COLUMN, FALSE,
548 IS_METADATA_COLUMN, FALSE,
549 -1);
550
551 g_hash_table_insert (category_root, g_strdup (info->category), GINT_TO_POINTER (1));
552 }
553
554 gtk_list_store_append (store, &iter);
555 gtk_list_store_set (store, &iter,
556 WEIGHT_COLUMN, PANGO_WEIGHT_NORMAL,
557 NAME_COLUMN, _(name),
558 ID_COLUMN, info->id,
559 SORT_ORDER_COLUMN, (category->sort_order * CATEGORY_SIZE) + info->sort_order,
560 USED_COLUMN, used,
561 SEPARATOR_COLUMN, FALSE,
562 IS_METADATA_COLUMN, TRUE,
563 -1);
564 }
565 gth_metadata_chooser_reorder_list (self);
566
567 g_signal_handler_unblock (store, self->priv->row_inserted_event);
568 g_signal_handler_unblock (store, self->priv->row_deleted_event);
569
570 g_hash_table_destroy (category_root);
571 g_strfreev (attributes_v);
572 g_strfreev (ids_v);
573 }
574
575
576 char *
gth_metadata_chooser_get_selection(GthMetadataChooser * self)577 gth_metadata_chooser_get_selection (GthMetadataChooser *self)
578 {
579 GString *selection;
580 GtkTreeModel *model;
581 GtkTreeIter iter;
582
583 selection = g_string_new ("");
584 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
585 if (gtk_tree_model_get_iter_first (model, &iter)) {
586 do {
587 gboolean used;
588 char *id;
589
590 gtk_tree_model_get (model, &iter,
591 ID_COLUMN, &id,
592 USED_COLUMN, &used,
593 -1);
594
595 if (used) {
596 if (selection->len > 0)
597 g_string_append (selection, ",");
598 g_string_append (selection, id);
599 }
600
601 g_free (id);
602 }
603 while (gtk_tree_model_iter_next (model, &iter));
604 }
605
606 if (selection->len == 0)
607 g_string_append (selection, "none");
608
609 return g_string_free (selection, FALSE);
610 }
611