[tracker/tracker-needle-improved-tagging: 6/6] WIP



commit 96b3e47da64b35ea80e001ad18ccfc233fdbbade
Author: Martyn Russell <martyn lanedo com>
Date:   Mon Oct 3 23:16:41 2011 +0100

    WIP

 src/tracker-needle/Makefile.am            |    1 +
 src/tracker-needle/tracker-needle.ui      |  133 +++++-
 src/tracker-needle/tracker-taglist.vala   |    2 +-
 src/tracker-needle/tracker-tags-view.vala |  802 +++++++++++++++++++++++++++++
 src/tracker-needle/tracker-view.vala      |   17 +-
 5 files changed, 929 insertions(+), 26 deletions(-)
---
diff --git a/src/tracker-needle/Makefile.am b/src/tracker-needle/Makefile.am
index 5dbc7eb..bfcc9ce 100644
--- a/src/tracker-needle/Makefile.am
+++ b/src/tracker-needle/Makefile.am
@@ -30,6 +30,7 @@ tracker_needle_SOURCES =                           \
 	tracker-result-store.vala                      \
 	tracker-stats.vala                             \
 	tracker-taglist.vala                           \
+	tracker-tags-view.vala
 	tracker-utils.vala                             \
 	tracker-needle.vala                            \
 	tracker-view.vala
diff --git a/src/tracker-needle/tracker-needle.ui b/src/tracker-needle/tracker-needle.ui
index f0f9142..4a74e3c 100644
--- a/src/tracker-needle/tracker-needle.ui
+++ b/src/tracker-needle/tracker-needle.ui
@@ -1,12 +1,132 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
   <!-- interface-requires gtk+ 2.12 -->
+  <!-- interface-naming-policy toplevel-contextual -->
   <object class="GtkListStore" id="liststore_search">
     <columns>
       <!-- column-name text -->
       <column type="gchararray"/>
     </columns>
   </object>
+  <object class="GtkVBox" id="vbox_tags_view">
+    <property name="visible">True</property>
+    <property name="can_focus">False</property>
+    <property name="spacing">6</property>
+    <child>
+      <object class="GtkLabel" id="label1">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="xalign">0</property>
+        <property name="label" translatable="yes">_Set the tags you want to associate with the %d selected items:</property>
+        <property name="use_underline">True</property>
+        <property name="justify">fill</property>
+        <property name="wrap">True</property>
+      </object>
+      <packing>
+        <property name="expand">False</property>
+        <property name="fill">True</property>
+        <property name="position">0</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkHBox" id="hbox1">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="spacing">12</property>
+        <child>
+          <object class="GtkEntry" id="entry_tag">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="has_focus">True</property>
+            <property name="invisible_char">â</property>
+            <property name="primary_icon_activatable">False</property>
+            <property name="secondary_icon_activatable">False</property>
+            <property name="primary_icon_sensitive">True</property>
+            <property name="secondary_icon_sensitive">True</property>
+            <signal name="changed" handler="tracker_tags_view_entry_tag_changed_cb" swapped="no"/>
+            <signal name="activate" handler="tracker_tags_view_entry_tag_activate_cb" swapped="no"/>
+          </object>
+          <packing>
+            <property name="expand">True</property>
+            <property name="fill">True</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkHBox" id="hbox2">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="spacing">6</property>
+            <child>
+              <object class="GtkButton" id="button_add">
+                <property name="label">gtk-add</property>
+                <property name="visible">True</property>
+                <property name="sensitive">False</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="use_action_appearance">False</property>
+                <property name="use_stock">True</property>
+                <signal name="clicked" handler="tracker_tags_view_button_add_clicked_cb" swapped="no"/>
+              </object>
+              <packing>
+                <property name="expand">True</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton" id="button_remove">
+                <property name="label">gtk-remove</property>
+                <property name="visible">True</property>
+                <property name="sensitive">False</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="use_action_appearance">False</property>
+                <property name="use_stock">True</property>
+                <signal name="clicked" handler="tracker_tags_view_button_remove_clicked_cb" swapped="no"/>
+              </object>
+              <packing>
+                <property name="expand">True</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+      </object>
+      <packing>
+        <property name="expand">False</property>
+        <property name="fill">True</property>
+        <property name="position">1</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkScrolledWindow" id="scrolledwindow_tags">
+        <property name="visible">True</property>
+        <property name="can_focus">True</property>
+        <property name="hscrollbar_policy">automatic</property>
+        <property name="vscrollbar_policy">never</property>
+        <child>
+          <object class="GtkTreeView" id="treeview_tags">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="enable_search">False</property>
+            <signal name="row-activated" handler="tracker_tags_view_treeview_tags_row_activated_cb" swapped="no"/>
+          </object>
+        </child>
+      </object>
+      <packing>
+        <property name="expand">True</property>
+        <property name="fill">True</property>
+        <property name="position">2</property>
+      </packing>
+    </child>
+  </object>
   <object class="GtkWindow" id="window_needle">
     <property name="can_focus">False</property>
     <property name="title" translatable="yes">Desktop Search</property>
