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