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