@@ -113,6 +233,8 @@
             </child>
             <child>
               <object class="GtkRadioToolButton" id="toolbutton_find_in_all">
+                <property name="can_focus">False</property>
+                <property name="use_action_appearance">False</property>
                 <property name="use_underline">True</property>
                 <property name="stock_id">gtk-select-all</property>
                 <property name="active">True</property>
@@ -166,17 +288,6 @@
                     <property name="yscale">0</property>
                     <property name="left_padding">4</property>
                     <property name="right_padding">4</property>
-                    <child>
-                      <object class="GtkComboBoxText" id="comboboxtext_search">
-                        <property name="visible">True</property>
-                        <property name="can_focus">True</property>
-                        <property name="has_focus">True</property>
-                        <property name="has_entry">True</property>
-                        <property name="model">liststore_search</property>
-                        <accelerator key="s" signal="grab-focus" modifiers="GDK_CONTROL_MASK"/>
-                        <accelerator key="f" signal="grab-focus" modifiers="GDK_CONTROL_MASK"/>
-                      </object>
-                    </child>
                   </object>
                 </child>
               </object>
diff --git a/src/tracker-needle/tracker-taglist.vala b/src/tracker-needle/tracker-taglist.vala
index c5c9796..6f8c328 100644
--- a/src/tracker-needle/tracker-taglist.vala
+++ b/src/tracker-needle/tracker-taglist.vala
@@ -20,7 +20,7 @@
 using Gtk;
 
 public class Tracker.TagList : ScrolledWindow {
-	static Sparql.Connection connection;
+	private static Sparql.Connection connection;
 	private TreeView treeview;
 	private ListStore store;
 	private int offset;
diff --git a/src/tracker-needle/tracker-tags-view.vala b/src/tracker-needle/tracker-tags-view.vala
new file mode 100644
index 0000000..d58893b
--- /dev/null
+++ b/src/tracker-needle/tracker-tags-view.vala
@@ -0,0 +1,802 @@
+/*
+ * Copyright (C) 2011, Martyn Russell <martyn lanedo com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+using Gtk;
+using Tracker;
+
+private class TagData {
+	public TrackerTagsView tv;
+	public Cancellable cancellable;
+	public string tag_id;
+	public TreeIter *iter;
+	public int items;
+	public bool update;
+	public bool selected;
+
+	TagData (string tag_id, TreeIter? iter, bool update, bool selected, int items, TrackerTagsView tv) {
+		debug ("Creating tag data");
+
+		self.tv = tv;
+		self.cancellable = new Cancellable ();
+		self.tag_id = tag_id;
+
+		if (iter != null) {
+			self.iter = iter.copy ();
+		} else {
+			self.iter = null;
+		}
+
+		self.items = items;
+		self.update = update;
+		self.selected = selected;
+	}
+
+	~TagData () {
+		if (cancellable != null) {
+			cancellable.cancel ();
+			cancellable = null;
+		}
+
+		if (iter != null) {
+			iter.free ();
+		}
+	}
+}
+
+private class FindTag {
+	public TrackerTagsView tv;
+	public string tag;
+	public bool found;
+	public TreeIter found_iter;
+
+	FindTag (TrackerTagsView tv, string tag) {
+		self.tv = tv;
+		self.tag = tag;
+	}
+}
+
+[CCode (cname = "TRACKER_UI_DIR")]
+extern static const string UIDIR;
+
+[CCode (cname = "SRCDIR")]
+extern static const string SRCDIR;
+
+public class TrackerTagsView : VBox {
+	private Sparql.Connection connection;
+	private Cancellable cancellable;
+
+	private List<TagData> tag_data_requests;
+	private List<string> files;
+
+	private ListStore store;
+
+	private const string UI_FILE = "tracker-preferences.ui";
+
+	private VBox vbox;
+	private HBox hbox;
+	private Label label;
+	private Entry entry;
+	private Button button_add;
+	private Button button_remove;
+	private ScrolledWindow scrolled_window;
+	private TreeView view;
+
+	enum Col {
+		SELECTION,
+		TAG_ID,
+		TAG_NAME,
+		TAG_COUNT,
+		TAG_COUNT_VALUE,
+		N_COLUMNS
+	}
+
+	enum Selection {
+		INCONSISTENT = -1,
+		FALSE = 0,
+		TRUE = 1
+	}
+
+	TrackerTagsView () {
+		try {
+			connection = Sparql.Connection.get ();
+		} catch (GLib.Error e) {
+			warning ("Could not get Sparql connection: %s", e.message);
+		}
+
+		cancellable = new Cancellable ();
+
+		store = new ListStore (Col.N_COLUMNS,
+		                       typeof (int),      /* Selection type */
+		                       typeof (string),   /* Tag ID */
+		                       typeof (string),   /* Tag Name */
+		                       typeof (string),   /* Tag Count String */
+		                       typeof (int));     /* Tag Count */
+	}
+
+	~TrackerTagsView () {
+		if (cancellable != null) {
+			cancellable.cancel ();
+			cancellable = null;
+		}
+
+		if (private->files) {
+			// TODO: finish
+			//nautilus_file_info_list_free (private->files);
+			private->files = null;
+		}
+
+		if (tag_data_requests != null) {
+			tag_data_requests.foreach ((td) => {
+				tag_data_free (td);
+			});
+
+			tag_data_requests = NULL;
+		}
+	}
+
+	private void show_error_dialog (Error e) {
+		string str = e.message != null ? e.message : _("No error was given");
+
+		var msg = new MessageDialog (null,
+		                             DialogFlags.MODAL,
+		                             MessageType.ERROR,
+		                             ButtonsType.CLOSE,
+		                             "%s",
+		                             e.message);
+		msg.run ();
+	}
+
+	[CCode (instance_pos = -1)]
+	public void button_remove_clicked_cb (Button source) {
+		debug ("Remove clicked");
+		
+//		TrackerTagsView *tv;
+//		TrackerTagsViewPrivate *private;
+//		TagData *td;
+//		GtkTreeIter iter;
+//		GtkTreeSelection *select;
+//		GtkTreeModel *model;
+//		gchar *id;
+
+//		tv = user_data;
+//		private = TRACKER_TAGS_VIEW_GET_PRIVATE (tv);
+
+//		select = gtk_tree_view_get_selection (GTK_TREE_VIEW (private->view));
+
+//		if (gtk_tree_selection_get_selected (select, &model, &iter)) {
+//			gtk_tree_model_get (GTK_TREE_MODEL (private->store), &iter, Col.TAG_ID, &id, -1);
+
+//			td = tag_data_new (id, &iter, FALSE, TRUE, 1, tv);
+//			private->tag_data_requests =
+//				g_list_prepend (private->tag_data_requests, td);
+
+//			tags_view_remove_tag (tv, td);
+
+//			private->tag_data_requests =
+//				g_list_remove (private->tag_data_requests, td);
+//			tag_data_free (td);
+//		}
+	}
+
+	[CCode (instance_pos = -1)]
+	public void button_add_clicked_cb (Button source) {
+		debug ("Add clicked");
+
+//		TrackerTagsView *tv;
+//		TrackerTagsViewPrivate *private;
+//		const gchar *tag;
+
+//		tv = user_data;
+//		private = TRACKER_TAGS_VIEW_GET_PRIVATE (tv);
+
+//		tag = gtk_entry_get_text (GTK_ENTRY (private->entry));
+//		tags_view_add_tag (tv, tag);
+	}
+
+	[CCode (instance_pos = -1)]
+	public void entry_tag_activated_cb (Entry source) {
+		debug ("Entry activated");
+
+//		TrackerTagsViewPrivate *private;
+
+//		private = TRACKER_TAGS_VIEW_GET_PRIVATE (tv);
+
+//		gtk_widget_activate (private->button_add);
+	}
+
+	[CCode (instance_pos = -1)]
+	public void entry_tag_changed_cb (Editable source) {
+		debug ("Entry changed");
+
+//		TrackerTagsViewPrivate *private;
+//		GtkTreeIter iter;
+//		const gchar *tag;
+
+//		private = TRACKER_TAGS_VIEW_GET_PRIVATE (tv);
+
+//		tag = gtk_entry_get_text (GTK_ENTRY (private->entry));
+
+//		if (tag_view_model_find_tag (tv, tag, &iter)) {
+//			gtk_widget_set_sensitive (GTK_WIDGET (private->button_add), FALSE);
+//		} else {
+//			gtk_widget_set_sensitive (GTK_WIDGET (private->button_add),
+//				                  !tracker_is_empty_string (tag));
+//		}
+	}
+
+	[CCode (instance_pos = -1)]
+	public void treeview_tags_cell_toggled_cb (CellRendererToggle cell, string path_string, TrackerTagsView tv) {
+		TreePath path;
+
+		// TODO: finish
+		// path = gtk_tree_path_new_from_string (path_string);
+		model_toggle_row (tv, path);
+		// gtk_tree_path_free (path);
+	}
+
+	[CCode (instance_pos = -1)]
+	public void treeview_tags_row_selected_cb (TreeSelection selection) {
+		TreeIter iter;
+		TreeModel model;
+
+		if (selection.get_selected (out model, out iter)) {
+			button_remove.set_sensitive = true;
+		} else {
+			button_remove.set_sensitive = false;
+		}
+	}
+
+	[CCode (instance_pos = -1)]
+	public void treeview_tags_row_activated_cb (TreeView source, TreePath path, TreeViewColumn column) {
+		debug ("Treeview row activated");
+
+		model_toggle_row (user_data, path);
+	}
+
+	[CCode (instance_pos = -1)]
+	private void treeview_tags_toggle_cell_data_func (TreeViewColumn column, Gtk.CellRenderer renderer, TreeModel model, TreeIter iter) {
+		Value inconsistent = { 0 };
+		int selection;
+
+		model.get (iter, Col.SELECTION, out selection, -1);
+		((Gtk.CellRendererToggle) renderer).set_active (Selection.TRUE == selection);
+
+		g_value_init (&inconsistent, typeof (bool));
+		g_value_set_boolean (&inconsistent, Selection.INCONSISTENT == selection);
+		g_object_set_property (G_OBJECT (cell_renderer), "inconsistent", &inconsistent);
+	}
+
+	private void create_ui (TrackerTagsView tv) {
+		CellRenderer cell_renderer;
+		TreeSelection selection;
+		TreeViewColumn column;
+		string str;
+
+		var builder = new Gtk.Builder ();
+
+		try {
+			debug ("Trying to use UI file:'%s'", SRCDIR + UI_FILE);
+			builder.add_from_file (SRCDIR + UI_FILE);
+		} catch (GLib.Error e) {
+			//now the install location
+			try {
+				debug ("Trying to use UI file:'%s'", UIDIR + UI_FILE);
+				builder.add_from_file (UIDIR + UI_FILE);
+			} catch (GLib.Error e) {
+				var msg = new MessageDialog (null,
+				                             DialogFlags.MODAL,
+				                             MessageType.ERROR,
+				                             ButtonsType.CANCEL,
+				                             "Failed to load UI file, %s\n",
+				                             e.message);
+				msg.run ();
+				Gtk.main_quit();
+			}
+		}
+
+		// Get widgets from .ui file
+		vbox = builder.get_object ("vbox_tags") as VBox;
+		label = builder.get_object ("label_tag") as Label;
+		entry = builder.get_object ("entry_tag") as Entry;
+		button_add = builder.get_object ("button_add") as Button;
+		button_remove = builder.get_object ("button_remove") as Button;
+		scrolled_window = builder.get_object ("scrolled_window_tags") as ScrolledWindow;
+		view = builder.get_object ("treeview_tags") as TreeView;
+
+		// TODO: finish
+		//str = g_strdup_printf (dngettext (NULL,
+		//	                          "_Set the tags you want to associate with the %d selected item:",
+		//	                          "_Set the tags you want to associate with the %d selected items:",
+		//	                          g_list_length (private->files)),
+		//	               g_list_length (private->files));
+
+		Gtk.TreeViewColumn col;
+		Gtk.CellRenderer renderer;
+
+		// List column: Tag
+		renderer = new CellRendererToggle ();
+		renderer.xpad = 5;
+		renderer.ypad = 5;
+		renderer.connect.toggled (cell_toggled_cb, tv);
+		((Gtk.CellRendererToggle) renderer).set_radio (false);
+
+		col = new Gtk.TreeViewColumn ();
+		col.set_title ("-");
+		col.set_resizable (false);
+		col.set_sizing (Gtk.TreeViewColumnSizing.FIXED);
+		col.set_fixed_width (50);
+		col.pack_start (renderer, false);
+		col.set_cell_data_func (renderer, model_toggle_cell_data_func);
+		view.append_column (col);
+
+		// List column: Name
+		renderer = new CellRendererText ();
+		renderer.xpad = 5;
+		renderer.ypad = 5;
+
+		col = new Gtk.TreeViewColumn ();
+		col.set_title (_("Name"));
+		col.set_resizable (true);
+		col.set_sizing (Gtk.TreeViewColumnSizing.AUTOSIZE);
+		col.set_expand (true);
+		col.pack_start (renderer, true);
+		col.add_attribute (renderer, "text", 2);
+
+		view.append_column (col);
+
+		// List coumnn: Count
+		renderer = new CellRendererText ();
+		renderer.xpad = 5;
+		renderer.ypad = 5;
+
+		col = new Gtk.TreeViewColumn ();
+		col.set_title ("-");
+		col.set_resizable (false);
+		col.set_sizing (Gtk.TreeViewColumnSizing.FIXED);
+		col.set_fixed_width (50);
+		col.pack_end (renderer, false);
+		col.add_attribute (renderer, "text", 3);
+
+		view.append_column (col);
+
+		var selection = treeview.get_selection ();
+		selection.changed.connect (model_selection_changed);
+
+		// Get all tags
+		Sparql.Cursor cursor = null;
+
+		try {
+			string query = "SELECT ?urn ?label WHERE { ?urn a nao:Tag ; nao:prefLabel ?label . } ORDER BY ?label";
+
+			cursor = yield connection.query_async (query, null);
+
+			while (yield cursor.next_async ()) {
+				debug ("Clearing tags in store");
+
+				store.clear ();
+
+				debug ("Adding all tags...");
+
+				const string id = cursor.get_string (0);
+				const string label = cursor.get_string (1);
+
+				debug ("  Adding tag id:'%s' with label:'%s' to store", id, label);
+
+				TreeIter iter;
+				store.append (out iter);
+
+				store.set (iter,
+				           Col.TAG_ID, id,
+				           Col.TAG_NAME, label,
+				           Col.SELECTION, Selection.FALSE,
+				           -1);
+
+				td = new TagData (id, iter, false, true, 1, tv);
+				tag_data_requests += td;
+
+				query_files_for_tag_id (td);
+			}
+		} catch (GLib.Error e) {
+			warning ("Could not run Sparql query: %s", e.message);
+		} finally {
+		}
+
+		vbox.show_all ();
+	}
+
+	private void model_toggle_row (TrackerTagsView tv, TreePath path) {
+		TagData td = null;
+		string f[] = null;
+		TreeIter iter;
+		TreeModel model;
+		string filter, query;
+		string id, tag, tag_escaped;
+		int selection;
+
+		model = view.get_model ();
+
+		if (model.get_iter (out iter, path) == false) {
+			return;
+		}
+
+		model.get (iter,
+		           Col.SELECTION, out selection,
+		           Col.TAG_ID, out id,
+		           Col.TAG_NAME, out tag,
+		           -1);
+
+		selection = selection == Selection.FALSE ? Selection.TRUE : Selection.FALSE;
+
+		// TODO: finish
+		//tag_escaped = tracker_tags_escape_sparql_string (tag);
+
+		// TODO: finish
+		// f = tracker_glist_to_string_list_for_nautilus_files (private->files);
+		// filter = tracker_tags_get_filter_string (f, NULL);
+		f = null;
+		
+		if (selection) {
+			// NB: ?f is used in filter.
+			query = "INSERT { 
+			           ?urn nao:hasTag ?label 
+			         } WHERE {
+			           ?urn nie:url ?f . 
+			           ?label nao:prefLabel %s .
+			           %s 
+			         }".printf (tag_escaped, filter);
+		} else {
+			// NB: ?f is used in filter.
+			query = "DELETE { 
+			           ?urn nao:hasTag ?label 
+			         } WHERE { 
+			           ?urn nie:url ?f .
+			           ?label nao:prefLabel %s .
+			           %s 
+			         }".printf (tag_escaped, filter);
+
+			/* Check if there are any files left with this tag and
+			 * remove tag if not.
+			 */
+			td = new TagData (id, iter, false, true, 1, tv);
+			tag_data_requests.prepend (td);
+
+			query_files_for_tag_id (td);
+		}
+
+		filter = null;
+		tag_escaped = null;
+
+		entry.set_sensitive (false);
+
+		if (connection == null) {
+			warning ("Can't update tags, no SPARQL connection available");
+			return;
+		}
+
+		debug ("Running query:'%s'", query);
+
+		// TODO: Why do we have 2x td parameters here?
+
+		td = new TagData (id, iter, true, selection, 1, tv);
+		tag_data_requests += td;
+
+		try {
+			Sparql.Cursor cursor = null;
+
+			cursor = yield connection.update_async (query, Priority.DEFAULT, td.cancellable);
+
+			debug ("Updated tags");
+			update_tag_data (td);
+		} catch (GLib.Error e) {
+			warning ("Could not run Sparql update query: %s", e.message);
+			show_error_dialog (e);
+		}
+
+		tag_data_requests -= td;
+		td = null;
+
+		entry.set_text ("");
+		entry.set_sensitive (true);
+	}
+
+	private bool find_tag (TrackerTagsView tv, string tag, out TreeIter iter) {
+		TreeView view;
+		TreeModel model;
+		FindTag data;
+
+		if (tag == null || *tag == '\0') {
+			return false;
+		}
+
+		data = new FindTag (tv, tag);
+
+		data.tv = tv;
+		data.tag = tag;
+		data.found = FALSE;
+
+		model = gtk_tree_view_get_model (view);
+
+		model.foreach ((model, path, out iter, data) => {
+			string tag;
+
+			model.get (iter, Col.TAG_NAME, out tag, -1);
+
+			if (tag == null) {
+				return false;
+			}
+
+			if (data->tag != null && strcmp (data->tag, tag) == 0) {
+				data.found = true;
+				data.found_iter = iter;
+			}
+
+			return true;
+		},
+		data);
+
+		if (data.found == true) {
+			iter = data.found_iter;
+			return true;
+		}
+
+		return false;
+	}
+
+	private void remove_tag (TrackerTagsView tv, TagData td) {
+		TagData td_copy;
+		string query;
+
+		if (connection == null) {
+			warning ("Can't remove tag '%s', no SPARQL connection available", td->tag_id);
+			return;
+		}
+
+		query = "DELETE { <%s> a rdfs:Resource }".printf (td.tag_id);
+
+		td_copy = new TagData (td.tag_id, td.iter, td.update, td.selected, td.items, td.tv);
+		tag_data_requests += td_copy;
+
+		try {
+			Sparql.Cursor cursor = null;
+
+			cursor = yield connection.update_async (query, Priority.DEFAULT, td_copy.cancellable);
+
+			if (cursor != null) {
+				yield cursor.next_async ();
+			}
+
+			debug ("Tag removed");
+			store.remove (td_copy.iter);
+		} catch (GLib.Error e) {
+			warning ("Could not run Sparql update query: %s", e.message);
+			show_error_dialog (e);
+		}
+
+		tag_data_requests -= td_copy;
+		td_copy = null;
+	}
+
+	private void add_tag (TrackerTagsView tv, string tag) {
+		TagData td;
+		string query;
+		int files;
+
+		if (connection == null) {
+			warning ("Can't add tag '%s', no SPARQL connection available", tag);
+			return;
+		}
+
+		entry.set_sensitive = false;
+
+		if (files.length > 0) {
+			List<string> files = null;
+			string[] f;
+			string tag_escaped;
+			string filter;
+			uint i;
+
+			query = g_string_new ("");
+
+			// TODO: finish
+			//files = tracker_glist_to_string_list_for_nautilus_files (private->files);
+			//filter = tracker_tags_get_filter_string (files, NULL);
+			tag_escaped = tracker_tags_escape_sparql_string (tag);
+
+			for (List<string> l = files; l != null; l.next ()) {
+				query += "INSERT {
+				            _:file a nie:DataObject ;
+				             nie:url '%s'
+				          } WHERE { 
+				            OPTIONAL {
+				               ?file a nie:DataObject ;
+				               nie:url '%s'
+				            } .
+				            FILTER (!bound(?file)) 
+				          }".printf (files[i], files[i]);
+			}
+
+			query += "INSERT { 
+			            _:tag a nao:Tag;
+			            nao:prefLabel %s . 
+			          } WHERE {
+			            OPTIONAL {
+			              ?tag a nao:Tag ;
+			              nao:prefLabel %s
+			            } .
+			            FILTER (!bound(?tag)) 
+			          }
+			          INSERT { 
+			            ?urn nao:hasTag ?label 
+			          } WHERE {
+			            ?urn nie:url ?f .
+			            ?label nao:prefLabel %s 
+			            %s 
+			          }".printf (tag_escaped, tag_escaped, tag_escaped, filter);
+		} else {
+			// TODO: finish
+			// query = new tracker_tags_add_query (tag);
+		}
+
+		td = new TagData (null, null, false, true, files, tv);
+		tag_data_requests += td;
+
+		try {
+			Sparql.Cursor cursor = null;
+
+			cursor = yield connection.update_async (query, Priority.DEFAULT, td.cancellable);
+
+			debug ("Updated tags");
+			update_tag_data (td);
+
+		} catch (GLib.Error e) {
+			warning ("Could not run Sparql update query: %s", e.message);
+			show_error_dialog (e);
+		}
+
+		tag_data_requests -= td;
+		td = null;
+
+		entry.set_text ("");
+		entry.set_sensitive (true);
+	}
+
+	private void update_tag_data (TagData td) {
+		tag = gtk_entry_get_text (GTK_ENTRY (private->entry));
+
+		if (td.update == false) {
+			TreeIter iter;
+
+			debug ("Setting tag selection state to ON (new)");
+
+			store.append (out iter);
+			store.set (iter,
+			           Col.TAG_ID, td.tag_id,
+			           Col.TAG_NAME, tag,
+			           Col.TAG_COUNT, "%d".printf (td.items),
+			           Col.TAG_COUNT_VALUE, td.items,
+			           Col.SELECTION, Selection.TRUE,
+			           -1);
+		} else if (td.selected == true) {
+			TagData td_copy;
+
+			debug ("Setting tag selection state to ON");
+
+			store.set (td.iter,
+			           Col.SELECTION, Selection.TRUE,
+			           -1);
+
+			td_copy = new TagData (td.tag_id, td.iter, td.update, td.selected, td.items, td.tv);
+			tag_data_requests += td_copy;
+
+			query_files_for_tag_id (td_copy);
+		} else {
+			TagData *td_copy;
+
+			debug ("Setting tag selection state to FALSE");
+
+			store.set (td.iter,
+			           Col.SELECTION, Selection.FALSE,
+			           -1);
+
+			td_copy = new TagData (td.tag_id, td.iter, td.update, td.selected, td.items, td.tv);
+			tag_data_requests += td_copy;
+
+			query_files_for_tag_id (td_copy);
+		}
+	
+	}
+
+	private void query_files_for_tag_id (TagData td) {
+		string query;
+
+		if (connection == null) {
+			warning ("Can't query files for tag id '%s', no SPARQL connection available", td->tag_id);
+			return;
+		}
+
+		query = "SELECT ?url WHERE { ?urn a rdfs:Resource ; nie:url ?url ; nao:hasTag <%s> . }".printf (td.tag_id);
+
+		try {
+			Sparql.Cursor cursor = null;
+
+			cursor = yield connection.query_async (query, td.cancellable);
+
+			TagData td;
+			Error error = null;
+			string str;
+			uint files_selected, files_with_tag, has_tag_in_selection;
+
+			has_tag_in_selection = 0;
+			files_with_tag = 0;
+			files_selected = files.length ();
+
+			while (yield cursor.next_async ()) {
+				List l;
+				bool equal;
+
+				files_with_tag++;
+
+				for (List l = files, equal = false; l != null && equal == false; l.next ()) {
+					string uri;
+
+					//debug ("--> %s", cursor.get_string (i));
+
+					// TODO: finish
+					// uri = nautilus_file_info_get_uri (NAUTILUS_FILE_INFO (l->data));
+
+					const string str = cursor.get_string (0);
+
+					if (str == null) {
+						continue;
+					}
+
+					equal = strcmp (str, uri) == 0;
+
+					if (equal) {
+						has_tag_in_selection++;
+					}
+				}
+			}
+
+			debug ("Querying files with tag, in selection:%d, in total:%d, selected:%d",
+			       has_tag_in_selection, files_with_tag, files_selected);
+
+			if (has_tag_in_selection == 0) {
+				store.set (iter, Col.SELECTION, Selection.FALSE, -1);
+			} else if (files_selected != has_tag_in_selection) {
+				store.set (iter, Col.SELECTION, Selection.INCONSISTENT, -1);
+			} else {
+				store.set (iter, Col.SELECTION, Selection.TRUE, -1);
+			}
+
+			str = "%d".printf (files_with_tag);
+			store.set (iter, Col.TAG_COUNT, str, Col.TAG_COUNT_VALUE, files_with_tag, -1);
+
+			debug ("Tags for file updated");
+			store.remove (td_copy.iter);
+		} catch (GLib.Error e) {
+			warning ("Could not run Sparql query: %s", e.message);
+			show_error_dialog (e);
+		}
+
+		tag_data_requests -= td_copy;
+		td_copy = null;
+	}
+}
+
diff --git a/src/tracker-needle/tracker-view.vala b/src/tracker-needle/tracker-view.vala
index 0c51af3..79fafe6 100644
--- a/src/tracker-needle/tracker-view.vala
+++ b/src/tracker-needle/tracker-view.vala
@@ -452,14 +452,8 @@ public class Tracker.View : ScrolledWindow {
 		var separator = new SeparatorMenuItem ();
 		context_menu.append (separator);
 
-		item = new MenuItem.with_mnemonic (_("_Add Tag..."));
-		item.activate.connect (context_menu_tag_add_clicked);
-		item.sensitive = false;
-		context_menu.append (item);
-
-		item = new MenuItem.with_mnemonic (_("_Remove Tag..."));
-		item.activate.connect (context_menu_tag_remove_clicked);
-		item.sensitive = false;
+		item = new MenuItem.with_mnemonic (_("_Tags..."));
+		item.activate.connect (context_menu_tags_clicked);
 		context_menu.append (item);
 
 		context_menu.show_all ();
@@ -522,12 +516,7 @@ public class Tracker.View : ScrolledWindow {
 		tracker_model_launch_selected_parent_dir (model, path, 1);
 	}
 
-	private void context_menu_tag_add_clicked () {
-		warning ("Not yet implemented");
-	}
-
-	private void context_menu_tag_remove_clicked () {
+	private void context_menu_tags_clicked () {
 		warning ("Not yet implemented");
 	}
-
 }



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]