gqview.h -> main.h
[geeqie.git] / src / bar_info.c
1 /*
2  * Geeqie
3  * (C) 2004 John Ellis
4  *
5  * Author: John Ellis
6  *
7  * This software is released under the GNU General Public License (GNU GPL).
8  * Please read the included file COPYING for more information.
9  * This software comes with no warranty of any kind, use at your own risk!
10  */
11
12
13 #include "main.h"
14 #include "exif.h"
15
16 #include "bar_info.h"
17
18 #include "cache.h"
19 #include "filelist.h"
20 #include "info.h"
21 #include "utilops.h"
22 #include "ui_bookmark.h"
23 #include "ui_fileops.h"
24 #include "ui_misc.h"
25 #include "ui_utildlg.h"
26
27
28 #define BAR_KEYWORD_AUTOSAVE_TIME 10000
29
30
31 static const gchar *keyword_favorite_defaults[] = {
32         N_("Favorite"),
33         N_("Todo"),
34         N_("People"),
35         N_("Places"),
36         N_("Art"),
37         N_("Nature"),
38         N_("Possessions"),
39         NULL
40 };
41
42
43 static void bar_info_keyword_update_all(void);
44
45
46 /*
47  *-------------------------------------------------------------------
48  * keyword / comment utils
49  *-------------------------------------------------------------------
50  */
51
52 static gint comment_file_write(gchar *path, GList *keywords, const gchar *comment)
53 {
54         FILE *f;
55
56         f = fopen(path, "w");
57         if (!f) return FALSE;
58
59         fprintf(f, "#Geeqie comment (%s)\n\n", VERSION);
60
61         fprintf(f, "[keywords]\n");
62         while (keywords)
63                 {
64                 const gchar *word = keywords->data;
65                 keywords = keywords->next;
66
67                 fprintf(f, "%s\n", word);
68                 }
69         fprintf(f, "\n");
70
71         fprintf(f, "[comment]\n");
72         fprintf(f, "%s\n", (comment) ? comment : "");
73
74         fprintf(f, "#end\n");
75
76         fclose(f);
77
78         return TRUE;
79 }
80
81 static gint comment_legacy_write(FileData *fd, GList *keywords, const gchar *comment)
82 {
83         gchar *comment_path;
84         gint success = FALSE;
85
86         /* If an existing metadata file exists, we will try writing to
87          * it's location regardless of the user's preference.
88          */
89         comment_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
90         if (comment_path && !access_file(comment_path, W_OK))
91                 {
92                 g_free(comment_path);
93                 comment_path = NULL;
94                 }
95
96         if (!comment_path)
97                 {
98                 gchar *comment_dir;
99                 mode_t mode = 0755;
100
101                 comment_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
102                 if (cache_ensure_dir_exists(comment_dir, mode))
103                         {
104                         comment_path = g_strconcat(comment_dir, "/", fd->name,
105                                                    GQVIEW_CACHE_EXT_METADATA, NULL);
106                         }
107                 g_free(comment_dir);
108                 }
109
110         if (comment_path)
111                 {
112                 gchar *comment_pathl;
113
114                 if (debug) printf("Saving comment: %s\n", comment_path);
115
116                 comment_pathl = path_from_utf8(comment_path);
117
118                 success = comment_file_write(comment_pathl, keywords, comment);
119
120                 g_free(comment_pathl);
121                 g_free(comment_path);
122                 }
123
124         return success;
125 }
126
127 static gint comment_file_read(gchar *path, GList **keywords, gchar **comment)
128 {
129         FILE *f;
130         gchar s_buf[1024];
131         gchar *key = NULL;
132         GList *list = NULL;
133         GString *comment_build = NULL;
134
135         f = fopen(path, "r");
136         if (!f) return FALSE;
137
138         while (fgets(s_buf,sizeof(s_buf), f))
139                 {
140                 if (s_buf[0]=='#') continue;
141                 if (s_buf[0]=='[')
142                         {
143                         gint c = 0;
144                         gchar *ptr = s_buf + 1;
145
146                         while(ptr[c] != ']' && ptr[c] != '\n' && ptr[c] != '\0') c++;
147
148                         g_free(key);
149                         key = g_strndup(ptr, c);
150                         }
151                 else if (key)
152                         {
153                         gint newline = FALSE;
154                         gchar *ptr = s_buf;
155
156                         while (*ptr != '\n' && *ptr != '\0') ptr++;
157                         if (*ptr == '\n')
158                                 {
159                                 *ptr = '\0';
160                                 newline = TRUE;
161                                 }
162
163                         if (strcasecmp(key, "keywords") == 0)
164                                 {
165                                 if (strlen(s_buf) > 0) list = g_list_prepend(list, g_strdup(s_buf));
166                                 }
167                         else if (strcasecmp(key, "comment") == 0)
168                                 {
169                                 if (!comment_build) comment_build = g_string_new("");
170                                 g_string_append(comment_build, s_buf);
171                                 if (strlen(s_buf) > 0 && newline) g_string_append_c(comment_build, '\n');
172                                 }
173                         }
174                 }
175
176         fclose(f);
177         g_free(key);
178
179         *keywords = g_list_reverse(list);
180         if (comment_build)
181                 {
182                 if (comment) *comment = g_strdup(comment_build->str);
183                 g_string_free(comment_build, TRUE);
184                 }
185
186         return TRUE;
187 }
188
189 static gint comment_delete_legacy(FileData *fd)
190 {
191         gchar *comment_path;
192         gchar *comment_pathl;
193         gint success = FALSE;
194         if (!fd) return FALSE;
195
196         comment_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
197         if (!comment_path) return FALSE;
198
199         comment_pathl = path_from_utf8(comment_path);
200
201         success = !unlink(comment_pathl);
202
203         g_free(comment_pathl);
204         g_free(comment_path);
205
206         return success;
207 }
208
209 static gint comment_legacy_read(FileData *fd, GList **keywords, gchar **comment)
210 {
211         gchar *comment_path;
212         gchar *comment_pathl;
213         gint success = FALSE;
214         if (!fd) return FALSE;
215
216         comment_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
217         if (!comment_path) return FALSE;
218
219         comment_pathl = path_from_utf8(comment_path);
220
221         success = comment_file_read(comment_pathl, keywords, comment);
222
223         g_free(comment_pathl);
224         g_free(comment_path);
225
226         return success;
227 }
228
229 gchar *comment_key = "Xmp.dc.description";
230 gchar *keyword_key = "Xmp.dc.subject";
231
232 static gint comment_xmp_read(FileData *fd, GList **keywords, gchar **comment)
233 {
234         ExifData *exif = exif_read_fd(fd, FALSE);
235         gint success;
236         if (!exif) return FALSE;
237
238         if (comment)
239                 {
240                 ExifItem *item = exif_get_item(exif, comment_key);
241                 *comment = exif_item_get_string(item, 0);
242                 }
243         
244         if (keywords)
245                 {
246                 ExifItem *item = exif_get_item(exif, keyword_key);
247                 int count = exif_item_get_elements(item);
248                 int i = 0;
249                 GList *work = NULL;
250                 char *kw = NULL;
251                 
252                 while (i < count && (kw = exif_item_get_string(item, i++)))
253                         {
254                         work = g_list_append(work, (gpointer) kw);
255                         }
256                 
257                 *keywords = work;
258                 }
259                 
260         exif_free(exif);
261         
262         success = *comment || *keywords;
263         
264         return success;
265 }
266
267 static gint comment_xmp_write(FileData *fd, GList *keywords, const gchar *comment)
268 {
269         gint success = FALSE;
270         GList *work = keywords;
271         ExifData *exif = exif_read_fd(fd, FALSE);
272         if (!exif) return FALSE;
273
274         ExifItem *item = exif_get_item(exif, comment_key);
275         
276         if (item && !(comment && comment[0])) 
277                 {
278                 exif_item_delete(exif, item); 
279                 item = NULL;
280                 }
281                 
282         if (!item && comment && comment[0]) item = exif_add_item(exif, comment_key);
283         if (item) exif_item_set_string(item, comment);
284
285
286
287         while ((item = exif_get_item(exif, keyword_key)))
288                 {
289                 exif_item_delete(exif, item);
290                 }
291         
292         if (work)
293                 {
294                 item = exif_add_item(exif, keyword_key);
295                 
296                 while (work)
297                         {
298                         gchar *kw = (gchar *) work->data;
299                         work = work->next;
300                 
301                         exif_item_set_string(item, kw);
302                         }
303                 }
304         
305         success = exif_write(exif);
306                 
307         exif_free(exif);
308         
309         return success;
310 }
311
312 gint comment_write(FileData *fd, GList *keywords, const gchar *comment)
313 {
314         if (!fd) return FALSE;
315
316         if (enable_metadata_dirs && /* FIXME - use dedicated option */
317             comment_xmp_write(fd, keywords, comment))
318                 {
319                 comment_delete_legacy(fd);
320                 return TRUE;
321                 }
322
323         return comment_legacy_write(fd, keywords, comment);
324 }
325
326 gint comment_read(FileData *fd, GList **keywords, gchar **comment)
327 {
328         GList *keywords1 = NULL;
329         GList *keywords2 = NULL;
330         gchar *comment1 = NULL;
331         gchar *comment2 = NULL;
332         gint res1, res2;
333
334         if (!fd) return FALSE;
335
336         res1 = comment_xmp_read(fd, &keywords1, &comment1);
337         res2 = comment_legacy_read(fd, &keywords2, &comment2);
338         
339         if (!res1 && !res2)
340                 {
341                 return FALSE;
342                 }
343         
344         if (keywords)
345                 {
346                 if (res1 && res2)
347                         *keywords = g_list_concat(keywords1, keywords2);
348                 else
349                         *keywords = res1 ? keywords1 : keywords2;
350                 }
351         else
352                 {
353                 if (res1) string_list_free(keywords1);
354                 if (res2) string_list_free(keywords2);
355                 }
356                 
357
358         if (comment)
359                 {
360                 if (res1 && res2 && comment1 && comment2 && comment1[0] && comment2[0])
361                         *comment = g_strdup_printf("%s\n%s", comment1, comment2);
362                 else
363                         *comment = res1 ? comment1 : comment2;
364                 }
365         if (res1 && (!comment || *comment != comment1)) g_free(comment1);
366         if (res2 && (!comment || *comment != comment2)) g_free(comment2);
367         
368         return TRUE;
369 }
370
371
372 static gchar *comment_pull(GtkWidget *textview)
373 {
374         GtkTextBuffer *buffer;
375         GtkTextIter start, end;
376                                                                                                                     
377         buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview));
378         gtk_text_buffer_get_bounds(buffer, &start, &end);
379                                                                                                                     
380         return gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
381 }
382
383 GList *keyword_list_pull(GtkWidget *text_widget)
384 {
385         GList *list = NULL;
386         gchar *text;
387         gchar *ptr;
388
389         if (GTK_IS_TEXT_VIEW(text_widget))
390                 {
391                 text = comment_pull(text_widget);
392                 }
393         else if (GTK_IS_ENTRY(text_widget))
394                 {
395                 text = g_strdup(gtk_entry_get_text(GTK_ENTRY(text_widget)));
396                 }
397         else
398                 {
399                 return NULL;
400                 }
401
402         ptr = text;
403         while (*ptr != '\0')
404                 {
405                 gchar *begin;
406                 gint l = 0;
407
408                 while (*ptr == ' ' || *ptr == ',' || *ptr == '\n' || *ptr == '\r' || *ptr == '\b') ptr++;
409                 begin = ptr;
410                 if (*ptr != '\0')
411                         {
412                         while (*ptr != ' ' && *ptr != ',' &&
413                                *ptr != '\n' && *ptr != '\r' && *ptr != '\b' &&
414                                *ptr != '\0')
415                                 {
416                                 ptr++;
417                                 l++;
418                                 }
419                         }
420
421                 if (l > 0) list = g_list_append(list, g_strndup(begin, l));
422                 }
423
424         g_free(text);
425
426         return list;
427 }
428
429 void keyword_list_push(GtkWidget *textview, GList *list)
430 {
431         GtkTextBuffer *buffer;
432         GtkTextIter start, end;
433
434         buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview));
435         gtk_text_buffer_get_bounds(buffer, &start, &end);
436         gtk_text_buffer_delete (buffer, &start, &end);
437
438         while (list)
439                 {
440                 const gchar *word = list->data;
441                 GtkTextIter iter;
442
443                 gtk_text_buffer_get_end_iter(buffer, &iter);
444                 if (word) gtk_text_buffer_insert(buffer, &iter, word, -1);
445                 gtk_text_buffer_get_end_iter(buffer, &iter);
446                 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
447
448                 list = list->next;
449                 }
450 }
451
452 static void metadata_set_keywords(FileData *fd, GList *list, gint add)
453 {
454         gchar *comment = NULL;
455         GList *keywords = NULL;
456         GList *save_list = NULL;
457
458         comment_read(fd, &keywords, &comment);
459
460         if (add)
461                 {
462                 GList *work;
463
464                 work = list;
465                 while (work)
466                         {
467                         gchar *key;
468                         GList *p;
469
470                         key = work->data;
471                         work = work->next;
472
473                         p = keywords;
474                         while (p && key)
475                                 {
476                                 gchar *needle = p->data;
477                                 p = p->next;
478
479                                 if (strcmp(needle, key) == 0) key = NULL;
480                                 }
481
482                         if (key) keywords = g_list_append(keywords, g_strdup(key));
483                         }
484                 save_list = keywords;
485                 }
486         else
487                 {
488                 save_list = list;
489                 }
490
491         comment_write(fd, save_list, comment);
492
493         string_list_free(keywords);
494         g_free(comment);
495 }
496
497 /*
498  *-------------------------------------------------------------------
499  * keyword list dialog
500  *-------------------------------------------------------------------
501  */
502
503 #define KEYWORD_DIALOG_WIDTH  200
504 #define KEYWORD_DIALOG_HEIGHT 250
505
506 typedef struct _KeywordDlg KeywordDlg;
507 struct _KeywordDlg
508 {
509         GenericDialog *gd;
510         GtkWidget *treeview;
511 };
512
513 static KeywordDlg *keyword_dialog = NULL;
514
515
516 static void keyword_dialog_cancel_cb(GenericDialog *gd, gpointer data)
517 {
518         g_free(keyword_dialog);
519         keyword_dialog = NULL;
520 }
521
522 static void keyword_dialog_ok_cb(GenericDialog *gd, gpointer data)
523 {
524         KeywordDlg *kd = data;
525         GtkTreeModel *store;
526         GtkTreeIter iter;
527         gint valid;
528
529         history_list_free_key("keywords");
530
531         store = gtk_tree_view_get_model(GTK_TREE_VIEW(kd->treeview));
532         valid = gtk_tree_model_get_iter_first(store, &iter);
533         while (valid)
534                 {
535                 gchar *key;
536
537                 gtk_tree_model_get(store, &iter, 0, &key, -1);
538                 valid = gtk_tree_model_iter_next(store, &iter);
539
540                 history_list_add_to_key("keywords", key, 0);
541                 }
542
543         keyword_dialog_cancel_cb(gd, data);
544
545         bar_info_keyword_update_all();
546 }
547
548 static void keyword_dialog_add_cb(GtkWidget *button, gpointer data)
549 {
550         KeywordDlg *kd = data;
551         GtkTreeSelection *selection;
552         GtkTreeModel *store;
553         GtkTreeIter sibling;
554         GtkTreeIter iter;
555         GtkTreePath *tpath;
556
557         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(kd->treeview));
558         if (gtk_tree_selection_get_selected(selection, &store, &sibling))
559                 {
560                 gtk_list_store_insert_before(GTK_LIST_STORE(store), &iter, &sibling);
561                 }
562         else
563                 {
564                 store = gtk_tree_view_get_model(GTK_TREE_VIEW(kd->treeview));
565                 gtk_list_store_append(GTK_LIST_STORE(store), &iter);
566                 }
567
568         gtk_list_store_set(GTK_LIST_STORE(store), &iter, 1, TRUE, -1);
569
570         tpath = gtk_tree_model_get_path(store, &iter);
571         gtk_tree_view_set_cursor(GTK_TREE_VIEW(kd->treeview), tpath,
572                                  gtk_tree_view_get_column(GTK_TREE_VIEW(kd->treeview), 0), TRUE);
573         gtk_tree_path_free(tpath);
574 }
575
576 static void keyword_dialog_remove_cb(GtkWidget *button, gpointer data)
577 {
578         KeywordDlg *kd = data;
579         GtkTreeSelection *selection;
580         GtkTreeModel *store;
581         GtkTreeIter iter;
582         GtkTreeIter next;
583         GtkTreePath *tpath;
584
585         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(kd->treeview));
586         if (!gtk_tree_selection_get_selected(selection, &store, &iter)) return;
587
588         tpath = NULL;
589         next = iter;
590         if (gtk_tree_model_iter_next(store, &next))
591                 {
592                 tpath = gtk_tree_model_get_path(store, &next);
593                 }
594         else
595                 {
596                 tpath = gtk_tree_model_get_path(store, &iter);
597                 if (!gtk_tree_path_prev(tpath))
598                         {
599                         gtk_tree_path_free(tpath);
600                         tpath = NULL;
601                         }
602                 }
603         if (tpath)
604                 {
605                 gtk_tree_view_set_cursor(GTK_TREE_VIEW(kd->treeview), tpath,
606                                          gtk_tree_view_get_column(GTK_TREE_VIEW(kd->treeview), 0), FALSE);
607                 gtk_tree_path_free(tpath);
608                 }
609
610         gtk_list_store_remove(GTK_LIST_STORE(store), &iter);
611 }
612
613 static void keyword_dialog_edit_cb(GtkCellRendererText *renderer, const gchar *path,
614                                    const gchar *new_text, gpointer data)
615 {
616         KeywordDlg *kd = data;
617         GtkTreeModel *store;
618         GtkTreeIter iter;
619         GtkTreePath *tpath;
620
621         if (!new_text || strlen(new_text) == 0) return;
622
623         store = gtk_tree_view_get_model(GTK_TREE_VIEW(kd->treeview));
624
625         tpath = gtk_tree_path_new_from_string(path);
626         gtk_tree_model_get_iter(store, &iter, tpath);
627         gtk_tree_path_free(tpath);
628
629         gtk_list_store_set(GTK_LIST_STORE(store), &iter, 0, new_text, -1);
630 }
631
632 static void keyword_dialog_populate(KeywordDlg *kd)
633 {
634         GtkListStore *store;
635         GList *list;
636
637         store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(kd->treeview)));
638         gtk_list_store_clear(store);
639
640         list = history_list_get_by_key("keywords");
641         list = g_list_last(list);
642         while (list)
643                 {
644                 GtkTreeIter iter;
645
646                 gtk_list_store_append(store, &iter);
647                 gtk_list_store_set(store, &iter, 0, list->data,
648                                                  1, TRUE, -1);
649
650                 list = list->prev;
651                 }
652 }
653
654 static void keyword_dialog_show(void)
655 {
656         GtkWidget *scrolled;
657         GtkListStore *store;
658         GtkTreeViewColumn *column;
659         GtkCellRenderer *renderer;
660         GtkWidget *hbox;
661         GtkWidget *button;
662
663         if (keyword_dialog)
664                 {
665                 gtk_window_present(GTK_WINDOW(keyword_dialog->gd->dialog));
666                 return;
667                 }
668
669         keyword_dialog = g_new0(KeywordDlg, 1);
670
671         keyword_dialog->gd = generic_dialog_new(_("Keyword Presets"),
672                                                 GQ_WMCLASS, "keyword_presets", NULL, TRUE,
673                                                 keyword_dialog_cancel_cb, keyword_dialog);
674         generic_dialog_add_message(keyword_dialog->gd, NULL, _("Favorite keywords list"), NULL);
675
676         generic_dialog_add_button(keyword_dialog->gd, GTK_STOCK_OK, NULL,
677                                  keyword_dialog_ok_cb, TRUE);
678
679         scrolled = gtk_scrolled_window_new(NULL, NULL);
680         gtk_widget_set_size_request(scrolled, KEYWORD_DIALOG_WIDTH, KEYWORD_DIALOG_HEIGHT);
681         gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled), GTK_SHADOW_IN);
682         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled),
683                                        GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
684         gtk_box_pack_start(GTK_BOX(keyword_dialog->gd->vbox), scrolled, TRUE, TRUE, 5);
685         gtk_widget_show(scrolled);
686
687         store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_BOOLEAN);
688         keyword_dialog->treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
689         g_object_unref(store);
690
691         gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(keyword_dialog->treeview), FALSE);
692         gtk_tree_view_set_search_column(GTK_TREE_VIEW(keyword_dialog->treeview), 0);
693         gtk_tree_view_set_reorderable(GTK_TREE_VIEW(keyword_dialog->treeview), TRUE);
694
695         column = gtk_tree_view_column_new();
696         gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
697         renderer = gtk_cell_renderer_text_new();
698         g_signal_connect(G_OBJECT(renderer), "edited",
699                          G_CALLBACK(keyword_dialog_edit_cb), keyword_dialog);
700         gtk_tree_view_column_pack_start(column, renderer, TRUE);
701         gtk_tree_view_column_add_attribute(column, renderer, "text", 0);
702         gtk_tree_view_column_add_attribute(column, renderer, "editable", 1);
703         gtk_tree_view_append_column(GTK_TREE_VIEW(keyword_dialog->treeview), column);
704
705         gtk_container_add(GTK_CONTAINER(scrolled), keyword_dialog->treeview);
706         gtk_widget_show(keyword_dialog->treeview);
707
708         hbox = gtk_hbox_new(FALSE, 5);
709         gtk_box_pack_start(GTK_BOX(keyword_dialog->gd->vbox), hbox, FALSE, FALSE, 0);
710         gtk_widget_show(hbox);
711
712         button = gtk_button_new_from_stock(GTK_STOCK_ADD);
713         g_signal_connect(G_OBJECT(button), "clicked",
714                          G_CALLBACK(keyword_dialog_add_cb), keyword_dialog);
715         gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0);
716         gtk_widget_show(button);
717
718         button = gtk_button_new_from_stock(GTK_STOCK_REMOVE);
719         g_signal_connect(G_OBJECT(button), "clicked",
720                          G_CALLBACK(keyword_dialog_remove_cb), keyword_dialog);
721         gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0);
722         gtk_widget_show(button);
723
724         keyword_dialog_populate(keyword_dialog);
725
726         gtk_widget_show(keyword_dialog->gd->dialog);
727 }
728
729
730 static void bar_keyword_edit_cb(GtkWidget *button, gpointer data)
731 {
732         keyword_dialog_show();
733 }
734
735
736 /*
737  *-------------------------------------------------------------------
738  * info bar
739  *-------------------------------------------------------------------
740  */
741
742 typedef enum {
743         BAR_SORT_COPY,
744         BAR_SORT_MOVE,
745         BAR_SORT_LINK
746 } SortActionType;
747
748 enum {
749         KEYWORD_COLUMN_TOGGLE = 0,
750         KEYWORD_COLUMN_TEXT
751 };
752
753 typedef struct _BarInfoData BarInfoData;
754 struct _BarInfoData
755 {
756         GtkWidget *vbox;
757         GtkWidget *group_box;
758         GtkWidget *label_file_name;
759         GtkWidget *label_file_time;
760
761         GtkWidget *keyword_view;
762         GtkWidget *keyword_treeview;
763
764         GtkWidget *comment_view;
765
766         GtkWidget *button_save;
767         GtkWidget *button_set_add;
768         GtkWidget *button_set_replace;
769
770         FileData *fd;
771
772         gint changed;
773         gint save_timeout_id;
774
775         GList *(*list_func)(gpointer);
776         gpointer list_data;
777 };
778
779
780 static GList *bar_list = NULL;
781
782
783 static void bar_info_write(BarInfoData *bd)
784 {
785         GList *list;
786         gchar *comment;
787
788         if (!bd->fd) return;
789
790         list = keyword_list_pull(bd->keyword_view);
791         comment = comment_pull(bd->comment_view);
792
793         comment_write(bd->fd, list, comment);
794
795         string_list_free(list);
796         g_free(comment);
797
798         bd->changed = FALSE;
799         gtk_widget_set_sensitive(bd->button_save, FALSE);
800 }
801
802 static gint bar_info_autosave(gpointer data)
803 {
804         BarInfoData *bd = data;
805
806         bar_info_write(bd);
807
808         bd->save_timeout_id = -1;
809
810         return FALSE;
811 }
812
813 static void bar_info_save_update(BarInfoData *bd, gint enable)
814 {
815         if (bd->save_timeout_id != -1)
816                 {
817                 g_source_remove(bd->save_timeout_id);
818                 bd->save_timeout_id = -1;
819                 }
820         if (enable)
821                 {
822                 bd->save_timeout_id = g_timeout_add(BAR_KEYWORD_AUTOSAVE_TIME, bar_info_autosave, bd);
823                 }
824 }
825
826 static gint bar_keyword_list_find(GList *list, const gchar *keyword)
827 {
828         while (list)
829                 {
830                 gchar *haystack = list->data;
831
832                 if (haystack && keyword && strcmp(haystack, keyword) == 0) return TRUE;
833
834                 list = list->next;
835                 }
836
837         return FALSE;
838 }
839
840 static void bar_keyword_list_sync(BarInfoData *bd, GList *keywords)
841 {
842         GList *list;
843         GtkListStore *store;
844         GtkTreeIter iter;
845
846         list = history_list_get_by_key("keywords");
847         if (!list)
848                 {
849                 /* blank? set up a few example defaults */
850
851                 gint i = 0;
852
853                 while (keyword_favorite_defaults[i] != NULL)
854                         {
855                         history_list_add_to_key("keywords", _(keyword_favorite_defaults[i]), 0);
856                         i++;
857                         }
858
859                 list = history_list_get_by_key("keywords");
860                 }
861
862         store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(bd->keyword_treeview)));
863
864         gtk_list_store_clear(store);
865
866         list = g_list_last(list);
867         while (list)
868                 {
869                 gchar *key = list->data;
870
871                 gtk_list_store_append(store, &iter);
872                 gtk_list_store_set(store, &iter, KEYWORD_COLUMN_TOGGLE, bar_keyword_list_find(keywords, key),
873                                                  KEYWORD_COLUMN_TEXT, key, -1);
874
875                 list = list->prev;
876                 }
877 }
878
879 static void bar_info_keyword_update_all(void)
880 {
881         GList *work;
882
883         work = bar_list;
884         while (work)
885                 {
886                 BarInfoData *bd;
887                 GList *keywords;
888
889                 bd = work->data;
890                 work = work->next;
891
892                 keywords = keyword_list_pull(bd->keyword_view);
893                 bar_keyword_list_sync(bd, keywords);
894                 string_list_free(keywords);
895                 }
896 }
897
898 static void bar_info_update(BarInfoData *bd)
899 {
900         GList *keywords = NULL;
901         gchar *comment = NULL;
902
903         if (bd->label_file_name)
904                 {
905                 gtk_label_set_text(GTK_LABEL(bd->label_file_name), (bd->fd) ? bd->fd->name : "");
906                 }
907         if (bd->label_file_time)
908                 {
909                 gtk_label_set_text(GTK_LABEL(bd->label_file_time), (bd->fd) ? text_from_time(bd->fd->date) : "");
910                 }
911
912         if (comment_read(bd->fd, &keywords, &comment))
913                 {
914                 keyword_list_push(bd->keyword_view, keywords);
915                 gtk_text_buffer_set_text(gtk_text_view_get_buffer(GTK_TEXT_VIEW(bd->comment_view)),
916                                          (comment) ? comment : "", -1);
917
918                 bar_keyword_list_sync(bd, keywords);
919
920                 string_list_free(keywords);
921                 g_free(comment);
922                 }
923         else
924                 {
925                 gtk_text_buffer_set_text(gtk_text_view_get_buffer(GTK_TEXT_VIEW(bd->keyword_view)), "", -1);
926                 gtk_text_buffer_set_text(gtk_text_view_get_buffer(GTK_TEXT_VIEW(bd->comment_view)), "", -1);
927
928                 bar_keyword_list_sync(bd, NULL);
929                 }
930
931         bar_info_save_update(bd, FALSE);
932         bd->changed = FALSE;
933         gtk_widget_set_sensitive(bd->button_save, FALSE);
934
935         gtk_widget_set_sensitive(bd->group_box, (bd->fd != NULL));
936 }
937
938 void bar_info_set(GtkWidget *bar, FileData *fd)
939 {
940         BarInfoData *bd;
941
942         bd = g_object_get_data(G_OBJECT(bar), "bar_info_data");
943         if (!bd) return;
944
945         if (bd->changed) bar_info_write(bd);
946
947         file_data_unref(bd->fd);
948         bd->fd = file_data_ref(fd);
949
950         bar_info_update(bd);
951 }
952
953 void bar_info_maint_renamed(GtkWidget *bar, FileData *fd)
954 {
955         BarInfoData *bd;
956
957         bd = g_object_get_data(G_OBJECT(bar), "bar_info_data");
958         if (!bd) return;
959
960         file_data_unref(bd->fd);
961         bd->fd = file_data_ref(fd);
962
963         if (bd->label_file_name)
964                 {
965                 gtk_label_set_text(GTK_LABEL(bd->label_file_name), (bd->fd) ? bd->fd->name : "");
966                 }
967 }
968
969 gint bar_info_event(GtkWidget *bar, GdkEvent *event)
970 {
971         BarInfoData *bd;
972
973         bd = g_object_get_data(G_OBJECT(bar), "bar_info_data");
974         if (!bd) return FALSE;
975
976         if (GTK_WIDGET_HAS_FOCUS(bd->keyword_view)) return gtk_widget_event(bd->keyword_view, event);
977         if (GTK_WIDGET_HAS_FOCUS(bd->comment_view)) return gtk_widget_event(bd->comment_view, event);
978
979         return FALSE;
980 }
981
982 static void bar_info_keyword_set(BarInfoData *bd, const gchar *keyword, gint active)
983 {
984         GList *list;
985         gint found;
986
987         if (!keyword) return;
988
989         list = keyword_list_pull(bd->keyword_view);
990         found = bar_keyword_list_find(list, keyword);
991
992         if (active != found)
993                 {
994                 if (found)
995                         {
996                         GList *work = list;
997
998                         while (work)
999                                 {
1000                                 gchar *key = work->data;
1001                                 work = work->next;
1002
1003                                 if (key && keyword && strcmp(key, keyword) == 0)
1004                                         {
1005                                         list = g_list_remove(list, key);
1006                                         g_free(key);
1007                                         }
1008                                 }
1009                         }
1010                 else
1011                         {
1012                         list = g_list_append(list, g_strdup(keyword));
1013                         }
1014
1015                 keyword_list_push(bd->keyword_view, list);
1016                 }
1017
1018         string_list_free(list);
1019 }
1020
1021 static void bar_info_keyword_toggle(GtkCellRendererToggle *toggle, const gchar *path, gpointer data)
1022 {
1023         BarInfoData *bd = data;
1024         GtkTreeModel *store;
1025         GtkTreeIter iter;
1026         GtkTreePath *tpath;
1027         gchar *key = NULL;
1028         gboolean active;
1029
1030         store = gtk_tree_view_get_model(GTK_TREE_VIEW(bd->keyword_treeview));
1031
1032         tpath = gtk_tree_path_new_from_string(path);
1033         gtk_tree_model_get_iter(store, &iter, tpath);
1034         gtk_tree_path_free(tpath);
1035
1036         gtk_tree_model_get(store, &iter, KEYWORD_COLUMN_TOGGLE, &active,
1037                                          KEYWORD_COLUMN_TEXT, &key, -1);
1038         active = (!active);
1039         gtk_list_store_set(GTK_LIST_STORE(store), &iter, KEYWORD_COLUMN_TOGGLE, active, -1);
1040
1041         bar_info_keyword_set(bd, key, active);
1042         g_free(key);
1043 }
1044
1045 static void bar_info_save(GtkWidget *button, gpointer data)
1046 {
1047         BarInfoData *bd = data;
1048
1049         bar_info_save_update(bd, FALSE);
1050         bar_info_write(bd);
1051 }
1052
1053 static void bar_info_set_selection(BarInfoData *bd, gint add)
1054 {
1055         GList *keywords;
1056         GList *list = NULL;
1057         GList *work;
1058
1059         if (!bd->list_func) return;
1060
1061         keywords = keyword_list_pull(bd->keyword_view);
1062         if (!keywords && add) return;
1063
1064         list = bd->list_func(bd->list_data);
1065         work = list;
1066         while (work)
1067                 {
1068                 FileData *fd = work->data;
1069                 work = work->next;
1070
1071                 metadata_set_keywords(fd, keywords, add);
1072                 }
1073
1074         filelist_free(list);
1075         string_list_free(keywords);
1076 }
1077
1078 static void bar_info_set_add(GtkWidget *button, gpointer data)
1079 {
1080         BarInfoData *bd = data;
1081
1082         bar_info_set_selection(bd, TRUE);
1083 }
1084
1085 static void bar_info_set_replace(GtkWidget *button, gpointer data)
1086 {
1087         BarInfoData *bd = data;
1088
1089         bar_info_set_selection(bd, FALSE);
1090 }
1091
1092 static void bar_info_changed(GtkTextBuffer *buffer, gpointer data)
1093 {
1094         BarInfoData *bd = data;
1095
1096         bd->changed = TRUE;
1097         gtk_widget_set_sensitive(bd->button_save, TRUE);
1098
1099         bar_info_save_update(bd, TRUE);
1100 }
1101
1102 void bar_info_close(GtkWidget *bar)
1103 {
1104         BarInfoData *bd;
1105
1106         bd = g_object_get_data(G_OBJECT(bar), "bar_info_data");
1107         if (!bd) return;
1108
1109         gtk_widget_destroy(bd->vbox);
1110 }
1111
1112 static void bar_info_destroy(GtkWidget *widget, gpointer data)
1113 {
1114         BarInfoData *bd = data;
1115
1116         if (bd->changed) bar_info_write(bd);
1117         bar_info_save_update(bd, FALSE);
1118
1119         bar_list = g_list_remove(bar_list, bd);
1120
1121         file_data_unref(bd->fd);
1122
1123         g_free(bd);
1124 }
1125
1126 GtkWidget *bar_info_new(FileData *fd, gint metadata_only, GtkWidget *bounding_widget)
1127 {
1128         BarInfoData *bd;
1129         GtkWidget *box;
1130         GtkWidget *hbox;
1131         GtkWidget *table;
1132         GtkWidget *scrolled;
1133         GtkTextBuffer *buffer;
1134         GtkWidget *label;
1135         GtkWidget *tbar;
1136         GtkListStore *store;
1137         GtkTreeViewColumn *column;
1138         GtkCellRenderer *renderer;
1139
1140         bd = g_new0(BarInfoData, 1);
1141
1142         bd->list_func = NULL;
1143         bd->list_data = NULL;
1144
1145         bd->vbox = gtk_vbox_new(FALSE, PREF_PAD_GAP);
1146         g_object_set_data(G_OBJECT(bd->vbox), "bar_info_data", bd);
1147         g_signal_connect(G_OBJECT(bd->vbox), "destroy",
1148                          G_CALLBACK(bar_info_destroy), bd);
1149
1150         if (!metadata_only)
1151                 {
1152                 hbox = pref_box_new(bd->vbox, FALSE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_GAP);
1153
1154                 label = sizer_new(bd->vbox, bounding_widget, SIZER_POS_LEFT);
1155                 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
1156                 gtk_widget_show(label);
1157
1158                 label = gtk_label_new(_("Keywords"));
1159                 pref_label_bold(label, TRUE, FALSE);
1160                 gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0);
1161                 gtk_widget_show(label);
1162                 }
1163
1164         bd->group_box = pref_box_new(bd->vbox, TRUE, GTK_ORIENTATION_VERTICAL, PREF_PAD_GAP);
1165
1166         if (!metadata_only)
1167                 {
1168                 GtkWidget *table;
1169
1170                 table = pref_table_new(bd->group_box, 2, 2, FALSE, FALSE);
1171
1172                 bd->label_file_name = table_add_line(table, 0, 0, _("Filename:"), NULL);
1173                 bd->label_file_time = table_add_line(table, 0, 1, _("File date:"), NULL);
1174                 }
1175         else
1176                 {
1177                 bd->label_file_name = NULL;
1178                 bd->label_file_time = NULL;
1179                 }
1180
1181         table = gtk_table_new(3, 1, TRUE);
1182         gtk_table_set_row_spacings(GTK_TABLE(table), PREF_PAD_GAP);
1183         gtk_box_pack_start(GTK_BOX(bd->group_box), table, TRUE, TRUE, 0);
1184         gtk_widget_show(table);
1185
1186         /* keyword entry */
1187
1188         box = gtk_vbox_new(FALSE, 0);
1189         gtk_table_attach(GTK_TABLE(table), box, 0, 1, 0, 2,
1190                          GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
1191         gtk_widget_show(box);
1192
1193         label = pref_label_new(box, _("Keywords:"));
1194         gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
1195         pref_label_bold(label, TRUE, FALSE);
1196
1197         hbox = pref_box_new(box, TRUE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_GAP);
1198
1199         scrolled = gtk_scrolled_window_new(NULL, NULL);
1200         gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled), GTK_SHADOW_IN);
1201         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled),
1202                                        GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1203         gtk_box_pack_start(GTK_BOX(hbox), scrolled, TRUE, TRUE, 0);
1204         gtk_widget_show(scrolled);
1205
1206         bd->keyword_view = gtk_text_view_new();
1207         gtk_container_add(GTK_CONTAINER(scrolled), bd->keyword_view);
1208         gtk_widget_show(bd->keyword_view);
1209
1210         buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(bd->keyword_view));
1211         g_signal_connect(G_OBJECT(buffer), "changed",
1212                          G_CALLBACK(bar_info_changed), bd);
1213
1214         scrolled = gtk_scrolled_window_new(NULL, NULL);
1215         gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled), GTK_SHADOW_IN);
1216         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled),
1217                                        GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1218         gtk_box_pack_start(GTK_BOX(hbox), scrolled, TRUE, TRUE, 0);
1219         gtk_widget_show(scrolled);
1220
1221         store = gtk_list_store_new(2, G_TYPE_BOOLEAN, G_TYPE_STRING);
1222         bd->keyword_treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
1223         g_object_unref(store);
1224
1225         gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(bd->keyword_treeview), FALSE);
1226
1227         if (metadata_only)
1228                 {
1229                 gtk_tree_view_set_search_column(GTK_TREE_VIEW(bd->keyword_treeview), KEYWORD_COLUMN_TEXT);
1230                 }
1231         else
1232                 {
1233                 gtk_tree_view_set_enable_search(GTK_TREE_VIEW(bd->keyword_treeview), FALSE);
1234                 }
1235
1236         column = gtk_tree_view_column_new();
1237         gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
1238
1239         renderer = gtk_cell_renderer_toggle_new();
1240         gtk_tree_view_column_pack_start(column, renderer, FALSE);
1241         gtk_tree_view_column_add_attribute(column, renderer, "active", KEYWORD_COLUMN_TOGGLE);
1242         g_signal_connect(G_OBJECT(renderer), "toggled",
1243                          G_CALLBACK(bar_info_keyword_toggle), bd);
1244
1245         renderer = gtk_cell_renderer_text_new();
1246         gtk_tree_view_column_pack_start(column, renderer, TRUE);
1247         gtk_tree_view_column_add_attribute(column, renderer, "text", KEYWORD_COLUMN_TEXT);
1248
1249         gtk_tree_view_append_column(GTK_TREE_VIEW(bd->keyword_treeview), column);
1250
1251         gtk_container_add(GTK_CONTAINER(scrolled), bd->keyword_treeview);
1252         gtk_widget_show(bd->keyword_treeview);
1253
1254         /* comment entry */
1255
1256         box = gtk_vbox_new(FALSE, 0);
1257         gtk_table_attach(GTK_TABLE(table), box, 0, 1, 2, 3,
1258                          GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
1259         gtk_widget_show(box);
1260
1261         label = pref_label_new(box, _("Comment:"));
1262         gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
1263         pref_label_bold(label, TRUE, FALSE);
1264
1265         scrolled = gtk_scrolled_window_new(NULL, NULL);
1266         gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled), GTK_SHADOW_IN);
1267         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled),
1268                                        GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1269         gtk_box_pack_start(GTK_BOX(box), scrolled, TRUE, TRUE, 0);
1270         gtk_widget_show(scrolled);
1271
1272         bd->comment_view = gtk_text_view_new();
1273         gtk_container_add(GTK_CONTAINER(scrolled), bd->comment_view);
1274         gtk_widget_show(bd->comment_view);
1275
1276         buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(bd->comment_view));
1277         g_signal_connect(G_OBJECT(buffer), "changed",
1278                          G_CALLBACK(bar_info_changed), bd);
1279
1280         /* toolbar */
1281
1282         tbar = pref_toolbar_new(bd->group_box, GTK_TOOLBAR_ICONS);
1283
1284         pref_toolbar_button(tbar, GTK_STOCK_INDEX, NULL, FALSE,
1285                         _("Edit favorite keywords list."),
1286                         G_CALLBACK(bar_keyword_edit_cb), bd);
1287         pref_toolbar_spacer(tbar);
1288         bd->button_set_add = pref_toolbar_button(tbar, GTK_STOCK_ADD, NULL, FALSE,
1289                         _("Add keywords to selected files"),
1290                         G_CALLBACK(bar_info_set_add), bd);
1291         bd->button_set_replace = pref_toolbar_button(tbar, GTK_STOCK_CONVERT, NULL, FALSE,
1292                         _("Add keywords to selected files, replacing the existing ones."),
1293                         G_CALLBACK(bar_info_set_replace), bd);
1294         pref_toolbar_spacer(tbar);
1295         bd->button_save = pref_toolbar_button(tbar, GTK_STOCK_SAVE, NULL, FALSE,
1296                         _("Save comment now"),
1297                         G_CALLBACK(bar_info_save), bd);
1298
1299         bd->save_timeout_id = -1;
1300
1301         bd->fd = file_data_ref(fd);
1302         bar_info_update(bd);
1303
1304         bar_info_selection(bd->vbox, 0);
1305
1306         bar_list = g_list_append(bar_list, bd);
1307         
1308         return bd->vbox;
1309 }
1310
1311 void bar_info_set_selection_func(GtkWidget *bar, GList *(*list_func)(gpointer data), gpointer data)
1312 {
1313         BarInfoData *bd;
1314
1315         bd = g_object_get_data(G_OBJECT(bar), "bar_info_data");
1316         if (!bd) return;
1317
1318         bd->list_func = list_func;
1319         bd->list_data = data;
1320 }
1321
1322 void bar_info_selection(GtkWidget *bar, gint count)
1323 {
1324         BarInfoData *bd;
1325         gint enable;
1326
1327         bd = g_object_get_data(G_OBJECT(bar), "bar_info_data");
1328         if (!bd) return;
1329
1330         enable = (count > 0 && bd->list_func != NULL);
1331
1332         gtk_widget_set_sensitive(bd->button_set_add, enable);
1333         gtk_widget_set_sensitive(bd->button_set_replace, enable);
1334 }
1335
1336 void bar_info_size_request(GtkWidget *bar, gint width)
1337 {
1338         BarInfoData *bd;
1339
1340         bd = g_object_get_data(G_OBJECT(bar), "bar_info_data");
1341         if (!bd) return;
1342
1343         if (bd->label_file_name)
1344                 {
1345                 gtk_widget_set_size_request(bd->vbox, width, -1);
1346                 }
1347 }
1348