1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 
3 /*
4  *  GThumb
5  *
6  *  Copyright (C) 2009 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.h>
24 #include <glib/gi18n.h>
25 #include <gthumb.h>
26 #include "gth-catalog.h"
27 #include "gth-organize-task.h"
28 
29 
30 #define GET_WIDGET(name) _gtk_builder_get_widget (self->priv->builder, (name))
31 #define KEY_FORMAT ("%Y.%m.%d")
32 
33 enum {
34 	NAME_COLUMN = 0,
35 	CARDINALITY_COLUMN,
36 	CREATE_CATALOG_COLUMN,
37 	KEY_COLUMN,
38 	ICON_COLUMN
39 };
40 
41 
42 struct _GthOrganizeTaskPrivate {
43 	GthBrowser     *browser;
44 	GFile          *folder;
45 	GthGroupPolicy  group_policy;
46 	gboolean        recursive;
47 	gboolean        create_singletons;
48 	GthCatalog     *singletons_catalog;
49 	GtkBuilder     *builder;
50 	GtkWidget      *dialog;
51 	GtkListStore   *results_liststore;
52 	GHashTable     *catalogs;
53 	GdkPixbuf      *icon_pixbuf;
54 	gboolean        organized;
55 	GtkWidget      *file_list;
56 	int             n_catalogs;
57 	int             n_files;
58 	GthTest        *filter;
59 };
60 
61 
G_DEFINE_TYPE_WITH_CODE(GthOrganizeTask,gth_organize_task,GTH_TYPE_TASK,G_ADD_PRIVATE (GthOrganizeTask))62 G_DEFINE_TYPE_WITH_CODE (GthOrganizeTask,
63 			 gth_organize_task,
64 			 GTH_TYPE_TASK,
65 			 G_ADD_PRIVATE (GthOrganizeTask))
66 
67 
68 static void
69 gth_organize_task_finalize (GObject *object)
70 {
71 	GthOrganizeTask *self;
72 
73 	self = GTH_ORGANIZE_TASK (object);
74 
75 	gtk_widget_destroy (self->priv->dialog);
76 	g_object_unref (self->priv->folder);
77 	_g_object_unref (self->priv->singletons_catalog);
78 	g_object_unref (self->priv->builder);
79 	g_hash_table_destroy (self->priv->catalogs);
80 	g_object_unref (self->priv->icon_pixbuf);
81 	g_object_unref (self->priv->filter);
82 
83 	G_OBJECT_CLASS (gth_organize_task_parent_class)->finalize (object);
84 }
85 
86 
87 static void
save_catalog(gpointer key,gpointer value,gpointer user_data)88 save_catalog (gpointer key,
89 	      gpointer value,
90 	      gpointer user_data)
91 {
92 	GthCatalog *catalog = value;
93 
94 	gth_catalog_save (catalog);
95 }
96 
97 
98 static void
save_catalogs(GthOrganizeTask * self)99 save_catalogs (GthOrganizeTask *self)
100 {
101 	GtkTreeIter iter;
102 
103 	if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (self->priv->results_liststore), &iter)) {
104 		do {
105 			char     *key;
106 			char     *name;
107 			gboolean  create;
108 
109 			gtk_tree_model_get (GTK_TREE_MODEL (self->priv->results_liststore), &iter,
110 					    KEY_COLUMN, &key,
111 					    NAME_COLUMN, &name,
112 					    CREATE_CATALOG_COLUMN, &create,
113 					    -1);
114 			if (create) {
115 				GthCatalog *catalog;
116 				char       *original_name;
117 
118 				catalog = g_hash_table_lookup (self->priv->catalogs, key);
119 
120 				/* remove the name if it is equal to the date
121 				 * to avoid a duplication in the display-name
122 				 * attribute. */
123 				original_name = gth_datetime_strftime (gth_catalog_get_date (catalog), "%x");
124 				if (g_strcmp0 (original_name, name) != 0)
125 					gth_catalog_set_name (catalog, name);
126 				else
127 					gth_catalog_set_name (catalog, NULL);
128 
129 				g_free (original_name);
130 			}
131 			else
132 				g_hash_table_remove (self->priv->catalogs, key);
133 
134 			g_free (name);
135 			g_free (key);
136 		}
137 		while (gtk_tree_model_iter_next (GTK_TREE_MODEL (self->priv->results_liststore), &iter));
138 	}
139 
140 	g_hash_table_foreach (self->priv->catalogs, save_catalog, NULL);
141 	gth_task_completed (GTH_TASK (self), NULL);
142 }
143 
144 
145 static void
done_func(GError * error,gpointer user_data)146 done_func (GError   *error,
147 	   gpointer  user_data)
148 {
149 	GthOrganizeTask *self = user_data;
150 	char            *status_text;
151 
152 	if ((error != NULL) && ! g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
153 		gth_task_completed (GTH_TASK (self), error);
154 		return;
155 	}
156 
157 	if (! self->priv->create_singletons) {
158 		GtkTreeIter iter;
159 		int         singletons = 0;
160 
161 		if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (self->priv->results_liststore), &iter)) {
162 			do {
163 				char *key;
164 				int   n;
165 
166 				gtk_tree_model_get (GTK_TREE_MODEL (self->priv->results_liststore), &iter,
167 						    KEY_COLUMN, &key,
168 						    CARDINALITY_COLUMN, &n,
169 						    -1);
170 				if (n == 1) {
171 					gtk_list_store_set (self->priv->results_liststore, &iter,
172 							    CREATE_CATALOG_COLUMN, FALSE,
173 							    -1);
174 					singletons++;
175 
176 					if (self->priv->singletons_catalog != NULL) {
177 						GthCatalog *catalog;
178 						GList      *file_list;
179 
180 						catalog = g_hash_table_lookup (self->priv->catalogs, key);
181 						file_list = gth_catalog_get_file_list (catalog);
182 
183 						gth_catalog_insert_file (self->priv->singletons_catalog, file_list->data, -1);
184 						if (singletons == 1)
185 							g_hash_table_insert (self->priv->catalogs,
186 									     g_strdup (gth_catalog_get_name (self->priv->singletons_catalog)),
187 									     g_object_ref (self->priv->singletons_catalog));
188 					}
189 				}
190 
191 				g_free (key);
192 			}
193 			while (gtk_tree_model_iter_next (GTK_TREE_MODEL (self->priv->results_liststore), &iter));
194 		}
195 
196 		if ((self->priv->singletons_catalog != NULL) && (singletons > 0)) {
197 			gtk_list_store_append (self->priv->results_liststore, &iter);
198 			gtk_list_store_set (self->priv->results_liststore, &iter,
199 					    KEY_COLUMN, gth_catalog_get_name (self->priv->singletons_catalog),
200 					    NAME_COLUMN, gth_catalog_get_name (self->priv->singletons_catalog),
201 					    CARDINALITY_COLUMN, gth_catalog_get_size (self->priv->singletons_catalog),
202 					    CREATE_CATALOG_COLUMN, TRUE,
203 					    ICON_COLUMN, self->priv->icon_pixbuf,
204 					    -1);
205 		}
206 	}
207 	self->priv->organized = TRUE;
208 
209 	status_text = g_strdup_printf (_("Operation completed. Catalogs: %d. Images: %d."), self->priv->n_catalogs, self->priv->n_files);
210 	gtk_label_set_text (GTK_LABEL (GET_WIDGET ("progress_label")), status_text);
211 	gtk_label_set_ellipsize (GTK_LABEL (GET_WIDGET ("progress_label")), PANGO_ELLIPSIZE_NONE);
212 	g_free (status_text);
213 
214 	gtk_widget_set_sensitive (GET_WIDGET ("cancel_button"), FALSE);
215 	gtk_dialog_set_response_sensitive (GTK_DIALOG (self->priv->dialog), GTK_RESPONSE_OK, TRUE);
216 }
217 
218 
219 static GthCatalog *
add_catalog_for_date(GthOrganizeTask * self,const char * catalog_key,GTimeVal * timeval)220 add_catalog_for_date (GthOrganizeTask *self,
221 		      const char      *catalog_key,
222 		      GTimeVal        *timeval)
223 {
224 	GthCatalog         *catalog;
225 	GthDateTime        *date_time;
226 	GFile              *catalog_file;
227 	char               *catalog_name;
228 	GtkTreeIter         iter;
229 	GthGroupPolicyData  policy_data;
230 
231 	catalog = g_hash_table_lookup (self->priv->catalogs, catalog_key);
232 	if (catalog != NULL)
233 		return catalog;
234 
235 	date_time = gth_datetime_new ();
236 	gth_datetime_from_timeval (date_time, timeval);
237 
238 	policy_data.task = self;
239 	policy_data.date_time = date_time;
240 	policy_data.tag = NULL;
241 	policy_data.catalog = NULL;
242 	policy_data.catalog_file = NULL;
243 	gth_hook_invoke ("gth-organize-task-create-catalog", &policy_data);
244 
245 	catalog = policy_data.catalog;
246 	catalog_file = policy_data.catalog_file;
247 
248 	if (catalog == NULL) {
249 		_g_object_unref (catalog_file);
250 		catalog_file = gth_catalog_get_file_for_date (date_time, ".catalog");
251 		catalog = gth_catalog_load_from_file (catalog_file);
252 	}
253 
254 	if (catalog == NULL)
255 		catalog = gth_catalog_new ();
256 
257 #if 0
258 	date_time = gth_datetime_new ();
259 	gth_datetime_from_timeval (date_time, timeval);
260 
261 	catalog_file = NULL;
262 
263 	if (gth_main_extension_is_active ("search")) {
264 		catalog_file = gth_catalog_get_file_for_date (date_time, ".search");
265 		catalog = gth_catalog_load_from_file (catalog_file);
266 	}
267 
268 	if (catalog == NULL) {
269 		_g_object_unref (catalog_file);
270 		catalog_file = gth_catalog_get_file_for_date (date_time, ".catalog");
271 		catalog = gth_catalog_load_from_file (catalog_file);
272 	}
273 
274 	if (catalog == NULL) {
275 		if (gth_main_extension_is_active ("search")) {
276 			GthTest *date_test;
277 			GthTest *test_chain;
278 
279 			_g_object_unref (catalog_file);
280 			catalog_file = gth_catalog_get_file_for_date (date_time, ".search");
281 
282 			catalog = (GthCatalog *) gth_search_new ();
283 			gth_search_set_source (GTH_SEARCH (catalog), self->priv->folder, self->priv->recursive);
284 
285 			date_test = gth_main_get_registered_object (GTH_TYPE_TEST, (self->priv->group_policy == GTH_GROUP_POLICY_MODIFIED_DATE) ? "file::mtime" : "Embedded::Photo::DateTimeOriginal");
286 			gth_test_simple_set_data_as_date (GTH_TEST_SIMPLE (date_test), date_time->date);
287 			g_object_set (GTH_TEST_SIMPLE (date_test), "op", GTH_TEST_OP_EQUAL, "negative", FALSE, NULL);
288 			test_chain = gth_test_chain_new (GTH_MATCH_TYPE_ALL, date_test, NULL);
289 			gth_search_set_test (GTH_SEARCH (catalog), GTH_TEST_CHAIN (test_chain));
290 
291 			g_object_unref (test_chain);
292 			g_object_unref (date_test);
293 		}
294 		else
295 			catalog = gth_catalog_new ();
296 	}
297 #endif
298 
299 	gth_catalog_set_date (catalog, date_time);
300 	gth_catalog_set_file (catalog, catalog_file);
301 
302 	g_hash_table_insert (self->priv->catalogs, g_strdup (catalog_key), catalog);
303 	self->priv->n_catalogs++;
304 
305 	catalog_name = gth_datetime_strftime (date_time, "%x");
306 
307 	gtk_list_store_append (self->priv->results_liststore, &iter);
308 	gtk_list_store_set (self->priv->results_liststore, &iter,
309 			    KEY_COLUMN, catalog_key,
310 			    NAME_COLUMN, catalog_name,
311 			    CARDINALITY_COLUMN, 0,
312 			    CREATE_CATALOG_COLUMN, TRUE,
313 			    ICON_COLUMN, self->priv->icon_pixbuf,
314 			    -1);
315 
316 	g_free (catalog_name);
317 	g_object_unref (catalog_file);
318 	gth_datetime_free (date_time);
319 
320 	return catalog;
321 }
322 
323 
324 static GthCatalog *
add_catalog_for_tag(GthOrganizeTask * self,const char * catalog_key,const char * tag)325 add_catalog_for_tag (GthOrganizeTask *self,
326 		     const char      *catalog_key,
327 		     const char      *tag)
328 {
329 	GthCatalog         *catalog;
330 	GFile              *catalog_file;
331 	GtkTreeIter         iter;
332 	GthGroupPolicyData  policy_data;
333 
334 	catalog = g_hash_table_lookup (self->priv->catalogs, catalog_key);
335 	if (catalog != NULL)
336 		return catalog;
337 
338 	policy_data.task = self;
339 	policy_data.date_time = NULL;
340 	policy_data.tag = tag;
341 	policy_data.catalog = NULL;
342 	policy_data.catalog_file = NULL;
343 	gth_hook_invoke ("gth-organize-task-create-catalog", &policy_data);
344 
345 	catalog = policy_data.catalog;
346 	catalog_file = policy_data.catalog_file;
347 
348 	if (catalog == NULL) {
349 		_g_object_unref (catalog_file);
350 		catalog_file = gth_catalog_get_file_for_tag (tag, ".catalog");
351 		catalog = gth_catalog_load_from_file (catalog_file);
352 	}
353 
354 	if (catalog == NULL)
355 		catalog = gth_catalog_new ();
356 
357 #if 0
358 	catalog_file = NULL;
359 
360 	if (gth_main_extension_is_active ("search")) {
361 		catalog_file = gth_catalog_get_file_for_tag (tag, ".search");
362 		catalog = gth_catalog_load_from_file (catalog_file);
363 	}
364 
365 	if (catalog == NULL) {
366 		_g_object_unref (catalog_file);
367 		catalog_file = gth_catalog_get_file_for_tag (tag, ".catalog");
368 		catalog = gth_catalog_load_from_file (catalog_file);
369 	}
370 
371 	if (catalog == NULL) {
372 		if (gth_main_extension_is_active ("search")) {
373 			GthTest *tag_test;
374 			GthTest *test_chain;
375 
376 			_g_object_unref (catalog_file);
377 			catalog_file = gth_catalog_get_file_for_tag (tag, ".search");
378 
379 			catalog = (GthCatalog *) gth_search_new ();
380 			gth_search_set_source (GTH_SEARCH (catalog), self->priv->folder, self->priv->recursive);
381 
382 			tag_test = gth_main_get_registered_object (GTH_TYPE_TEST, (self->priv->group_policy == GTH_GROUP_POLICY_TAG) ? "comment::category" : "general::tags");
383 			gth_test_category_set (GTH_TEST_CATEGORY (tag_test), GTH_TEST_OP_CONTAINS, FALSE, tag);
384 			test_chain = gth_test_chain_new (GTH_MATCH_TYPE_ALL, tag_test, NULL);
385 			gth_search_set_test (GTH_SEARCH (catalog), GTH_TEST_CHAIN (test_chain));
386 
387 			g_object_unref (test_chain);
388 			g_object_unref (tag_test);
389 		}
390 		else
391 			catalog = gth_catalog_new ();
392 	}
393 #endif
394 
395 	gth_catalog_set_file (catalog, catalog_file);
396 
397 	g_hash_table_insert (self->priv->catalogs, g_strdup (catalog_key), catalog);
398 	self->priv->n_catalogs++;
399 
400 	gtk_list_store_append (self->priv->results_liststore, &iter);
401 	gtk_list_store_set (self->priv->results_liststore, &iter,
402 			    KEY_COLUMN, catalog_key,
403 			    NAME_COLUMN, tag,
404 			    CARDINALITY_COLUMN, 0,
405 			    CREATE_CATALOG_COLUMN, TRUE,
406 			    ICON_COLUMN, self->priv->icon_pixbuf,
407 			    -1);
408 
409 	g_object_unref (catalog_file);
410 
411 	return catalog;
412 }
413 
414 
415 static void
add_file_to_catalog(GthOrganizeTask * self,GthCatalog * catalog,const char * catalog_key,GthFileData * file_data)416 add_file_to_catalog (GthOrganizeTask *self,
417 		     GthCatalog      *catalog,
418 		     const char      *catalog_key,
419 		     GthFileData     *file_data)
420 {
421 	GtkTreeIter iter;
422 	int         n = 0;
423 
424 	if (! gth_catalog_insert_file (catalog, file_data->file, -1))
425 		return;
426 
427 	if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (self->priv->results_liststore), &iter)) {
428 		do {
429 			char *k;
430 
431 			gtk_tree_model_get (GTK_TREE_MODEL (self->priv->results_liststore),
432 					    &iter,
433 					    KEY_COLUMN, &k,
434 					    CARDINALITY_COLUMN, &n,
435 					    -1);
436 			if (g_strcmp0 (k, catalog_key) == 0) {
437 				self->priv->n_files++;
438 				n += 1;
439 				gtk_list_store_set (self->priv->results_liststore, &iter,
440 						    CARDINALITY_COLUMN, n,
441 						    -1);
442 				g_free (k);
443 				break;
444 			}
445 
446 			g_free (k);
447 		}
448 		while (gtk_tree_model_iter_next (GTK_TREE_MODEL (self->priv->results_liststore), &iter));
449 	}
450 }
451 
452 
453 static void
for_each_file_func(GFile * file,GFileInfo * info,gpointer user_data)454 for_each_file_func (GFile     *file,
455 		    GFileInfo *info,
456 		    gpointer   user_data)
457 {
458 	GthOrganizeTask *self = user_data;
459 	GthFileData     *file_data;
460 	char            *catalog_key;
461 	GObject         *metadata;
462 	GTimeVal         timeval;
463 	GthCatalog      *catalog;
464 
465 	if (g_file_info_get_file_type (info) != G_FILE_TYPE_REGULAR)
466 		return;
467 
468 	file_data = gth_file_data_new (file, info);
469 
470 	if (! gth_test_match (self->priv->filter, file_data)) {
471 		g_object_unref (file_data);
472 		return;
473 	}
474 
475 	catalog_key = NULL;
476 
477 	switch (self->priv->group_policy) {
478 	case GTH_GROUP_POLICY_DIGITALIZED_DATE:
479 		metadata = g_file_info_get_attribute_object (info, "Embedded::Photo::DateTimeOriginal");
480 		if (metadata != NULL) {
481 			if (_g_time_val_from_exif_date (gth_metadata_get_raw (GTH_METADATA (metadata)), &timeval)) {
482 				catalog_key = _g_time_val_strftime (&timeval, KEY_FORMAT);
483 				catalog = add_catalog_for_date (self, catalog_key, &timeval);
484 				add_file_to_catalog (self, catalog, catalog_key, file_data);
485 			}
486 		}
487 		break;
488 
489 	case GTH_GROUP_POLICY_MODIFIED_DATE:
490 		timeval = *gth_file_data_get_modification_time (file_data);
491 		catalog_key = _g_time_val_strftime (&timeval, KEY_FORMAT);
492 		catalog = add_catalog_for_date (self, catalog_key, &timeval);
493 		add_file_to_catalog (self, catalog, catalog_key, file_data);
494 		break;
495 
496 	case GTH_GROUP_POLICY_TAG:
497 	case GTH_GROUP_POLICY_TAG_EMBEDDED:
498 		if (self->priv->group_policy == GTH_GROUP_POLICY_TAG)
499 			metadata = g_file_info_get_attribute_object (file_data->info, "comment::categories");
500 		else
501 			metadata = g_file_info_get_attribute_object (file_data->info, "general::tags");
502 		if ((metadata != NULL) && GTH_IS_METADATA (metadata)) {
503 			GthStringList *categories;
504 			GList         *list;
505 			GList         *scan;
506 
507 			categories = gth_metadata_get_string_list (GTH_METADATA (metadata));
508 			list = gth_string_list_get_list (categories);
509 			for (scan = list; scan; scan = scan->next) {
510 				char *tag = (char *) scan->data;
511 
512 				catalog_key = g_strdup (tag);
513 				catalog = add_catalog_for_tag (self, catalog_key, tag);
514 				add_file_to_catalog (self, catalog, catalog_key, file_data);
515 			}
516 		}
517 		break;
518 	}
519 
520 	g_free (catalog_key);
521 	g_object_unref (file_data);
522 }
523 
524 
525 static DirOp
start_dir_func(GFile * directory,GFileInfo * info,GError ** error,gpointer user_data)526 start_dir_func (GFile      *directory,
527 		GFileInfo  *info,
528 		GError    **error,
529 		gpointer    user_data)
530 {
531 	GthOrganizeTask *self = user_data;
532 	char            *uri;
533 	char            *text;
534 
535 	uri = g_file_get_parse_name (directory);
536 	text = g_strdup_printf ("Searching in %s", uri);
537 	gtk_label_set_text (GTK_LABEL (GET_WIDGET ("progress_label")), text);
538 
539 	g_free (text);
540 	g_free (uri);
541 
542 	return DIR_OP_CONTINUE;
543 }
544 
545 
546 static void
gth_organize_task_exec(GthTask * base)547 gth_organize_task_exec (GthTask *base)
548 {
549 	GthOrganizeTask *self;
550 	const char      *attributes = "";
551 
552 	self = GTH_ORGANIZE_TASK (base);
553 
554 	self->priv->organized = FALSE;
555 	self->priv->n_catalogs = 0;
556 	self->priv->n_files = 0;
557 	gtk_list_store_clear (self->priv->results_liststore);
558 
559 	switch (self->priv->group_policy) {
560 	case GTH_GROUP_POLICY_DIGITALIZED_DATE:
561 		attributes = "standard::name,standard::type,time::modified,time::modified-usec,Embedded::Photo::DateTimeOriginal";
562 		break;
563 	case GTH_GROUP_POLICY_MODIFIED_DATE:
564 		attributes = "standard::name,standard::type,time::modified,time::modified-usec";
565 		break;
566 	case GTH_GROUP_POLICY_TAG:
567 		attributes = "standard::name,standard::type,time::modified,time::modified-usec,comment::categories";
568 		break;
569 	case GTH_GROUP_POLICY_TAG_EMBEDDED:
570 		attributes = "standard::name,standard::type,time::modified,time::modified-usec,general::tags";
571 		break;
572 	}
573 
574 	_g_directory_foreach_child (self->priv->folder,
575 				   self->priv->recursive,
576 				   TRUE,
577 				   attributes,
578 				   gth_task_get_cancellable (GTH_TASK (self)),
579 				   start_dir_func,
580 				   for_each_file_func,
581 				   done_func,
582 				   self);
583 
584 	gtk_widget_set_sensitive (GET_WIDGET ("cancel_button"), TRUE);
585 	gtk_dialog_set_response_sensitive (GTK_DIALOG (self->priv->dialog), GTK_RESPONSE_OK, FALSE);
586 	gtk_window_set_transient_for (GTK_WINDOW (self->priv->dialog), GTK_WINDOW (self->priv->browser));
587 	gtk_window_set_modal (GTK_WINDOW (self->priv->dialog), TRUE);
588 	gtk_widget_show (self->priv->dialog);
589 
590 	gth_task_dialog (base, TRUE, self->priv->dialog);
591 }
592 
593 
594 static void
gth_organize_task_cancelled(GthTask * base)595 gth_organize_task_cancelled (GthTask *base)
596 {
597 }
598 
599 
600 static void
organize_files_dialog_response_cb(GtkDialog * dialog,int response_id,gpointer user_data)601 organize_files_dialog_response_cb (GtkDialog *dialog,
602 				   int        response_id,
603 				   gpointer   user_data)
604 {
605 	GthOrganizeTask *self = user_data;
606 
607 	if (response_id == GTK_RESPONSE_DELETE_EVENT) {
608 		if (self->priv->organized)
609 			response_id = GTK_RESPONSE_CLOSE;
610 		else
611 			response_id = GTK_RESPONSE_CANCEL;
612 	}
613 
614 	if (self->priv->organized && (response_id == GTK_RESPONSE_CANCEL))
615 		response_id = GTK_RESPONSE_CLOSE;
616 
617 	switch (response_id) {
618 	case GTK_RESPONSE_CANCEL:
619 		gth_task_cancel (GTH_TASK (self));
620 		break;
621 
622 	case GTK_RESPONSE_CLOSE:
623 		gth_task_completed (GTH_TASK (self), NULL);
624 		break;
625 
626 	case GTK_RESPONSE_OK:
627 		save_catalogs (self);
628 		break;
629 	}
630 }
631 
632 
633 static void
gth_organize_task_class_init(GthOrganizeTaskClass * klass)634 gth_organize_task_class_init (GthOrganizeTaskClass *klass)
635 {
636 	GObjectClass *object_class;
637 	GthTaskClass *task_class;
638 
639 	object_class = (GObjectClass*) klass;
640 	object_class->finalize = gth_organize_task_finalize;
641 
642 	task_class = (GthTaskClass*) klass;
643 	task_class->exec = gth_organize_task_exec;
644 	task_class->cancelled = gth_organize_task_cancelled;
645 }
646 
647 
648 static void
catalog_name_cellrenderertext_edited_cb(GtkCellRendererText * renderer,char * path,char * new_text,gpointer user_data)649 catalog_name_cellrenderertext_edited_cb (GtkCellRendererText *renderer,
650 					 char                *path,
651 					 char                *new_text,
652 					 gpointer             user_data)
653 {
654 	GthOrganizeTask *self = user_data;
655 	GtkTreePath     *tree_path;
656 	GtkTreeIter      iter;
657 
658 	tree_path = gtk_tree_path_new_from_string (path);
659 	if (! gtk_tree_model_get_iter (GTK_TREE_MODEL (self->priv->results_liststore),
660 				       &iter,
661 				       tree_path))
662 	{
663 		gtk_tree_path_free (tree_path);
664 		return;
665 	}
666 	gtk_tree_path_free (tree_path);
667 
668 	gtk_list_store_set (self->priv->results_liststore,
669 			    &iter,
670 			    NAME_COLUMN, new_text,
671 			    -1);
672 }
673 
674 
675 static void
create_cellrenderertoggle_toggled_cb(GtkCellRendererToggle * cell_renderer,char * path,gpointer user_data)676 create_cellrenderertoggle_toggled_cb (GtkCellRendererToggle *cell_renderer,
677 				      char                  *path,
678 				      gpointer               user_data)
679 {
680 	GthOrganizeTask *self = user_data;
681 	GtkTreePath     *tpath;
682 	GtkTreeIter      iter;
683 
684 	tpath = gtk_tree_path_new_from_string (path);
685 	if (tpath == NULL)
686 		return;
687 
688 	if (gtk_tree_model_get_iter (GTK_TREE_MODEL (self->priv->results_liststore), &iter, tpath)) {
689 		gboolean create;
690 
691 		gtk_tree_model_get (GTK_TREE_MODEL (self->priv->results_liststore), &iter,
692 				    CREATE_CATALOG_COLUMN, &create,
693 				    -1);
694 		gtk_list_store_set (self->priv->results_liststore, &iter,
695 				    CREATE_CATALOG_COLUMN, ! create,
696 				    -1);
697 	}
698 
699 	gtk_tree_path_free (tpath);
700 }
701 
702 
703 static void
file_list_info_ready_cb(GList * files,GError * error,gpointer user_data)704 file_list_info_ready_cb (GList    *files,
705 			 GError   *error,
706 			 gpointer  user_data)
707 {
708 	GthOrganizeTask *self = user_data;
709 
710 	if (error != NULL)
711 		return;
712 
713 	gth_file_list_set_files (GTH_FILE_LIST (self->priv->file_list), files);
714 }
715 
716 
717 static void
organization_treeview_selection_changed_cb(GtkTreeSelection * treeselection,gpointer user_data)718 organization_treeview_selection_changed_cb (GtkTreeSelection *treeselection,
719                                             gpointer          user_data)
720 {
721 	GthOrganizeTask *self = user_data;
722 	GtkTreeIter      iter;
723 	char            *key;
724 	GthCatalog      *catalog;
725 
726 	if (! self->priv->organized)
727 		return;
728 	if (! gtk_tree_selection_get_selected (treeselection, NULL, &iter))
729 		return;
730 
731 	gtk_tree_model_get (GTK_TREE_MODEL (self->priv->results_liststore), &iter,
732 			    KEY_COLUMN, &key,
733 			    -1);
734 	catalog = g_hash_table_lookup (self->priv->catalogs, key);
735 	if (catalog != NULL) {
736 		GList *file_list;
737 
738 		gtk_widget_show (GET_WIDGET ("preview_box"));
739 
740 		file_list = gth_catalog_get_file_list (catalog);
741 		_g_file_list_query_info_async (file_list,
742 					       GTH_LIST_DEFAULT,
743 					       GFILE_STANDARD_ATTRIBUTES_WITH_FAST_CONTENT_TYPE,
744 					       NULL,
745 					       file_list_info_ready_cb,
746 					       self);
747 	}
748 
749 	g_free (key);
750 }
751 
752 
753 static void
select_all_button_clicked_cb(GtkButton * button,gpointer user_data)754 select_all_button_clicked_cb (GtkButton *button,
755 			      gpointer   user_data)
756 {
757 	GthOrganizeTask *self = user_data;
758 	GtkTreeIter      iter;
759 
760 	if (! gtk_tree_model_get_iter_first (GTK_TREE_MODEL (self->priv->results_liststore), &iter))
761 		return;
762 	do {
763 		gtk_list_store_set (self->priv->results_liststore, &iter,
764 				    CREATE_CATALOG_COLUMN, TRUE,
765 				    -1);
766 	}
767 	while (gtk_tree_model_iter_next (GTK_TREE_MODEL (self->priv->results_liststore), &iter));
768 }
769 
770 
771 static void
select_none_button_clicked_cb(GtkButton * button,gpointer user_data)772 select_none_button_clicked_cb (GtkButton *button,
773 			       gpointer   user_data)
774 {
775 	GthOrganizeTask *self = user_data;
776 	GtkTreeIter      iter;
777 
778 	if (! gtk_tree_model_get_iter_first (GTK_TREE_MODEL (self->priv->results_liststore), &iter))
779 		return;
780 	do {
781 		gtk_list_store_set (self->priv->results_liststore, &iter,
782 				    CREATE_CATALOG_COLUMN, FALSE,
783 				    -1);
784 	}
785 	while (gtk_tree_model_iter_next (GTK_TREE_MODEL (self->priv->results_liststore), &iter));
786 }
787 
788 
789 static void
cancel_button_clicked_cb(GtkButton * button,gpointer user_data)790 cancel_button_clicked_cb (GtkButton *button,
791 			  gpointer   user_data)
792 {
793 	GthOrganizeTask *self = user_data;
794 
795 	if (! self->priv->organized)
796 		gth_task_cancel (GTH_TASK (self));
797 }
798 
799 
800 static void
gth_organize_task_init(GthOrganizeTask * self)801 gth_organize_task_init (GthOrganizeTask *self)
802 {
803 	GIcon *icon;
804 
805 	self->priv = gth_organize_task_get_instance_private (self);
806 	self->priv->builder = _gtk_builder_new_from_file ("organize-files-task.ui", "catalogs");
807 	self->priv->results_liststore = (GtkListStore *) gtk_builder_get_object (self->priv->builder, "results_liststore");
808 	self->priv->catalogs = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
809 	self->priv->filter = gth_main_get_general_filter ();
810 
811 	self->priv->dialog = g_object_new (GTK_TYPE_DIALOG,
812 				           "title", _("Organize Files"),
813 					   "transient-for", GTK_WINDOW (self->priv->browser),
814 					   "resizable", TRUE,
815 					   "use-header-bar", _gtk_settings_get_dialogs_use_header (),
816 					   NULL);
817 	gtk_container_add (GTK_CONTAINER (gtk_dialog_get_content_area (GTK_DIALOG (self->priv->dialog))),
818 			   _gtk_builder_get_widget (self->priv->builder, "dialog_content"));
819 	gtk_dialog_add_buttons (GTK_DIALOG (self->priv->dialog),
820 				_GTK_LABEL_CANCEL, GTK_RESPONSE_CANCEL,
821 				_GTK_LABEL_SAVE, GTK_RESPONSE_OK,
822 				NULL);
823 	_gtk_dialog_add_class_to_response (GTK_DIALOG (self->priv->dialog), GTK_RESPONSE_OK, GTK_STYLE_CLASS_SUGGESTED_ACTION);
824 
825 	gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (self->priv->results_liststore), KEY_COLUMN, GTK_SORT_ASCENDING);
826 	g_object_set (GET_WIDGET ("catalog_name_cellrenderertext"), "editable", TRUE, NULL);
827 
828 	icon = g_themed_icon_new ("file-catalog-symbolic");
829 	self->priv->icon_pixbuf = _g_icon_get_pixbuf (icon,
830 						      _gtk_widget_lookup_for_size (GET_WIDGET ("organization_treeview"), GTK_ICON_SIZE_MENU),
831 						      _gtk_widget_get_icon_theme (GET_WIDGET ("organization_treeview")));
832 	g_object_unref (icon);
833 
834 	self->priv->file_list = gth_file_list_new (gth_grid_view_new (), GTH_FILE_LIST_MODE_NORMAL, FALSE);
835 	gth_file_list_set_filter (GTH_FILE_LIST (self->priv->file_list), NULL);
836 	gth_file_list_set_caption (GTH_FILE_LIST (self->priv->file_list), "standard::display-name");
837 	gth_file_list_set_thumb_size (GTH_FILE_LIST (self->priv->file_list), 128);
838 	gth_file_list_set_ignore_hidden (GTH_FILE_LIST (self->priv->file_list), FALSE);
839 	gtk_widget_show (self->priv->file_list);
840 	gtk_widget_set_size_request (self->priv->file_list, 350, -1);
841 	gtk_box_pack_start (GTK_BOX (GET_WIDGET ("preview_box")), self->priv->file_list, TRUE, TRUE, 0);
842 
843 	g_signal_connect (GET_WIDGET ("catalog_name_cellrenderertext"),
844 			  "edited",
845 			  G_CALLBACK (catalog_name_cellrenderertext_edited_cb),
846 			  self);
847 	g_signal_connect (GET_WIDGET ("create_cellrenderertoggle"),
848 			  "toggled",
849 			  G_CALLBACK (create_cellrenderertoggle_toggled_cb),
850 			  self);
851 	g_signal_connect (self->priv->dialog,
852 			  "delete-event",
853 			  G_CALLBACK (gtk_true),
854 			  NULL);
855 	g_signal_connect (self->priv->dialog,
856 			  "response",
857 			  G_CALLBACK (organize_files_dialog_response_cb),
858 			  self);
859 	g_signal_connect (gtk_tree_view_get_selection (GTK_TREE_VIEW (GET_WIDGET ("organization_treeview"))),
860 			  "changed",
861 			  G_CALLBACK (organization_treeview_selection_changed_cb),
862 			  self);
863 	g_signal_connect (GET_WIDGET ("select_all_button"),
864 			  "clicked",
865 			  G_CALLBACK (select_all_button_clicked_cb),
866 			  self);
867 	g_signal_connect (GET_WIDGET ("select_none_button"),
868 			  "clicked",
869 			  G_CALLBACK (select_none_button_clicked_cb),
870 			  self);
871 	g_signal_connect (GET_WIDGET ("cancel_button"),
872 			  "clicked",
873 			  G_CALLBACK (cancel_button_clicked_cb),
874 			  self);
875 }
876 
877 
878 GthTask *
gth_organize_task_new(GthBrowser * browser,GFile * folder,GthGroupPolicy group_policy)879 gth_organize_task_new (GthBrowser     *browser,
880 		       GFile          *folder,
881 		       GthGroupPolicy  group_policy)
882 {
883 	GthOrganizeTask *self;
884 
885 	self = (GthOrganizeTask *) g_object_new (GTH_TYPE_ORGANIZE_TASK, NULL);
886 	self->priv->browser = browser;
887 	self->priv->folder = g_file_dup (folder);
888 	self->priv->group_policy = group_policy;
889 
890 	return (GthTask*) self;
891 }
892 
893 
894 void
gth_organize_task_set_recursive(GthOrganizeTask * self,gboolean recursive)895 gth_organize_task_set_recursive (GthOrganizeTask *self,
896 				 gboolean         recursive)
897 {
898 	self->priv->recursive = recursive;
899 }
900 
901 
902 void
gth_organize_task_set_create_singletons(GthOrganizeTask * self,gboolean create)903 gth_organize_task_set_create_singletons (GthOrganizeTask *self,
904 					 gboolean         create)
905 {
906 	self->priv->create_singletons = create;
907 }
908 
909 
910 void
gth_organize_task_set_singletons_catalog(GthOrganizeTask * self,const char * catalog_name)911 gth_organize_task_set_singletons_catalog (GthOrganizeTask *self,
912 					  const char      *catalog_name)
913 {
914 	GFile *file;
915 
916 	_g_object_unref (self->priv->singletons_catalog);
917 	self->priv->singletons_catalog = NULL;
918 	if (catalog_name == NULL)
919 		return;
920 
921 	self->priv->singletons_catalog = gth_catalog_new ();
922 	file = _g_file_new_for_display_name ("catalog:///", catalog_name, ".catalog");
923 	gth_catalog_set_file (self->priv->singletons_catalog, file);
924 	gth_catalog_set_name (self->priv->singletons_catalog, catalog_name);
925 
926 	g_object_unref (file);
927 }
928 
929 
930 GFile *
gth_organize_task_get_folder(GthOrganizeTask * self)931 gth_organize_task_get_folder (GthOrganizeTask *self)
932 {
933 	return self->priv->folder;
934 }
935 
936 
937 GthGroupPolicy
gth_organize_task_get_group_policy(GthOrganizeTask * self)938 gth_organize_task_get_group_policy (GthOrganizeTask *self)
939 {
940 	return self->priv->group_policy;
941 }
942 
943 
944 gboolean
gth_organize_task_get_recursive(GthOrganizeTask * self)945 gth_organize_task_get_recursive (GthOrganizeTask *self)
946 {
947 	return self->priv->recursive;
948 }
949