1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 
3 /*
4  *  GThumb
5  *
6  *  Copyright (C) 2006-2008 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 <string.h>
24 #include <stdlib.h>
25 #include <time.h>
26 #include <glib/gi18n.h>
27 #include <gtk/gtk.h>
28 #include "dom.h"
29 #include "glib-utils.h"
30 #include "gth-filterbar.h"
31 #include "gth-main.h"
32 #include "gth-user-dir.h"
33 
34 enum {
35 	ITEM_TYPE_NONE,
36 	ITEM_TYPE_SEPARATOR,
37 	ITEM_TYPE_FILTER,
38 	ITEM_TYPE_PERSONALIZE
39 };
40 
41 enum {
42 	ICON_COLUMN,
43 	NAME_COLUMN,
44 	TYPE_COLUMN,
45 	FILTER_COLUMN,
46 	N_COLUMNS
47 };
48 
49 enum {
50 	CHANGED,
51 	PERSONALIZE,
52 	CLOSE_BUTTON_CLICKED,
53 	LAST_SIGNAL
54 };
55 
56 struct _GthFilterbarPrivate {
57 	GtkListStore *model;
58 	GtkWidget    *test_combo_box;
59 	GthTest      *test;
60 	GtkWidget    *control_box;
61 	GtkWidget    *control;
62 	GtkWidget    *extra_area;
63 	GtkTreeIter   current_iter;
64 	gulong        filters_changed_id;
65 	gulong        test_changed_id;
66 };
67 
68 
69 static guint gth_filterbar_signals[LAST_SIGNAL] = { 0 };
70 
71 
G_DEFINE_TYPE_WITH_CODE(GthFilterbar,gth_filterbar,GTK_TYPE_BOX,G_ADD_PRIVATE (GthFilterbar))72 G_DEFINE_TYPE_WITH_CODE (GthFilterbar,
73 			 gth_filterbar,
74 			 GTK_TYPE_BOX,
75 			 G_ADD_PRIVATE (GthFilterbar))
76 
77 
78 static void
79 gth_filterbar_finalize (GObject *object)
80 {
81 	GthFilterbar *filterbar;
82 
83 	filterbar = GTH_FILTERBAR (object);
84 
85 	g_signal_handler_disconnect (gth_main_get_default_monitor (), filterbar->priv->filters_changed_id);
86 	if (filterbar->priv->test != NULL) {
87 		g_signal_handler_disconnect (filterbar->priv->test, filterbar->priv->test_changed_id);
88 		g_object_unref (filterbar->priv->test);
89 	}
90 
91 	G_OBJECT_CLASS (gth_filterbar_parent_class)->finalize (object);
92 }
93 
94 
95 static void
gth_filterbar_class_init(GthFilterbarClass * class)96 gth_filterbar_class_init (GthFilterbarClass *class)
97 {
98 	GObjectClass   *object_class;
99 
100 	object_class = (GObjectClass*) class;
101 	object_class->finalize = gth_filterbar_finalize;
102 
103 	gth_filterbar_signals[CHANGED] =
104 		g_signal_new ("changed",
105 			      G_TYPE_FROM_CLASS (class),
106 			      G_SIGNAL_RUN_LAST,
107 			      G_STRUCT_OFFSET (GthFilterbarClass, changed),
108 			      NULL, NULL,
109 			      g_cclosure_marshal_VOID__VOID,
110 			      G_TYPE_NONE,
111 			      0);
112 	gth_filterbar_signals[PERSONALIZE] =
113 		g_signal_new ("personalize",
114 			      G_TYPE_FROM_CLASS (class),
115 			      G_SIGNAL_RUN_LAST,
116 			      G_STRUCT_OFFSET (GthFilterbarClass, personalize),
117 			      NULL, NULL,
118 			      g_cclosure_marshal_VOID__VOID,
119 			      G_TYPE_NONE,
120 			      0);
121 	 gth_filterbar_signals[CLOSE_BUTTON_CLICKED] =
122 		g_signal_new ("close_button_clicked",
123 			      G_TYPE_FROM_CLASS (class),
124 			      G_SIGNAL_RUN_LAST,
125 			      G_STRUCT_OFFSET (GthFilterbarClass, close_button_clicked),
126 			      NULL, NULL,
127 			      g_cclosure_marshal_VOID__VOID,
128 			      G_TYPE_NONE,
129 			      0);
130 }
131 
132 
133 static void
gth_filterbar_init(GthFilterbar * filterbar)134 gth_filterbar_init (GthFilterbar *filterbar)
135 {
136 	filterbar->priv = gth_filterbar_get_instance_private (filterbar);
137 	filterbar->priv->model = NULL;
138 	filterbar->priv->test_combo_box = NULL;
139 	filterbar->priv->test = NULL;
140 	filterbar->priv->control_box = NULL;
141 	filterbar->priv->control = NULL;
142 	filterbar->priv->extra_area = NULL;
143 	filterbar->priv->filters_changed_id = 0;
144 	filterbar->priv->test_changed_id = 0;
145 
146 	gtk_orientable_set_orientation (GTK_ORIENTABLE (filterbar), GTK_ORIENTATION_HORIZONTAL);
147 }
148 
149 
150 static void
gth_filterbar_changed(GthFilterbar * filterbar)151 gth_filterbar_changed (GthFilterbar *filterbar)
152 {
153 	g_signal_emit (filterbar, gth_filterbar_signals[CHANGED], 0);
154 }
155 
156 
157 static void
_gth_filterbar_set_test_control(GthFilterbar * filterbar,GtkWidget * control)158 _gth_filterbar_set_test_control (GthFilterbar *filterbar,
159 				  GtkWidget    *control)
160 {
161 	if (filterbar->priv->control != NULL) {
162 		gtk_container_remove (GTK_CONTAINER (filterbar->priv->control_box),
163 				      filterbar->priv->control);
164 		filterbar->priv->control = NULL;
165 	}
166 
167 	gth_filterbar_changed (filterbar);
168 
169 	if (control == NULL)
170 		return;
171 
172 	filterbar->priv->control = control;
173 	gtk_widget_show (control);
174 	gtk_container_add (GTK_CONTAINER (filterbar->priv->control_box),
175 			   filterbar->priv->control);
176 }
177 
178 
179 static void
test_changed_cb(GthTest * test,GthFilterbar * filterbar)180 test_changed_cb (GthTest      *test,
181 		 GthFilterbar *filterbar)
182 {
183 	gth_filterbar_changed (filterbar);
184 }
185 
186 
187 static void
_gth_filterbar_set_test(GthFilterbar * filterbar,GthTest * test)188 _gth_filterbar_set_test (GthFilterbar *filterbar,
189 			  GthTest      *test)
190 {
191 	GthTest *old_test;
192 
193 	old_test = filterbar->priv->test;
194 	if (old_test != NULL) {
195 		if (filterbar->priv->test_changed_id != 0)
196 			g_signal_handler_disconnect (old_test, filterbar->priv->test_changed_id);
197 		filterbar->priv->test = NULL;
198 	}
199 
200 	if (test != NULL) {
201 		filterbar->priv->test = g_object_ref (test);
202 		filterbar->priv->test_changed_id = g_signal_connect (test,
203 								     "changed",
204 								     G_CALLBACK (test_changed_cb),
205 								     filterbar);
206 		_gth_filterbar_set_test_control (filterbar, gth_test_create_control (filterbar->priv->test));
207 		gth_test_focus_control (filterbar->priv->test);
208 	}
209 	else
210 		_gth_filterbar_set_test_control (filterbar, NULL);
211 
212 	_g_object_unref (old_test);
213 }
214 
215 
216 static void
test_combo_box_changed_cb(GtkComboBox * scope_combo_box,GthFilterbar * filterbar)217 test_combo_box_changed_cb (GtkComboBox  *scope_combo_box,
218 			   GthFilterbar *filterbar)
219 {
220 	GtkTreeIter  iter;
221 	int          item_type = ITEM_TYPE_NONE;
222 	GthTest     *test;
223 
224 	if (! gtk_combo_box_get_active_iter (scope_combo_box, &iter))
225 		return;
226 
227 	gtk_tree_model_get (GTK_TREE_MODEL (filterbar->priv->model),
228 			    &iter,
229 			    TYPE_COLUMN, &item_type,
230 			    FILTER_COLUMN, &test,
231 			    -1);
232 
233 	switch (item_type) {
234 	case ITEM_TYPE_FILTER:
235 		_gth_filterbar_set_test (filterbar, test);
236 		filterbar->priv->current_iter = iter;
237 		break;
238 	case ITEM_TYPE_PERSONALIZE:
239 		g_signal_emit (filterbar, gth_filterbar_signals[PERSONALIZE], 0);
240 		g_signal_handlers_block_by_func (filterbar->priv->test_combo_box, test_combo_box_changed_cb, filterbar);
241 		gtk_combo_box_set_active_iter (GTK_COMBO_BOX (filterbar->priv->test_combo_box), &filterbar->priv->current_iter);
242 		g_signal_handlers_unblock_by_func (filterbar->priv->test_combo_box, test_combo_box_changed_cb, filterbar);
243 		break;
244 	default:
245 		break;
246 	}
247 
248 	if (test != NULL)
249 		g_object_unref (test);
250 }
251 
252 
253 static gboolean
test_combo_box_row_separator_func(GtkTreeModel * model,GtkTreeIter * iter,gpointer data)254 test_combo_box_row_separator_func (GtkTreeModel *model,
255 				   GtkTreeIter  *iter,
256 				   gpointer      data)
257 {
258 	int item_type = ITEM_TYPE_NONE;
259 
260 	gtk_tree_model_get (model, iter, TYPE_COLUMN, &item_type, -1);
261 
262 	return (item_type == ITEM_TYPE_SEPARATOR);
263 }
264 
265 
266 static void
update_filter_list(GthFilterbar * filterbar,const char * current_filter)267 update_filter_list (GthFilterbar *filterbar,
268 		    const char   *current_filter)
269 {
270 	gboolean     no_filter_selected = TRUE;
271 	GList       *filters, *scan;
272 	GtkTreeIter  iter;
273 
274 	gtk_list_store_clear (filterbar->priv->model);
275 
276 	gtk_list_store_append (filterbar->priv->model, &iter);
277 	gtk_list_store_set (filterbar->priv->model, &iter,
278 			    TYPE_COLUMN, ITEM_TYPE_FILTER,
279 			    FILTER_COLUMN, NULL,
280 			    NAME_COLUMN, _("All"),
281 			    -1);
282 
283 	filters = gth_main_get_all_filters ();
284 	for (scan = filters; scan; scan = scan->next) {
285 		GthTest *test = scan->data;
286 
287 		if (! gth_test_is_visible (test))
288 			continue;
289 
290 		gtk_list_store_append (filterbar->priv->model, &iter);
291 		gtk_list_store_set (filterbar->priv->model, &iter,
292 				    TYPE_COLUMN, ITEM_TYPE_FILTER,
293 				    FILTER_COLUMN, test,
294 				    NAME_COLUMN, gth_test_get_display_name (test),
295 				    -1);
296 
297 		if (g_strcmp0 (current_filter, gth_test_get_id (test)) == 0) {
298 			gtk_combo_box_set_active_iter (GTK_COMBO_BOX (filterbar->priv->test_combo_box), &iter);
299 			filterbar->priv->current_iter = iter;
300 			_gth_filterbar_set_test (GTH_FILTERBAR (filterbar), test);
301 			no_filter_selected = FALSE;
302 		}
303 	}
304 	_g_object_list_unref (filters);
305 
306 	gtk_list_store_append (filterbar->priv->model, &iter);
307 	gtk_list_store_set (filterbar->priv->model, &iter,
308 			    TYPE_COLUMN, ITEM_TYPE_SEPARATOR,
309 			    -1);
310 
311 	gtk_list_store_append (filterbar->priv->model, &iter);
312 	gtk_list_store_set (filterbar->priv->model, &iter,
313 			    TYPE_COLUMN, ITEM_TYPE_PERSONALIZE,
314 			    NAME_COLUMN, _("Personalize…"),
315 			    -1);
316 
317 	if (no_filter_selected) {
318 		GtkTreeIter iter;
319 
320 		gtk_tree_model_get_iter_first (GTK_TREE_MODEL (filterbar->priv->model), &iter);
321 		gtk_combo_box_set_active_iter (GTK_COMBO_BOX (filterbar->priv->test_combo_box), &iter);
322 		filterbar->priv->current_iter = iter;
323 	}
324 }
325 
326 
327 static void
filters_changed_cb(GthMonitor * monitor,GthFilterbar * filterbar)328 filters_changed_cb (GthMonitor   *monitor,
329 		    GthFilterbar *filterbar)
330 {
331 	GthTest *current_filter;
332 
333 	gtk_tree_model_get (GTK_TREE_MODEL (filterbar->priv->model),
334 			    &filterbar->priv->current_iter,
335 			    FILTER_COLUMN, &current_filter,
336 			    -1);
337 
338 	update_filter_list (filterbar, current_filter != NULL ? gth_test_get_id (current_filter) : NULL);
339 
340 	if (current_filter != NULL)
341 		g_object_unref (current_filter);
342 }
343 
344 
345 static void
gth_filterbar_construct(GthFilterbar * filterbar,const char * selected_filter)346 gth_filterbar_construct (GthFilterbar *filterbar,
347 			  const char   *selected_filter)
348 {
349 	GtkCellRenderer *renderer;
350 	GtkWidget       *label;
351 
352 	gtk_box_set_spacing (GTK_BOX (filterbar), 6);
353 	gtk_container_set_border_width (GTK_CONTAINER (filterbar), 4);
354 
355 	/* filter combo box */
356 
357 	filterbar->priv->model = gtk_list_store_new (N_COLUMNS,
358 							     GDK_TYPE_PIXBUF,
359 							     G_TYPE_STRING,
360 						      G_TYPE_INT,
361 						      G_TYPE_OBJECT);
362 	filterbar->priv->test_combo_box = gtk_combo_box_new_with_model (GTK_TREE_MODEL (filterbar->priv->model));
363 	g_object_unref (filterbar->priv->model);
364 	gtk_combo_box_set_row_separator_func (GTK_COMBO_BOX (filterbar->priv->test_combo_box),
365 					      test_combo_box_row_separator_func,
366 					      filterbar,
367 					      NULL);
368 
369 	/* icon renderer */
370 
371 	renderer = gtk_cell_renderer_pixbuf_new ();
372 	gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (filterbar->priv->test_combo_box),
373 				    renderer,
374 				    FALSE);
375 	gtk_cell_layout_set_attributes  (GTK_CELL_LAYOUT (filterbar->priv->test_combo_box),
376 					 renderer,
377 					 "pixbuf", ICON_COLUMN,
378 					 NULL);
379 
380 	/* name renderer */
381 
382 	renderer = gtk_cell_renderer_text_new ();
383 	gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (filterbar->priv->test_combo_box),
384 				    renderer,
385 				    TRUE);
386 	gtk_cell_layout_set_attributes  (GTK_CELL_LAYOUT (filterbar->priv->test_combo_box),
387 					 renderer,
388 					 "text", NAME_COLUMN,
389 					 NULL);
390 
391 	/**/
392 
393 	update_filter_list (filterbar, selected_filter);
394 
395 	g_signal_connect (G_OBJECT (filterbar->priv->test_combo_box),
396 			  "changed",
397 			  G_CALLBACK (test_combo_box_changed_cb),
398 			  filterbar);
399 
400 	gtk_widget_show (filterbar->priv->test_combo_box);
401 
402 	/* test control box */
403 
404 	filterbar->priv->control_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
405 	gtk_widget_show (filterbar->priv->control_box);
406 
407 	/* extra widgets container */
408 
409 	filterbar->priv->extra_area = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
410 	gtk_widget_show (filterbar->priv->extra_area);
411 
412 	/* view label */
413 
414 	label = gtk_label_new_with_mnemonic (_("S_how:"));
415 	gtk_label_set_mnemonic_widget (GTK_LABEL (label), filterbar->priv->test_combo_box);
416 	gtk_widget_show (label);
417 
418 	/**/
419 
420 	filterbar->priv->filters_changed_id =
421 		g_signal_connect (gth_main_get_default_monitor (),
422 				  "filters-changed",
423 				  G_CALLBACK (filters_changed_cb),
424 				  filterbar);
425 
426 	gtk_box_pack_start (GTK_BOX (filterbar), label, FALSE, FALSE, 0);
427 	gtk_box_pack_start (GTK_BOX (filterbar), filterbar->priv->test_combo_box, FALSE, FALSE, 0);
428 	gtk_box_pack_start (GTK_BOX (filterbar), filterbar->priv->control_box, FALSE, FALSE, 0);
429 	gtk_box_pack_end (GTK_BOX (filterbar), filterbar->priv->extra_area, FALSE, FALSE, 0);
430 }
431 
432 
433 GtkWidget*
gth_filterbar_new(const char * selected_filter)434 gth_filterbar_new (const char *selected_filter)
435 {
436 	GtkWidget *widget;
437 
438 	widget = GTK_WIDGET (g_object_new (GTH_TYPE_FILTERBAR, NULL));
439 	gth_filterbar_construct (GTH_FILTERBAR (widget), selected_filter);
440 
441 	return widget;
442 }
443 
444 
445 GthTest *
gth_filterbar_get_test(GthFilterbar * filterbar)446 gth_filterbar_get_test (GthFilterbar *filterbar)
447 {
448 	if (filterbar->priv->test != NULL)
449 		return g_object_ref (filterbar->priv->test);
450 	else
451 		return NULL;
452 }
453 
454 
455 void
gth_filterbar_save_filter(GthFilterbar * filterbar,const char * filename)456 gth_filterbar_save_filter (GthFilterbar *filterbar,
457 			   const char   *filename)
458 {
459 	char  *filter_description;
460 	gsize  len;
461 	GFile *filter_file;
462 
463 	if (filterbar->priv->test != NULL) {
464 		DomDocument *doc;
465 
466 		doc = dom_document_new ();
467 		dom_element_append_child (DOM_ELEMENT (doc), dom_domizable_create_element (DOM_DOMIZABLE (filterbar->priv->test), doc));
468 		filter_description = dom_document_dump (doc, &len);
469 
470 		g_object_unref (doc);
471 	}
472 	else {
473 		filter_description = g_strdup ("");
474 		len = 0;
475 	}
476 	filter_file = gth_user_dir_get_file_for_write (GTH_DIR_CONFIG, GTHUMB_DIR, filename, NULL);
477 	_g_file_write (filter_file, FALSE, 0, filter_description, len, NULL, NULL);
478 
479 	g_object_unref (filter_file);
480 	g_free (filter_description);
481 }
482 
483 
484 static gboolean
find_test_by_id(GthFilterbar * filterbar,const char * id,GthTest ** test,GtkTreeIter * iter)485 find_test_by_id (GthFilterbar  *filterbar,
486 		 const char    *id,
487 		 GthTest      **test,
488 		 GtkTreeIter   *iter)
489 {
490 	g_return_val_if_fail (test != NULL, FALSE);
491 	g_return_val_if_fail (iter != NULL, FALSE);
492 
493 	if (! gtk_tree_model_get_iter_first(GTK_TREE_MODEL (filterbar->priv->model), iter))
494 		return FALSE;
495 
496 	do {
497 		int item_type = ITEM_TYPE_NONE;
498 
499 		gtk_tree_model_get (GTK_TREE_MODEL (filterbar->priv->model),
500 				    iter,
501 				    TYPE_COLUMN, &item_type,
502 				    FILTER_COLUMN, test,
503 				    -1);
504 
505 		if ((item_type == ITEM_TYPE_FILTER) && (*test != NULL) && (g_strcmp0 (gth_test_get_id (*test), id) == 0))
506 			return TRUE;
507 	}
508 	while (gtk_tree_model_iter_next (GTK_TREE_MODEL (filterbar->priv->model), iter));
509 
510 	return FALSE;
511 }
512 
513 
514 void
gth_filterbar_load_filter(GthFilterbar * filterbar,const char * filename)515 gth_filterbar_load_filter (GthFilterbar *filterbar,
516 			   const char   *filename)
517 {
518 	GFile       *filter_file;
519 	char        *buffer;
520 	gsize        len;
521 	DomDocument *doc;
522 
523 	filter_file = gth_user_dir_get_file_for_write (GTH_DIR_CONFIG, GTHUMB_DIR, filename, NULL);
524 	if (! _g_file_load_in_buffer (filter_file, (void **) &buffer, &len, NULL, NULL)) {
525 		g_object_unref (filter_file);
526 		return;
527 	}
528 
529 	if (buffer == NULL) {
530 		g_object_unref (filter_file);
531 		return;
532 	}
533 
534 	doc = dom_document_new ();
535 	if (dom_document_load (doc, buffer, len, NULL)) {
536 		DomElement *node = DOM_ELEMENT (doc)->first_child;
537 
538 		if (node != NULL) {
539 			GthTest     *test;
540 			GtkTreeIter  iter;
541 
542 			if (find_test_by_id (filterbar,
543 					     dom_element_get_attribute (node, "id"),
544 					     &test,
545 					     &iter))
546 			{
547 				dom_domizable_load_from_element (DOM_DOMIZABLE (test), node);
548 
549 				g_signal_handlers_block_by_func (filterbar->priv->test_combo_box, test_combo_box_changed_cb, filterbar);
550 				gtk_combo_box_set_active_iter (GTK_COMBO_BOX (filterbar->priv->test_combo_box), &iter);
551 				g_signal_handlers_unblock_by_func (filterbar->priv->test_combo_box, test_combo_box_changed_cb, filterbar);
552 
553 				filterbar->priv->current_iter = iter;
554 				_gth_filterbar_set_test (GTH_FILTERBAR (filterbar), test);
555 			}
556 		}
557 	}
558 
559 	g_object_unref (doc);
560 	g_free (buffer);
561 	g_object_unref (filter_file);
562 }
563 
564 
565 GtkWidget *
gth_filterbar_get_extra_area(GthFilterbar * filterbar)566 gth_filterbar_get_extra_area (GthFilterbar *filterbar)
567 {
568 	return filterbar->priv->extra_area;
569 }
570