tracker-0.16.2/src/plugins/nautilus/tracker-tags-view.c

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 }