No issues found
1 /*
2 * Copyright (C) 2009, Debarshi Ray <debarshir@src.gnome.org>
3 * Copyright (C) 2009, Nokia <ivan.frade@nokia.com>
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version 2
8 * of the License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18 * 02110-1301, USA.
19 */
20
21 #include "config.h"
22
23 #include <string.h>
24
25 #include <glib/gi18n.h>
26
27 #include <libnautilus-extension/nautilus-file-info.h>
28
29 #include <libtracker-sparql/tracker-sparql.h>
30
31 #include "tracker-tags-utils.h"
32 #include "tracker-tags-view.h"
33
34 #define TRACKER_TAGS_VIEW_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), TRACKER_TYPE_TAGS_VIEW, TrackerTagsViewPrivate))
35
36 struct _TrackerTagsViewPrivate {
37 TrackerSparqlConnection *connection;
38 GCancellable *cancellable;
39
40 GList *tag_data_requests;
41
42 GList *files;
43
44 GtkListStore *store;
45
46 GtkWidget *button_add;
47 GtkWidget *button_remove;
48 GtkWidget *entry;
49 GtkWidget *view;
50 };
51
52 typedef struct {
53 TrackerTagsView *tv;
54 GCancellable *cancellable;
55 gchar *tag_id;
56 GtkTreeIter *iter;
57 gint items;
58 gboolean update;
59 gboolean selected;
60 } TagData;
61
62 typedef struct {
63 TrackerTagsView *tv;
64 const gchar *tag;
65 gboolean found;
66 GtkTreeIter found_iter;
67 } FindTag;
68
69 enum {
70 COL_SELECTION,
71 COL_TAG_ID,
72 COL_TAG_NAME,
73 COL_TAG_COUNT,
74 COL_TAG_COUNT_VALUE,
75 N_COLUMNS
76 };
77
78 enum {
79 SELECTION_INCONSISTENT = -1,
80 SELECTION_FALSE = 0,
81 SELECTION_TRUE
82 };
83
84 static void tracker_tags_view_finalize (GObject *object);
85 static void tracker_tags_view_register_type (GTypeModule *module);
86 static void tags_view_create_ui (TrackerTagsView *tv);
87 static void tag_data_free (TagData *td);
88
89 G_DEFINE_DYNAMIC_TYPE (TrackerTagsView, tracker_tags_view, GTK_TYPE_VBOX)
90
91 static void
92 tracker_tags_view_class_init (TrackerTagsViewClass *klass)
93 {
94 GObjectClass *object_class = G_OBJECT_CLASS (klass);
95
96 object_class->finalize = tracker_tags_view_finalize;
97
98 g_type_class_add_private (klass, sizeof (TrackerTagsViewPrivate));
99 }
100
101 static void
102 tracker_tags_view_class_finalize (TrackerTagsViewClass *klass)
103 {
104 }
105
106 static void
107 tracker_tags_view_init (TrackerTagsView *tv)
108 {
109 TrackerTagsViewPrivate *private;
110 GError *error = NULL;
111
112 private = TRACKER_TAGS_VIEW_GET_PRIVATE (tv);
113
114 private->cancellable = g_cancellable_new ();
115 private->connection = tracker_sparql_connection_get (private->cancellable,
116 &error);
117 if (!private->connection) {
118 g_critical ("Couldn't get a proper SPARQL connection: '%s'",
119 error ? error->message : "unknown error");
120 g_clear_error (&error);
121 }
122
123 private->files = NULL;
124
125 private->store = gtk_list_store_new (N_COLUMNS,
126 G_TYPE_INT, /* Selection type */
127 G_TYPE_STRING, /* Tag ID */
128 G_TYPE_STRING, /* Tag Name */
129 G_TYPE_STRING, /* Tag Count String */
130 G_TYPE_INT); /* Tag Count */
131 }
132
133 static void
134 tracker_tags_view_finalize (GObject *object)
135 {
136 TrackerTagsViewPrivate *private = TRACKER_TAGS_VIEW_GET_PRIVATE (object);
137
138 if (private->cancellable) {
139 g_cancellable_cancel (private->cancellable);
140 g_object_unref (private->cancellable);
141 private->cancellable = NULL;
142 }
143
144 if (private->connection) {
145 g_object_unref (private->connection);
146 private->connection = NULL;
147 }
148
149 if (private->files) {
150 nautilus_file_info_list_free (private->files);
151 private->files = NULL;
152 }
153
154 if (private->tag_data_requests) {
155 g_list_foreach (private->tag_data_requests, (GFunc) tag_data_free, NULL);
156 g_list_free (private->tag_data_requests);
157 private->tag_data_requests = NULL;
158 }
159
160 G_OBJECT_CLASS (tracker_tags_view_parent_class)->finalize (object);
161 }
162
163 static TagData *
164 tag_data_new (const gchar *tag_id,
165 GtkTreeIter *iter,
166 gboolean update,
167 gboolean selected,
168 gint items,
169 TrackerTagsView *tv)
170 {
171 TagData *td;
172
173 td = g_slice_new (TagData);
174
175 g_debug ("Creating tag data");
176
177 td->tv = tv;
178 td->cancellable = g_cancellable_new ();
179 td->tag_id = g_strdup (tag_id);
180
181 if (iter) {
182 td->iter = gtk_tree_iter_copy (iter);
183 } else {
184 td->iter = NULL;
185 }
186
187 td->items = items;
188 td->update = update;
189 td->selected = selected;
190
191 return td;
192 }
193
194 static void
195 tag_data_free (TagData *td)
196 {
197 if (td->cancellable) {
198 g_cancellable_cancel (td->cancellable);
199 g_object_unref (td->cancellable);
200 }
201
202 g_free (td->tag_id);
203
204 if (td->iter) {
205 gtk_tree_iter_free (td->iter);
206 }
207
208 g_slice_free (TagData, td);
209 }
210
211 static TagData *
212 tag_data_copy (TagData *td)
213 {
214 TagData *new_td;
215
216 new_td = g_slice_new (TagData);
217
218 new_td->tv = td->tv;
219 new_td->cancellable = g_cancellable_new ();
220 new_td->tag_id = g_strdup (td->tag_id);
221
222 if (td->iter) {
223 new_td->iter = gtk_tree_iter_copy (td->iter);
224 } else {
225 new_td->iter = NULL;
226 }
227
228 new_td->items = td->items;
229 new_td->update = td->update;
230 new_td->selected = td->selected;
231
232 return new_td;
233 }
234
235 static void
236 show_error_dialog (GError *error)
237 {
238 GtkWidget *dialog;
239 const gchar *str;
240
241 str = error->message ? error->message : _("No error was given");
242
243 dialog = gtk_message_dialog_new (NULL,
244 0,
245 GTK_MESSAGE_ERROR,
246 GTK_BUTTONS_OK,
247 "%s",
248 str);
249 g_signal_connect (dialog, "response",
250 G_CALLBACK (gtk_widget_destroy), NULL);
251 gtk_dialog_run (GTK_DIALOG (dialog));
252 }
253
254 static gboolean
255 tag_view_model_find_tag_foreach (GtkTreeModel *model,
256 GtkTreePath *path,
257 GtkTreeIter *iter,
258 FindTag *data)
259 {
260 gchar *tag;
261
262 gtk_tree_model_get (model, iter,
263 COL_TAG_NAME, &tag,
264 -1);
265
266 if (!tag) {
267 return FALSE;
268 }
269
270 if (data->tag && strcmp (data->tag, tag) == 0) {
271 data->found = TRUE;
272 data->found_iter = *iter;
273
274 g_free (tag);
275
276 return TRUE;
277 }
278
279 g_free (tag);
280
281 return FALSE;
282 }
283
284 static gboolean
285 tag_view_model_find_tag (TrackerTagsView *tv,
286 const gchar *tag,
287 GtkTreeIter *iter)
288 {
289 TrackerTagsViewPrivate *private;
290 GtkTreeView *view;
291 GtkTreeModel *model;
292 FindTag data;
293
294 if (tracker_is_empty_string (tag)) {
295 return FALSE;
296 }
297
298 data.tv = tv;
299 data.tag = tag;
300 data.found = FALSE;
301
302 private = TRACKER_TAGS_VIEW_GET_PRIVATE (tv);
303
304 view = GTK_TREE_VIEW (private->view);
305 model = gtk_tree_view_get_model (view);
306
307 gtk_tree_model_foreach (model,
308 (GtkTreeModelForeachFunc) tag_view_model_find_tag_foreach,
309 &data);
310
311 if (data.found == TRUE) {
312 *iter = data.found_iter;
313 return TRUE;
314 }
315
316 return FALSE;
317 }
318
319 static void
320 tags_view_tag_removed_cb (GObject *source_object,
321 GAsyncResult *res,
322 gpointer user_data)
323 {
324 TagData *td;
325 TrackerTagsViewPrivate *private;
326 GError *error = NULL;
327
328 g_debug ("Update callback");
329
330 td = user_data;
331 private = TRACKER_TAGS_VIEW_GET_PRIVATE (td->tv);
332
333 tracker_sparql_connection_update_finish (TRACKER_SPARQL_CONNECTION (source_object),
334 res,
335 &error);
336
337 if (error) {
338 show_error_dialog (error);
339 g_error_free (error);
340 } else {
341 g_message ("Tag removed (id:'%s') from store", td->tag_id);
342
343 gtk_list_store_remove (private->store, td->iter);
344 }
345
346 private->tag_data_requests = g_list_remove (private->tag_data_requests, td);
347 tag_data_free (td);
348 }
349
350 static void
351 tags_view_query_files_for_tag_id_cb (GObject *source_object,
352 GAsyncResult *res,
353 gpointer user_data)
354 {
355 TagData *td;
356 TrackerTagsViewPrivate *private;
357 TrackerSparqlCursor *cursor;
358 GtkTreeIter *iter;
359 GError *error = NULL;
360 gchar *str;
361 guint files_selected, files_with_tag, has_tag_in_selection;
362
363 td = user_data;
364 private = TRACKER_TAGS_VIEW_GET_PRIVATE (td->tv);
365 iter = td->iter;
366
367 cursor = tracker_sparql_connection_query_finish (TRACKER_SPARQL_CONNECTION (source_object),
368 res,
369 &error);
370
371 if (error) {
372 show_error_dialog (error);
373 g_error_free (error);
374
375 private->tag_data_requests = g_list_remove (private->tag_data_requests, td);
376 tag_data_free (td);
377
378 if (cursor) {
379 g_object_unref (cursor);
380 }
381
382 return;
383 }
384
385 has_tag_in_selection = 0;
386 files_with_tag = 0;
387 files_selected = g_list_length (private->files);
388
389 /* FIXME: make this async */
390 while (tracker_sparql_cursor_next (cursor,
391 private->cancellable,
392 &error)) {
393 GList *l;
394 gboolean equal;
395
396 files_with_tag++;
397
398 for (l = private->files, equal = FALSE; l && !equal; l = l->next) {
399 gchar *uri;
400 const gchar *str;
401
402 uri = nautilus_file_info_get_uri (NAUTILUS_FILE_INFO (l->data));
403
404 str = tracker_sparql_cursor_get_string (cursor, 0, NULL);
405 equal = g_strcmp0 (str, uri) == 0;
406
407 if (equal) {
408 has_tag_in_selection++;
409 }
410
411 g_free (uri);
412 }
413 }
414
415 if (cursor) {
416 g_object_unref (cursor);
417 }
418
419 if (error) {
420 show_error_dialog (error);
421
422 g_error_free (error);
423
424 return;
425 }
426
427 g_debug ("Querying files with tag, in selection:%d, in total:%d, selected:%d",
428 has_tag_in_selection, files_with_tag, files_selected);
429
430 if (has_tag_in_selection == 0) {
431 gtk_list_store_set (private->store, iter,
432 COL_SELECTION, SELECTION_FALSE,
433 -1);
434 } else if (files_selected != has_tag_in_selection) {
435 gtk_list_store_set (private->store, iter,
436 COL_SELECTION, SELECTION_INCONSISTENT,
437 -1);
438 } else {
439 gtk_list_store_set (private->store, iter,
440 COL_SELECTION, SELECTION_TRUE,
441 -1);
442 }
443
444 str = g_strdup_printf ("%d", files_with_tag);
445 gtk_list_store_set (private->store, iter,
446 COL_TAG_COUNT, str,
447 COL_TAG_COUNT_VALUE, files_with_tag,
448 -1);
449 g_free (str);
450
451 private->tag_data_requests = g_list_remove (private->tag_data_requests, td);
452 tag_data_free (td);
453 }
454
455 static void
456 tags_view_query_files_for_tag_id (TagData *td)
457 {
458 TrackerTagsViewPrivate *private;
459 gchar *query;
460
461 private = TRACKER_TAGS_VIEW_GET_PRIVATE (td->tv);
462
463 if (!private->connection) {
464 g_warning ("Can't query files for tag id '%s', "
465 "no SPARQL connection available",
466 td->tag_id);
467 return;
468 }
469
470 query = g_strdup_printf ("SELECT ?url "
471 "WHERE {"
472 " ?urn a rdfs:Resource ;"
473 " nie:url ?url ;"
474 " nao:hasTag <%s> . "
475 "}", td->tag_id);
476
477 tracker_sparql_connection_query_async (private->connection,
478 query,
479 td->cancellable,
480 tags_view_query_files_for_tag_id_cb,
481 td);
482 g_free (query);
483 }
484
485 static void
486 tags_view_add_tags_cb (GObject *source_object,
487 GAsyncResult *res,
488 gpointer user_data)
489 {
490 TrackerTagsView *tv;
491 TrackerTagsViewPrivate *private;
492 TrackerSparqlCursor *cursor;
493 GError *error = NULL;
494
495 g_debug ("Clearing tags in store");
496
497 tv = user_data;
498 private = TRACKER_TAGS_VIEW_GET_PRIVATE (tv);
499
500 cursor = tracker_sparql_connection_query_finish (TRACKER_SPARQL_CONNECTION (source_object),
501 res,
502 &error);
503
504 gtk_list_store_clear (private->store);
505
506 if (error) {
507 show_error_dialog (error);
508 g_error_free (error);
509
510 if (cursor) {
511 g_object_unref (cursor);
512 }
513 } else {
514 g_message ("Adding all tags...");
515
516 /* FIXME: make async */
517 while (tracker_sparql_cursor_next (cursor, private->cancellable, NULL)) {
518 TagData *td;
519 GtkTreeIter iter;
520 const gchar *id, *label;
521
522 id = tracker_sparql_cursor_get_string (cursor, 0, NULL);
523 label = tracker_sparql_cursor_get_string (cursor, 1, NULL);
524
525 g_message ("Tag added (id:'%s' with label:'%s') to store", id, label);
526
527 gtk_list_store_append (private->store, &iter);
528 gtk_list_store_set (private->store, &iter,
529 COL_TAG_ID, id,
530 COL_TAG_NAME, label,
531 COL_SELECTION, SELECTION_FALSE,
532 -1);
533
534 td = tag_data_new (id, &iter, FALSE, TRUE, 1, tv);
535 private->tag_data_requests =
536 g_list_prepend (private->tag_data_requests, td);
537
538 tags_view_query_files_for_tag_id (td);
539 }
540
541 if (cursor) {
542 g_object_unref (cursor);
543 }
544
545 if (error) {
546 show_error_dialog (error);
547 g_error_free (error);
548 }
549 }
550 }
551
552 static void
553 tags_view_model_update_cb (GObject *source_object,
554 GAsyncResult *res,
555 gpointer user_data)
556 {
557 TagData *td = user_data;
558 TrackerTagsView *tv = td->tv;
559 TrackerTagsViewPrivate *private;
560 GError *error = NULL;
561
562 g_debug ("Update callback");
563
564 private = TRACKER_TAGS_VIEW_GET_PRIVATE (tv);
565
566 tracker_sparql_connection_update_finish (TRACKER_SPARQL_CONNECTION (source_object),
567 res,
568 &error);
569
570 if (error) {
571 show_error_dialog (error);
572 g_error_free (error);
573 } else {
574 const gchar *tag;
575
576 tag = gtk_entry_get_text (GTK_ENTRY (private->entry));
577
578 if (!td->update) {
579 GtkTreeIter iter;
580 gchar *str;
581
582 g_debug ("Setting tag selection state to ON (new)");
583
584 str = g_strdup_printf ("%d", td->items);
585 gtk_list_store_append (private->store, &iter);
586 gtk_list_store_set (private->store, &iter,
587 COL_TAG_ID, td->tag_id,
588 COL_TAG_NAME, tag,
589 COL_TAG_COUNT, str,
590 COL_TAG_COUNT_VALUE, td->items,
591 COL_SELECTION, SELECTION_TRUE,
592 -1);
593 g_free (str);
594 } else if (td->selected) {
595 TagData *td_copy;
596
597 g_debug ("Setting tag selection state to ON");
598
599 gtk_list_store_set (private->store, td->iter,
600 COL_SELECTION, SELECTION_TRUE,
601 -1);
602
603 td_copy = tag_data_copy (td);
604 private->tag_data_requests =
605 g_list_prepend (private->tag_data_requests, td_copy);
606
607 tags_view_query_files_for_tag_id (td_copy);
608 } else {
609 TagData *td_copy;
610
611 g_debug ("Setting tag selection state to FALSE");
612
613 gtk_list_store_set (private->store, td->iter,
614 COL_SELECTION, SELECTION_FALSE,
615 -1);
616
617 td_copy = tag_data_copy (td);
618 private->tag_data_requests =
619 g_list_prepend (private->tag_data_requests, td_copy);
620
621 tags_view_query_files_for_tag_id (td_copy);
622 }
623 }
624
625 gtk_entry_set_text (GTK_ENTRY (private->entry), "");
626 gtk_widget_set_sensitive (private->entry, TRUE);
627
628 private->tag_data_requests =
629 g_list_remove (private->tag_data_requests, td);
630 tag_data_free (td);
631 }
632
633 static void
634 tags_view_add_tag (TrackerTagsView *tv,
635 const gchar *tag)
636 {
637 TrackerTagsViewPrivate *private;
638 TagData *td;
639 GString *query;
640 gint files;
641
642 private = TRACKER_TAGS_VIEW_GET_PRIVATE (tv);
643
644 if (!private->connection) {
645 g_warning ("Can't add tag '%s', "
646 "no SPARQL connection available",
647 tag);
648 return;
649 }
650
651 gtk_widget_set_sensitive (private->entry, FALSE);
652
653 files = g_list_length (private->files);
654
655 if (files > 0) {
656 GStrv files;
657 gchar *tag_escaped;
658 gchar *filter;
659 guint i;
660
661 query = g_string_new ("");
662
663 files = tracker_glist_to_string_list_for_nautilus_files (private->files);
664 filter = tracker_tags_get_filter_string (files, NULL);
665 tag_escaped = tracker_tags_escape_sparql_string (tag);
666
667 for (i = 0; files[i] != NULL; i++) {
668 g_string_append_printf (query,
669 "INSERT { _:file a nie:DataObject ; nie:url '%s' } "
670 "WHERE { "
671 " OPTIONAL {"
672 " ?file a nie:DataObject ;"
673 " nie:url '%s'"
674 " } ."
675 " FILTER (!bound(?file)) "
676 "} ",
677 files[i], files[i]);
678 }
679
680 g_string_append_printf (query,
681 "INSERT { "
682 " _:tag a nao:Tag;"
683 " nao:prefLabel %s . "
684 "} "
685 "WHERE {"
686 " OPTIONAL {"
687 " ?tag a nao:Tag ;"
688 " nao:prefLabel %s"
689 " } ."
690 " FILTER (!bound(?tag)) "
691 "} "
692 "INSERT { "
693 " ?urn nao:hasTag ?label "
694 "} "
695 "WHERE {"
696 " ?urn nie:url ?f ."
697 " ?label nao:prefLabel %s "
698 " %s "
699 "}",
700 tag_escaped,
701 tag_escaped,
702 tag_escaped,
703 filter);
704
705 g_free (tag_escaped);
706 g_free (filter);
707 g_strfreev (files);
708 } else {
709 query = g_string_new (tracker_tags_add_query (tag));
710 }
711
712 td = tag_data_new (NULL, NULL, FALSE, TRUE, files, tv);
713 private->tag_data_requests =
714 g_list_prepend (private->tag_data_requests, td);
715
716 tracker_sparql_connection_update_async (private->connection,
717 query->str,
718 G_PRIORITY_DEFAULT,
719 td->cancellable,
720 tags_view_model_update_cb,
721 td);
722
723 g_string_free (query, TRUE);
724 }
725
726 static void
727 tags_view_model_toggle_cell_data_func (GtkTreeViewColumn *column,
728 GtkCellRenderer *cell_renderer,
729 GtkTreeModel *tree_model,
730 GtkTreeIter *iter,
731 gpointer user_data)
732 {
733 GValue inconsistent = { 0 };
734 gint selection;
735
736 gtk_tree_model_get (tree_model, iter, COL_SELECTION, &selection, -1);
737 gtk_cell_renderer_toggle_set_active (GTK_CELL_RENDERER_TOGGLE (cell_renderer),
738 SELECTION_TRUE == selection);
739
740 g_value_init (&inconsistent, G_TYPE_BOOLEAN);
741 g_value_set_boolean (&inconsistent, SELECTION_INCONSISTENT == selection);
742 g_object_set_property (G_OBJECT (cell_renderer), "inconsistent", &inconsistent);
743 }
744
745 static void
746 tags_view_model_toggle_row (TrackerTagsView *tv,
747 GtkTreePath *path)
748 {
749 TrackerTagsViewPrivate *private;
750 TagData *td;
751 GStrv files;
752 GtkTreeIter iter;
753 GtkTreeModel *model;
754 gchar *filter, *query;
755 gchar *id, *tag, *tag_escaped;
756 gint selection;
757
758 private = TRACKER_TAGS_VIEW_GET_PRIVATE (tv);
759
760 model = gtk_tree_view_get_model (GTK_TREE_VIEW (private->view));
761
762 if (gtk_tree_model_get_iter (model, &iter, path) == FALSE) {
763 return;
764 }
765
766 gtk_tree_model_get (model, &iter,
767 COL_SELECTION, &selection,
768 COL_TAG_ID, &id,
769 COL_TAG_NAME, &tag,
770 -1);
771
772 selection = selection == SELECTION_FALSE ? SELECTION_TRUE : SELECTION_FALSE;
773
774 tag_escaped = tracker_tags_escape_sparql_string (tag);
775 g_free (tag);
776
777 files = tracker_glist_to_string_list_for_nautilus_files (private->files);
778 filter = tracker_tags_get_filter_string (files, NULL);
779 g_strfreev (files);
780
781 if (selection) {
782 query = g_strdup_printf ("INSERT { "
783 " ?urn nao:hasTag ?label "
784 "} "
785 "WHERE {"
786 " ?urn nie:url ?f ." /* NB: ?f is used in filter. */
787 " ?label nao:prefLabel %s ."
788 " %s "
789 "}",
790 tag_escaped,
791 filter);
792 } else {
793 TagData *td;
794
795 query = g_strdup_printf ("DELETE { "
796 " ?urn nao:hasTag ?label "
797 "} "
798 "WHERE { "
799 " ?urn nie:url ?f ." /* NB: ?f is used in filter. */
800 " ?label nao:prefLabel %s ."
801 " %s "
802 "}",
803 tag_escaped,
804 filter);
805
806 /* Check if there are any files left with this tag and
807 * remove tag if not.
808 */
809 td = tag_data_new (id, &iter, FALSE, TRUE, 1, tv);
810 private->tag_data_requests =
811 g_list_prepend (private->tag_data_requests, td);
812
813 tags_view_query_files_for_tag_id (td);
814 }
815
816 g_free (filter);
817 g_free (tag_escaped);
818
819 gtk_widget_set_sensitive (private->entry, FALSE);
820
821 if (!private->connection) {
822 g_warning ("Can't update tags, "
823 "no SPARQL connection available");
824 g_free (id);
825 g_free (query);
826 return;
827 }
828
829 g_debug ("Running query:'%s'", query);
830
831 td = tag_data_new (id, &iter, TRUE, selection, 1, tv);
832 private->tag_data_requests =
833 g_list_prepend (private->tag_data_requests, td);
834
835 tracker_sparql_connection_update_async (private->connection,
836 query,
837 G_PRIORITY_DEFAULT,
838 td->cancellable,
839 tags_view_model_update_cb,
840 td);
841
842 g_free (id);
843 g_free (query);
844 }
845
846 static void
847 tags_view_model_cell_toggled_cb (GtkCellRendererToggle *cell,
848 gchar *path_string,
849 TrackerTagsView *tv)
850 {
851 GtkTreePath *path;
852
853 path = gtk_tree_path_new_from_string (path_string);
854 tags_view_model_toggle_row (tv, path);
855 gtk_tree_path_free (path);
856 }
857
858 static void
859 tags_view_model_row_activated_cb (GtkTreeView *view,
860 GtkTreePath *path,
861 GtkTreeViewColumn *column,
862 gpointer user_data)
863 {
864 tags_view_model_toggle_row (user_data, path);
865 }
866
867 static void
868 tags_view_entry_changed_cb (GtkEditable *editable,
869 TrackerTagsView *tv)
870 {
871 TrackerTagsViewPrivate *private;
872 GtkTreeIter iter;
873 const gchar *tag;
874
875 private = TRACKER_TAGS_VIEW_GET_PRIVATE (tv);
876
877 tag = gtk_entry_get_text (GTK_ENTRY (private->entry));
878
879 if (tag_view_model_find_tag (tv, tag, &iter)) {
880 gtk_widget_set_sensitive (GTK_WIDGET (private->button_add), FALSE);
881 } else {
882 gtk_widget_set_sensitive (GTK_WIDGET (private->button_add),
883 !tracker_is_empty_string (tag));
884 }
885 }
886
887 static void
888 tags_view_entry_activate_cb (GtkEditable *editable,
889 TrackerTagsView *tv)
890 {
891 TrackerTagsViewPrivate *private;
892
893 private = TRACKER_TAGS_VIEW_GET_PRIVATE (tv);
894
895 gtk_widget_activate (private->button_add);
896 }
897
898 static void
899 tags_view_add_clicked_cb (GtkButton *button,
900 gpointer user_data)
901 {
902 TrackerTagsView *tv;
903 TrackerTagsViewPrivate *private;
904 const gchar *tag;
905
906 tv = user_data;
907 private = TRACKER_TAGS_VIEW_GET_PRIVATE (tv);
908
909 tag = gtk_entry_get_text (GTK_ENTRY (private->entry));
910 tags_view_add_tag (tv, tag);
911 }
912
913 static void
914 tags_view_remove_tag (TrackerTagsView *tv,
915 TagData *td)
916 {
917 TrackerTagsViewPrivate *private;
918 TagData *td_copy;
919 gchar *query;
920
921 private = TRACKER_TAGS_VIEW_GET_PRIVATE (tv);
922
923 if (!private->connection) {
924 g_warning ("Can't remove tag '%s', "
925 "no SPARQL connection available",
926 td->tag_id);
927 return;
928 }
929
930 query = g_strdup_printf ("DELETE { "
931 " <%s> a rdfs:Resource "
932 "}",
933 td->tag_id);
934
935 td_copy = tag_data_copy (td);
936 private->tag_data_requests =
937 g_list_prepend (private->tag_data_requests, td_copy);
938
939 tracker_sparql_connection_update_async (private->connection,
940 query,
941 G_PRIORITY_DEFAULT,
942 td_copy->cancellable,
943 tags_view_tag_removed_cb,
944 td_copy);
945 g_free (query);
946 }
947
948 static void
949 tags_view_remove_clicked_cb (GtkButton *button,
950 gpointer user_data)
951 {
952 TrackerTagsView *tv;
953 TrackerTagsViewPrivate *private;
954 TagData *td;
955 GtkTreeIter iter;
956 GtkTreeSelection *select;
957 GtkTreeModel *model;
958 gchar *id;
959
960 tv = user_data;
961 private = TRACKER_TAGS_VIEW_GET_PRIVATE (tv);
962
963 select = gtk_tree_view_get_selection (GTK_TREE_VIEW (private->view));
964
965 if (gtk_tree_selection_get_selected (select, &model, &iter)) {
966 gtk_tree_model_get (GTK_TREE_MODEL (private->store), &iter, COL_TAG_ID, &id, -1);
967
968 td = tag_data_new (id, &iter, FALSE, TRUE, 1, tv);
969 private->tag_data_requests =
970 g_list_prepend (private->tag_data_requests, td);
971
972 tags_view_remove_tag (tv, td);
973
974 private->tag_data_requests =
975 g_list_remove (private->tag_data_requests, td);
976 tag_data_free (td);
977 }
978 }
979
980 static void
981 tags_view_model_row_selected_cb (GtkTreeSelection *selection,
982 gpointer user_data)
983 {
984 TrackerTagsViewPrivate *private;
985 GtkTreeIter iter;
986 GtkTreeModel *model;
987
988 private = TRACKER_TAGS_VIEW_GET_PRIVATE (user_data);
989
990 if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
991 gtk_widget_set_sensitive (GTK_WIDGET (private->button_remove), TRUE);
992 } else {
993 gtk_widget_set_sensitive (GTK_WIDGET (private->button_remove), FALSE);
994 }
995 }
996
997 static void
998 tags_view_create_ui (TrackerTagsView *tv)
999 {
1000 TrackerTagsViewPrivate *private;
1001 GtkCellRenderer *cell_renderer;
1002 GtkTreeSelection *selection;
1003 GtkTreeViewColumn *column;
1004 GtkWidget *hbox;
1005 GtkWidget *label;
1006 GtkWidget *entry;
1007 GtkWidget *button;
1008 GtkWidget *scrolled_window;
1009 GtkWidget *view;
1010 gchar *str;
1011
1012 private = TRACKER_TAGS_VIEW_GET_PRIVATE (tv);
1013
1014 gtk_container_set_border_width (GTK_CONTAINER (tv), 6);
1015 gtk_box_set_homogeneous (GTK_BOX (tv), FALSE);
1016 gtk_box_set_spacing (GTK_BOX (tv), 6);
1017
1018 /* Add entry/label part */
1019 str = g_strdup_printf (dngettext (NULL,
1020 "_Set the tags you want to associate with the %d selected item:",
1021 "_Set the tags you want to associate with the %d selected items:",
1022 g_list_length (private->files)),
1023 g_list_length (private->files));
1024
1025 label = gtk_label_new_with_mnemonic (str);
1026 g_free (str);
1027 gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
1028
1029 gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
1030 gtk_box_pack_start (GTK_BOX (tv), label, FALSE, TRUE, 0);
1031
1032 hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12);
1033 gtk_box_pack_start (GTK_BOX (tv), hbox, FALSE, TRUE, 0);
1034
1035 entry = gtk_entry_new ();
1036 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
1037
1038 gtk_entry_set_activates_default (GTK_ENTRY (entry), TRUE);
1039 gtk_label_set_mnemonic_widget (GTK_LABEL (label), entry);
1040
1041 g_signal_connect (entry, "changed",
1042 G_CALLBACK (tags_view_entry_changed_cb),
1043 tv);
1044 g_signal_connect (entry, "activate",
1045 G_CALLBACK (tags_view_entry_activate_cb),
1046 tv);
1047
1048 button = gtk_button_new_from_stock (GTK_STOCK_ADD);
1049 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, TRUE, 0);
1050
1051 gtk_widget_set_can_default (button, TRUE);
1052 gtk_widget_set_sensitive (button, FALSE);
1053
1054 g_signal_connect (button, "clicked",
1055 G_CALLBACK (tags_view_add_clicked_cb),
1056 tv);
1057
1058 private->button_add = button;
1059
1060 button = gtk_button_new_from_stock (GTK_STOCK_REMOVE);
1061 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, TRUE, 0);
1062
1063 gtk_widget_set_sensitive (button, FALSE);
1064
1065 g_signal_connect (button, "clicked",
1066 G_CALLBACK (tags_view_remove_clicked_cb),
1067 tv);
1068
1069 private->button_remove = button;
1070
1071 /* List */
1072 scrolled_window = gtk_scrolled_window_new (NULL, NULL);
1073 gtk_box_pack_start (GTK_BOX (tv), scrolled_window, TRUE, TRUE, 0);
1074 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
1075 GTK_POLICY_AUTOMATIC,
1076 GTK_POLICY_AUTOMATIC);
1077 gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_window),
1078 GTK_SHADOW_IN);
1079
1080 view = gtk_tree_view_new ();
1081 gtk_container_add (GTK_CONTAINER (scrolled_window), view);
1082
1083 /* List column: toggle */
1084 column = gtk_tree_view_column_new ();
1085 gtk_tree_view_append_column (GTK_TREE_VIEW (view), column);
1086
1087 gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
1088 gtk_tree_view_column_set_fixed_width (column, 50);
1089
1090 cell_renderer = gtk_cell_renderer_toggle_new ();
1091 g_signal_connect (cell_renderer, "toggled",
1092 G_CALLBACK (tags_view_model_cell_toggled_cb),
1093 tv);
1094
1095 gtk_tree_view_column_pack_start (column, cell_renderer, TRUE);
1096 gtk_tree_view_column_set_cell_data_func (column,
1097 cell_renderer,
1098 tags_view_model_toggle_cell_data_func,
1099 NULL,
1100 NULL);
1101 gtk_cell_renderer_toggle_set_radio (GTK_CELL_RENDERER_TOGGLE (cell_renderer), FALSE);
1102
1103 /* List column: tag */
1104 column = gtk_tree_view_column_new ();
1105 gtk_tree_view_append_column (GTK_TREE_VIEW (view), column);
1106
1107 cell_renderer = gtk_cell_renderer_text_new ();
1108 gtk_tree_view_column_set_expand (column, TRUE);
1109 gtk_tree_view_column_pack_start (column, cell_renderer, TRUE);
1110 gtk_tree_view_column_add_attribute (column, cell_renderer, "text", COL_TAG_NAME);
1111
1112 /* List column: count */
1113 column = gtk_tree_view_column_new ();
1114 gtk_tree_view_append_column (GTK_TREE_VIEW (view), column);
1115
1116 gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
1117 gtk_tree_view_column_set_fixed_width (column, 50);
1118
1119 cell_renderer = gtk_cell_renderer_text_new ();
1120 gtk_tree_view_column_pack_end (column, cell_renderer, FALSE);
1121 gtk_tree_view_column_add_attribute (column, cell_renderer, "text", COL_TAG_COUNT);
1122
1123 /* List settings */
1124 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (view), FALSE);
1125 gtk_tree_view_set_model (GTK_TREE_VIEW (view),
1126 GTK_TREE_MODEL (private->store));
1127
1128
1129 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1130 gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
1131 g_signal_connect (view, "row-activated",
1132 G_CALLBACK (tags_view_model_row_activated_cb),
1133 tv);
1134
1135 g_signal_connect (selection, "changed",
1136 G_CALLBACK (tags_view_model_row_selected_cb),
1137 tv);
1138
1139 if (private->connection) {
1140 tracker_sparql_connection_query_async (private->connection,
1141 "SELECT ?urn ?label "
1142 "WHERE {"
1143 " ?urn a nao:Tag ;"
1144 " nao:prefLabel ?label . "
1145 "} ORDER BY ?label",
1146 private->cancellable,
1147 tags_view_add_tags_cb,
1148 tv);
1149 } else {
1150 g_warning ("Can't query for tags, "
1151 "no SPARQL connection available");
1152 }
1153
1154 gtk_widget_show_all (GTK_WIDGET (tv));
1155 gtk_widget_grab_focus (entry);
1156
1157 /* Save vars */
1158 private->entry = entry;
1159 private->view = view;
1160 }
1161
1162 void
1163 tracker_tags_view_register_types (GTypeModule *module)
1164 {
1165 tracker_tags_view_register_type (module);
1166 }
1167
1168 GtkWidget *
1169 tracker_tags_view_new (GList *files)
1170 {
1171 TrackerTagsView *tv;
1172 TrackerTagsViewPrivate *private;
1173
1174 g_return_val_if_fail (files != NULL, NULL);
1175
1176 g_debug ("New TrackerTagsView with %d files", g_list_length (files));
1177 tv = g_object_new (TRACKER_TYPE_TAGS_VIEW, NULL);
1178
1179 private = TRACKER_TAGS_VIEW_GET_PRIVATE (tv);
1180 private->files = nautilus_file_info_list_copy (files);
1181
1182 tags_view_create_ui (tv);
1183
1184 return GTK_WIDGET (tv);
1185 }