[tracker/tracker-needle-model: 1/6] Move querying mechanism to a custom GtkTreeModel implementation



commit d943f05a3b02c1824ed43e4931eef9ddaab86edc
Author: Carlos Garnacho <carlosg gnome org>
Date:   Thu Mar 17 13:34:14 2011 +0100

    Move querying mechanism to a custom GtkTreeModel implementation
    
    This model takes care of querying and queueing operations for incremental
    loading.

 src/tracker-needle/Makefile.am               |    1 +
 src/tracker-needle/tracker-needle.vala       |   92 +++--
 src/tracker-needle/tracker-query.vala        |  289 +++++--------
 src/tracker-needle/tracker-result-store.vala |  611 ++++++++++++++++++++++++++
 src/tracker-needle/tracker-view.vala         |  138 +++++--
 5 files changed, 884 insertions(+), 247 deletions(-)
---
diff --git a/src/tracker-needle/Makefile.am b/src/tracker-needle/Makefile.am
index f3cdfde..b8e5aed 100644
--- a/src/tracker-needle/Makefile.am
+++ b/src/tracker-needle/Makefile.am
@@ -28,6 +28,7 @@ tracker_needle_SOURCES =                           \
 	tracker-cell-renderer-text.vala                \
 	tracker-history.vala                           \
 	tracker-query.vala                             \
+	tracker-result-store.vala                      \
 	tracker-stats.vala                             \
 	tracker-taglist.vala                           \
 	tracker-utils.vala                             \
diff --git a/src/tracker-needle/tracker-needle.vala b/src/tracker-needle/tracker-needle.vala
index 0f8f54f..174643f 100644
--- a/src/tracker-needle/tracker-needle.vala
+++ b/src/tracker-needle/tracker-needle.vala
@@ -80,6 +80,19 @@ public class Tracker.Needle {
 		}
 	}
 
+	private void store_state_changed (GLib.Object object,
+	                                  ParamSpec   p) {
+		ResultStore store = (ResultStore) object;
+
+		if (store.active) {
+			spinner_shell.show_all ();
+			spinner.start ();
+		} else {
+			spinner_shell.hide ();
+			spinner.stop ();
+		}
+	}
+
 	private void setup_ui () {
 		var builder = new Gtk.Builder ();
 
@@ -154,17 +167,18 @@ public class Tracker.Needle {
 		sw_categories = new Tracker.View (Tracker.View.Display.CATEGORIES, null);
 		treeview = (TreeView) sw_categories.get_child ();
 		treeview.row_activated.connect (view_row_selected);
+		sw_categories.store.notify["active"].connect (store_state_changed);
 		view.pack_start (sw_categories, true, true, 0);
 
-		sw_filelist = new Tracker.View (Tracker.View.Display.FILE_LIST, null);
-		treeview = (TreeView) sw_filelist.get_child ();
-		treeview.row_activated.connect (view_row_selected);
-		view.pack_start (sw_filelist, true, true, 0);
+		// sw_filelist = new Tracker.View (Tracker.View.Display.FILE_LIST, null);
+		// treeview = (TreeView) sw_filelist.get_child ();
+		// treeview.row_activated.connect (view_row_selected);
+		// view.pack_start (sw_filelist, true, true, 0);
 
-		sw_icons = new Tracker.View (Tracker.View.Display.FILE_ICONS, null);
-		iconview = (IconView) sw_icons.get_child ();
-		iconview.item_activated.connect (icon_item_selected);
-		view.pack_start (sw_icons, true, true, 0);
+		// sw_icons = new Tracker.View (Tracker.View.Display.FILE_ICONS, null);
+		// iconview = (IconView) sw_icons.get_child ();
+		// iconview.item_activated.connect (icon_item_selected);
+		// view.pack_start (sw_icons, true, true, 0);
 
 		// Set up taglist
 		taglist = new Tracker.TagList ();
@@ -185,13 +199,13 @@ public class Tracker.Needle {
 	}
 
 	private ListStore? get_store_for_active_view () {
-		if (view_icons.active) {
-			return sw_icons.store;
-		} else if (view_filelist.active) {
-			return sw_filelist.store;
-		} else if (view_categories.active) {
-			return sw_categories.store;
-		}
+       		// if (view_icons.active) {
+		// 	return sw_icons.store;
+		// } else if (view_filelist.active) {
+		// 	return sw_filelist.store;
+		// } else if (view_categories.active) {
+		// 	return sw_categories.store;
+		// }
 
 		debug ("No views active to get store?!?!");
 		return null;
@@ -206,6 +220,7 @@ public class Tracker.Needle {
 	}
 
 	private async void search_simple (ListStore store) requires (store != null) {
+		/*
 		Tracker.Query query = new Tracker.Query ();
 		Tracker.Sparql.Cursor cursor = null;
 
@@ -216,9 +231,9 @@ public class Tracker.Needle {
 
 		try {
 			if (find_in_contents.active) {
-				cursor = yield query.perform_async (query.Type.ALL);
+				cursor = yield query.perform_async (query.Type.ALL, null);
 			} else {
-				cursor = yield query.perform_async (query.Type.ALL_ONLY_IN_TITLES);
+				cursor = yield query.perform_async (query.Type.ALL_ONLY_IN_TITLES, null);
 			}
 
 			if (cursor == null) {
@@ -281,9 +296,11 @@ public class Tracker.Needle {
 		}
 
 		search_finished (store);
+		*/
 	}
 
-	private async void search_detailed (ListStore store) requires (store != null) {
+	private async void search_detailed (ResultStore store) requires (store != null) {
+		/*
 		Tracker.Query.Type[] categories = { 
 			Tracker.Query.Type.APPLICATIONS,
 			Tracker.Query.Type.MUSIC,
@@ -293,8 +310,10 @@ public class Tracker.Needle {
 			Tracker.Query.Type.IMAGES,
 			Tracker.Query.Type.FOLDERS
 		};
+
 		Tracker.Query query = new Tracker.Query ();
 
+
 		store.clear ();
 
 		debug ("Doing detailed search using store:%p", store);
@@ -311,8 +330,10 @@ public class Tracker.Needle {
 			query.limit = 1000;
 			query.criteria = search.get_text ();
 
+			print (search.get_text ());
+
 			try {
-				cursor = yield query.perform_async (type);
+				cursor = yield query.perform_async (type, null);
 
 				if (cursor == null) {
 					search_finished (store);
@@ -429,17 +450,12 @@ public class Tracker.Needle {
 		}
 
 		search_finished (store);
+		*/
 	}
 
-	private void search_finished (ListStore? store) {
-		// Hide spinner
-		spinner.stop ();
-		spinner_shell.hide ();
-
-		TreeModel model = (TreeModel) store;
-
+	private void search_finished (ResultStore? store) {
 		// Check if we have any results, if we don't change the view
-		if (model == null || model.iter_n_children (null) < 1) {
+		if (store == null || !store.has_results ()) {
 			sw_noresults.show ();
 			sw_icons.hide ();
 			sw_categories.hide ();
@@ -497,15 +513,10 @@ public class Tracker.Needle {
 
 		string str = search.get_text ();
 		string criteria = str.strip ();
-		ListStore store = get_store_for_active_view ();
-
-		if (criteria.length < 1) {
-			if (store != null) {
-				store.clear ();
-			}
+		ResultStore store = null;
 
+		if (criteria.length < 3) {
 			search_finished (store);
-
 			return false;
 		}
 
@@ -516,30 +527,27 @@ public class Tracker.Needle {
 
 		if (view_icons.active) {
 			sw_icons.show ();
+			store = sw_icons.store;
 		} else {
 			sw_icons.hide ();
 		}
 
 		if (view_categories.active) {
 			sw_categories.show ();
+			store = sw_categories.store;
 		} else {
 			sw_categories.hide ();
 		}
 
 		if (view_filelist.active) {
 			sw_filelist.show ();
+			store = sw_filelist.store;
 		} else {
 			sw_filelist.hide ();
 		}
 
-		// Show spinner
-		spinner_shell.show_all ();
-		spinner.start ();
-
-		if (view_categories.active) {
-			search_detailed (store);
-		} else {
-			search_simple (store);
+		if (store != null) {
+			store.search_term = search.get_text ();
 		}
 
 		return false;
diff --git a/src/tracker-needle/tracker-query.vala b/src/tracker-needle/tracker-query.vala
index b4daf40..97a167b 100644
--- a/src/tracker-needle/tracker-query.vala
+++ b/src/tracker-needle/tracker-query.vala
@@ -34,6 +34,87 @@ public class Tracker.Query {
 		FOLDERS
 	}
 
+        private string [] where_clauses = {
+        	// ALL
+                "WHERE { ?u fts:match \"%s\" . ?u nfo:belongsToContainer ?c ; tracker:available true . }",
+
+		// ALL_ONLY_IN_TITLES
+                "WHERE { ?u a nfo:FileDataObject ; nfo:belongsToContainer ?c ; tracker:available true . FILTER(fn:contains(fn:lower-case(nfo:fileName(?u)), \"$criteria_escaped_down\")) }",
+		// CONTACTS
+                "",
+
+		// APPLICATIONS
+                "WHERE {
+		   ?urn a nfo:Software ;
+		        fts:match \"%s\"
+		}",
+
+		// MUSIC
+                "WHERE {
+		   {
+		     ?urn nmm:musicAlbum ?match
+		   } UNION {
+		     ?urn nmm:performer ?match
+		   } UNION {
+		     ?urn a nfo:Audio .
+		     ?match a nfo:Audio
+		     FILTER (?urn = ?match)
+		   }
+		   ?match fts:match \"%s\" .
+		   ?urn nmm:performer [ nmm:artistName ?performer ] ;
+		         nmm:musicAlbum [ nie:title ?album ] ;
+		         nie:url ?tooltip
+		}",
+
+		// IMAGES
+                "WHERE {
+		   ?urn a nfo:Image ;
+		        nie:url ?tooltip ;
+		        fts:match \"%s\"
+		}",
+
+		// VIDEOS
+                "WHERE {
+		   ?urn a nfo:Video ;
+		        nie:url ?tooltip ;
+		        fts:match \"%s\" .
+		}",
+
+		// DOCUMENTS
+                "WHERE {
+		   ?urn a nfo:Document ;
+		        nie:url ?tooltip ;
+		        fts:match \"%s\" .
+		   OPTIONAL {
+		     ?urn nco:creator ?creator .
+		   }
+		   OPTIONAL {
+		     ?urn nco:publisher ?publisher .
+		   }
+		}",
+
+		// MAIL
+                "WHERE {
+		   ?urn a nmo:Email ;
+		        nmo:from ?sender ;
+		        nmo:to ?to ;
+		        fts:match \"%s\" .
+		}",
+
+		// CALENDAR
+                "",
+
+		// FOLDERS
+                "WHERE {
+		   ?urn a nfo:Folder ;
+		        nie:url ?tooltip ;
+		        fts:match \"%s\" .
+		   OPTIONAL {
+		     ?urn nfo:belongsToContainer ?parent .
+		   }
+		}"
+        };
+
 	public string criteria { get; set; }
 	public uint offset { get; set; }
 	public uint limit { get; set; }
@@ -42,6 +123,7 @@ public class Tracker.Query {
 	private static Sparql.Connection connection;
 
 	public Query () {
+
 		try {
 			connection = Sparql.Connection.get ();
 		} catch (Sparql.Error ea) {
@@ -53,7 +135,39 @@ public class Tracker.Query {
 		}
 	}
 
-	public async Sparql.Cursor? perform_async (Type query_type, Cancellable? cancellable = null) throws IOError
+        public async uint get_count_async (Type query_type,
+                                           Cancellable? cancellable = null) throws IOError
+        requires (connection != null) {
+		Sparql.Cursor cursor = null;
+
+		if (criteria == null || criteria.length < 1) {
+			warning ("Criteria was NULL or an empty string, no query performed");
+			return 0;
+		}
+
+                string criteria_escaped = Tracker.Sparql.escape_string (criteria);
+
+                query = "SELECT count(?urn) " + where_clauses[query_type].printf (criteria_escaped);
+
+		try {
+			cursor = yield connection.query_async (query, null);
+                        yield cursor.next_async ();
+		} catch (Sparql.Error ea) {
+			warning ("Could not run Sparql count query: %s", ea.message);
+		} catch (GLib.IOError eb) {
+			warning ("Could not run Sparql count query: %s", eb.message);
+		} catch (GLib.DBusError ec) {
+			warning ("Could not run Sparql count query: %s", ec.message);
+		} catch (GLib.Error ge) {
+			warning ("Could not run Sparql count query: %s", ge.message);
+                }
+
+                return (uint) cursor.get_integer (0);
+        }
+
+        public async Sparql.Cursor? perform_async (Type         query_type,
+                                                   string []    ?args,
+                                                   Cancellable? cancellable = null) throws IOError
 	requires (connection != null) {
 		Sparql.Cursor cursor = null;
 
@@ -68,178 +182,9 @@ public class Tracker.Query {
 		}
 
 		string criteria_escaped = Tracker.Sparql.escape_string (criteria);
-		string unknown = _("Unknown");
-
-		switch (query_type) {
-		case Type.ALL:
-			query = @"SELECT ?u nie:url(?u) tracker:coalesce(nie:title(?u), nfo:fileName(?u), \"$unknown\") nfo:fileLastModified(?u) nfo:fileSize(?u) nie:url(?c) WHERE { ?u fts:match \"$criteria_escaped\" . ?u nfo:belongsToContainer ?c ; tracker:available true . } ORDER BY DESC(fts:rank(?u)) OFFSET $offset LIMIT $limit";
-			break;
-			
-		case Type.ALL_ONLY_IN_TITLES:
-			string criteria_escaped_down = criteria_escaped.down();
-
-			query = @"SELECT ?u nie:url(?u) tracker:coalesce(nfo:fileName(?u), \"$unknown\") nfo:fileLastModified(?u) nfo:fileSize(?u) nie:url(?c) WHERE { ?u a nfo:FileDataObject ; nfo:belongsToContainer ?c ; tracker:available true . FILTER(fn:contains(fn:lower-case(nfo:fileName(?u)), \"$criteria_escaped_down\")) } ORDER BY DESC(nfo:fileName(?u)) OFFSET $offset LIMIT $limit";
-			break;
-
-		case Type.APPLICATIONS:
-			query = @"
-			        SELECT
-			          ?urn
-			          tracker:coalesce(nfo:softwareCmdLine(?urn), ?urn)
-			          tracker:coalesce(nie:title(?urn), nfo:fileName(?urn), \"$unknown\")
-			          nie:comment(?urn)
-			        WHERE {
-			          ?urn a nfo:Software ;
-			               fts:match \"$criteria_escaped\"
-			        }
-			        ORDER BY DESC(fts:rank(?urn)) DESC(nie:title(?urn))
-			        OFFSET $offset LIMIT $limit
-			        ";
-			break;
-
-		case Type.MUSIC:
-			query = @"
-			        SELECT
-			          ?song
-			          nie:url(?song)
-			          tracker:coalesce(nie:title(?song), nfo:fileName(?song), \"$unknown\")
-			          fn:string-join((?performer, ?album), \" - \")
-			          nfo:duration(?song)
-			          ?tooltip
-			        WHERE {
-			          ?match fts:match \"$criteria_escaped\"
-			          {
-			            ?song nmm:musicAlbum ?match
-			          } UNION {
-			            ?song nmm:performer ?match
-			          } UNION {
-			            ?song a nfo:Audio .
-			            ?match a nfo:Audio
-			            FILTER (?song = ?match)
-			          }
-			          ?song nmm:performer [ nmm:artistName ?performer ] ;
-			                nmm:musicAlbum [ nie:title ?album ] ;
-			                nie:url ?tooltip
-			        }
-			        ORDER BY DESC(fts:rank(?song)) DESC(nie:title(?song))
-			        OFFSET $offset LIMIT $limit
-			        ";
-			break;
 
-		case Type.IMAGES:
-			query = @"
-			        SELECT
-			          ?urn 
-			          nie:url(?urn) 
-			          tracker:coalesce(nie:title(?urn), nfo:fileName(?urn), \"$unknown\") 
-			          fn:string-join((nfo:height(?urn), nfo:width(?urn)), \" x \") 
-			          nfo:fileSize(?urn)
-			          ?tooltip
-			        WHERE {
-			          ?urn a nfo:Image ;
-			          nie:url ?tooltip ;
-			          fts:match \"$criteria_escaped\" 
-			        }
-			        ORDER BY DESC(fts:rank(?urn)) DESC(nie:title(?urn)) 
-			        OFFSET $offset LIMIT $limit
-			        ";
-			break;
-
-		case Type.VIDEOS:
-			query = @"
-			        SELECT
-			          ?urn 
-			          nie:url(?urn) 
-			          tracker:coalesce(nie:title(?urn), nfo:fileName(?urn), \"$unknown\") 
-			          \"\"
-			          nfo:duration(?urn)
-			          ?tooltip
-			        WHERE {
-			          ?urn a nfo:Video ;
-			          nie:url ?tooltip ;
-			          fts:match \"$criteria_escaped\" .
-			        }
-			        ORDER BY DESC(fts:rank(?urn)) DESC(nie:title(?urn)) 
-			        OFFSET $offset LIMIT $limit
-			        ";
-			break;
-
-		case Type.DOCUMENTS:
-//			          fn:concat(nco:pageCount(?urn), \" pages\")
-			string pages = _("Pages");
-			
-			query = @"
-			        SELECT
-			          ?urn 
-			          nie:url(?urn) 
-			          tracker:coalesce(nie:title(?urn), nfo:fileName(?urn), \"$unknown\") 
-			          tracker:coalesce(nco:fullname(?creator), nco:fullname(?publisher), \"\")
-			          fn:concat(nfo:pageCount(?urn), \" $pages\")
-			          ?tooltip
-			        WHERE {
-			          ?urn a nfo:Document ;
-			          nie:url ?tooltip ;
-			          fts:match \"$criteria_escaped\" .
-			          OPTIONAL {
-			            ?urn nco:creator ?creator .
-			          }
-			          OPTIONAL {
-			            ?urn nco:publisher ?publisher .
-			          }
-			        }
-			        ORDER BY DESC(fts:rank(?urn)) DESC(nie:title(?urn)) 
-			        OFFSET $offset LIMIT $limit
-			        ";
-			break;
-
-		case Type.MAIL:
-			string no_subject = _("No Subject");
-			string to = _("To");
-			
-			query = @"
-			        SELECT
-			          ?urn
-			          nie:url(?urn)
-			          tracker:coalesce(nco:fullname(?sender), nco:nickname(?sender), nco:emailAddress(?sender), \"$unknown\")
-			          tracker:coalesce(nmo:messageSubject(?urn), \"$no_subject\")
-			          nmo:receivedDate(?urn)
-			          fn:concat(\"$to: \", tracker:coalesce(nco:fullname(?to), nco:nickname(?to), nco:emailAddress(?to), \"$unknown\"))
-			        WHERE {
-			          ?urn a nmo:Email ;
-			          nmo:from ?sender ;
-			          nmo:to ?to ;
-			          fts:match \"$criteria_escaped\" .
-			        }
-			        ORDER BY DESC(fts:rank(?urn)) DESC(nmo:messageSubject(?urn)) DESC(nmo:receivedDate(?urn))
-			        OFFSET $offset LIMIT $limit
-			        ";
-			break;
-
-		case Type.FOLDERS:
-			query = @"
-			        SELECT
-			          ?urn
-			          nie:url(?urn)
-			          tracker:coalesce(nie:title(?urn), nfo:fileName(?urn), \"$unknown\")
-			          tracker:coalesce(nie:url(?parent), \"\")
-			          nfo:fileLastModified(?urn)
-			          ?tooltip
-			        WHERE {
-			          ?urn a nfo:Folder ;
-			          nie:url ?tooltip ;
-			          fts:match \"$criteria_escaped\" .
-			          OPTIONAL {
-			            ?urn nfo:belongsToContainer ?parent .
-			          }
-			        }
-			        ORDER BY DESC(fts:rank(?urn)) DESC(nie:title(?urn))
-			        OFFSET $offset LIMIT $limit
-			        ";
-			break;
-
-		default:
-			assert_not_reached ();
-		}
+		query = "SELECT " + string.joinv (" ", args) + " " + where_clauses[query_type].printf (criteria_escaped);
+		query += @" OFFSET $offset LIMIT $limit";
 
 		debug ("Running query: '%s'", query);
 
diff --git a/src/tracker-needle/tracker-result-store.vala b/src/tracker-needle/tracker-result-store.vala
new file mode 100644
index 0000000..1091421
--- /dev/null
+++ b/src/tracker-needle/tracker-result-store.vala
@@ -0,0 +1,611 @@
+//
+// Copyright 2010, Carlos Garnacho <carlos 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;
+
+public class Tracker.ResultStore : Gtk.TreeModel, GLib.Object {
+	private GLib.Cancellable cancellable;
+
+	private struct ResultNode {
+		string [] values;
+	}
+	private struct CategoryNode {
+		Tracker.Query.Type type;
+		string [] args;
+		ResultNode [] results;
+	}
+	private CategoryNode [] categories;
+
+	private class Operation : GLib.Object {
+		public CategoryNode *node;
+		public int offset;
+	}
+	private GenericArray<Operation> running_operations;
+	private GenericArray<Operation> delayed_operations;
+
+	private int n_columns;
+
+	private Operation * find_operation (GenericArray<Operation>  array,
+					    CategoryNode            *node,
+					    int                      offset) {
+		Operation op;
+		int i;
+
+		for (i = 0; i < array.length; i++) {
+			op = array[i];
+
+			if (op.node == node &&
+			    op.offset == offset) {
+				return op;
+			}
+		}
+
+		return null;
+	}
+
+	async void load_operation (Operation    op,
+				   Cancellable? cancellable) {
+		Tracker.Query query;
+		Sparql.Cursor cursor = null;
+		int i;
+
+		try {
+			cancellable.set_error_if_cancelled ();
+
+			query = new Tracker.Query ();
+			query.criteria = _search_term;
+			query.limit = 100;
+			query.offset = op.offset;
+
+			cursor = yield query.perform_async (op.node.type, op.node.args);
+
+			for (i = op.offset; i < op.offset + 100; i++) {
+				ResultNode *result;
+				TreeIter iter;
+				TreePath path;
+				bool b = false;
+				int j;
+
+				try {
+					b = yield cursor.next_async ();
+				} catch (GLib.Error ge) {
+					warning ("Could not fetch row: %s\n", ge.message);
+				}
+
+				if (!b) {
+					break;
+				}
+
+				result = &op.node.results[i];
+
+				for (j = 0; j < n_columns; j++) {
+					result.values[j] = cursor.get_string (j);
+				}
+
+				// Emit row-changed
+				iter = TreeIter ();
+				iter.user_data = op.node;
+				iter.user_data2 = result;
+				iter.user_data3 = i.to_pointer ();
+
+				path = this.get_path (iter);
+
+				row_changed (path, iter);
+			}
+
+			running_operations.remove (op);
+		} catch (GLib.IOError ie) {
+			warning ("Could not load items: %s\n", ie.message);
+			return;
+		}
+
+		if (delayed_operations.length > 0) {
+			Operation next_to_start;
+
+			// Take last added task from delayed queue and start it
+			next_to_start = delayed_operations[delayed_operations.length - 1];
+			delayed_operations.remove (next_to_start);
+			running_operations.add (next_to_start);
+
+			load_operation.begin (next_to_start, cancellable);
+		} else if (running_operations.length == 0) {
+			// finished processing
+			this.active = false;
+		}
+	}
+
+	private void add_operation (CategoryNode *cat,
+				    int           offset) {
+		Operation op = new Operation ();
+		Operation old;
+
+		op.node = cat;
+		op.offset = offset;
+
+		if (find_operation (running_operations, cat, offset) != null) {
+			// Operation already running
+			return;
+		}
+
+		// If the task is delayed, it will be either pushed
+	        // to the running queue, or reordered to be processed
+		// next in the delayed queue.
+		old = find_operation (delayed_operations, cat, offset);
+
+		if (old != null) {
+			delayed_operations.remove (old);
+		}
+
+		this.active = true;
+
+		// Running queue is limited to 2 simultaneous queries,
+		// anything after that will be added to a different queue.
+		if (running_operations.length < 2) {
+			running_operations.add (op);
+
+			// Start the operation right away
+			load_operation.begin (op, cancellable);
+		} else {
+			// Reorder the operation if it was already there, else just add
+			delayed_operations.add (op);
+		}
+	}
+
+	async void load_category (CategoryNode *cat,
+				  Cancellable?  cancellable) {
+		uint count = 0;
+
+		try {
+			cancellable.set_error_if_cancelled ();
+
+			Tracker.Query query = new Tracker.Query ();
+			query.criteria = _search_term;
+
+			count = yield query.get_count_async (cat.type);
+		} catch (GLib.IOError ie) {
+			warning ("Could not get count: %s\n", ie.message);
+			return;
+		}
+
+		if (count != 0) {
+			int i;
+
+			Gtk.TreeIter iter;
+			Gtk.TreePath path;
+
+			cat.results.resize ((int) count);
+
+			iter = TreeIter ();
+			iter.user_data = cat;
+
+			for (i = 0; i < count; i++) {
+				ResultNode *res;
+
+				res = &cat.results[i];
+				res.values = new string[n_columns];
+
+				iter.user_data2 = res;
+				iter.user_data3 = i.to_pointer ();
+				path = this.get_path (iter);
+
+				row_inserted (path, iter);
+			}
+		}
+
+		if (running_operations.length == 0) {
+			this.active = false;
+		}
+	}
+
+	private void clear_results () {
+		int i, j;
+
+		print ("POSCLAROOO\n");
+
+		for (i = 0; i < categories.length; i++) {
+			CategoryNode *cat = &categories[i];
+			TreeIter iter;
+			TreePath path;
+
+			iter = TreeIter ();
+			iter.user_data = cat;
+
+			for (j = 0; j < cat.results.length; j++) {
+				iter.user_data2 = &cat.results[j];
+				iter.user_data3 = j.to_pointer ();
+				path = get_path (iter);
+				print ("removing = %s\n", path.to_string ());
+
+				row_deleted (path);
+			}
+
+			iter.user_data2 = null;
+			iter.user_data3 = null;
+			path = get_path (iter);
+
+			row_changed (path, iter);
+
+			cat.results.resize (0);
+		}
+	}
+
+	private string _search_term;
+	public string search_term {
+		get {
+			return _search_term;
+		}
+		set {
+			int i;
+
+			_search_term = value;
+
+			if (cancellable != null) {
+				cancellable.cancel ();
+			}
+
+			clear_results ();
+			this.active = true;
+
+			for (i = 0; i < categories.length; i++) {
+				load_category.begin (&categories[i], cancellable);
+			}
+		}
+	}
+
+	public bool active {
+		get;
+		private set;
+	}
+
+	private int find_nth_category_index (CategoryNode *node,
+	                                     int           n) {
+		int i;
+
+		if (node == null) {
+			// Count from the first one
+			return n;
+		}
+
+		for (i = 0; i < categories.length; i++) {
+			CategoryNode *cat;
+
+			cat = &categories[i];
+
+			if (cat == node) {
+				return i + n;
+			}
+		}
+
+		return -1;
+	}
+
+	private int filled_categories_count () {
+		int i, n = 0;
+
+		for (i = 0; i < categories.length; i++) {
+			CategoryNode *cat;
+
+			cat = &categories[i];
+
+			if (cat.results.length > 0) {
+				n++;
+			}
+		}
+
+		return n;
+	}
+
+	public GLib.Type get_column_type (int index_) {
+		return typeof (string);
+	}
+
+	public Gtk.TreeModelFlags get_flags () {
+		return Gtk.TreeModelFlags.ITERS_PERSIST;
+	}
+
+	public bool get_iter (out Gtk.TreeIter iter,
+	                      Gtk.TreePath     path) {
+		unowned int [] indices = path.get_indices ();
+		CategoryNode *cat;
+
+		if (indices[0] >= categories.length) {
+			return false;
+		}
+
+		cat = &categories[indices[0]];
+		iter.user_data = cat;
+
+		if (path.get_depth () == 2) {
+			// it's a result
+			if (indices[1] >= cat.results.length) {
+				return false;
+			}
+
+			iter.user_data2 = &cat.results[indices[1]];
+			iter.user_data3 = indices[1].to_pointer ();
+		}
+
+		return true;
+	}
+
+	public int get_n_columns () {
+		return n_columns;
+	}
+
+	public Gtk.TreePath get_path (Gtk.TreeIter iter) {
+		TreePath path = new TreePath ();
+		CategoryNode *cat;
+		int i;
+
+		for (i = 0; i < categories.length; i++) {
+			cat = &categories[i];
+
+			if (cat == iter.user_data) {
+				path.append_index (i);
+				break;
+			}
+		}
+
+		if (iter.user_data2 != null) {
+			path.append_index ((int) iter.user_data3);
+		}
+
+		return path;
+	}
+
+	public void get_value (Gtk.TreeIter   iter,
+	                       int            column,
+	                       out GLib.Value value) {
+		CategoryNode *cat;
+
+		if (column > n_columns) {
+			value.init (typeof (string));
+			return;
+		}
+
+		cat = iter.user_data;
+		value.init (this.get_column_type (column));
+
+		if (iter.user_data2 == null) {
+			if (column == 2) {
+				switch (cat.type) {
+				case Tracker.Query.Type.APPLICATIONS:
+					value.set_string (_("Applications"));
+					break;
+				case Tracker.Query.Type.MUSIC:
+					value.set_string (_("Music"));
+					break;
+				case Tracker.Query.Type.IMAGES:
+					value.set_string (_("Images"));
+					break;
+				case Tracker.Query.Type.VIDEOS:
+					value.set_string (_("Videos"));
+					break;
+				case Tracker.Query.Type.DOCUMENTS:
+					value.set_string (_("Documents"));
+					break;
+				case Tracker.Query.Type.MAIL:
+					value.set_string (_("Mail"));
+					break;
+				case Tracker.Query.Type.FOLDERS:
+					value.set_string (_("Folders"));
+					break;
+				}
+			}
+		} else {
+			ResultNode *result;
+			int n_node;
+
+			result = iter.user_data2;
+			n_node = (int) iter.user_data3;
+
+			if (result.values[0] != null) {
+				value.set_string (result.values[column]);
+			} else {
+				n_node /= 100;
+				n_node *= 100;
+
+				add_operation (cat, n_node);
+			}
+		}
+	}
+
+	public bool iter_children (out Gtk.TreeIter iter,
+	                           Gtk.TreeIter?    parent) {
+		CategoryNode *cat;
+
+		if (parent == null) {
+			int i;
+
+			if (categories.length == 0) {
+				return false;
+			}
+
+			i = find_nth_category_index (null, 0);
+			cat = &categories[i];
+			iter.user_data = cat;
+			return true;
+		}
+
+		if (parent.user_data2 != null) {
+			return false;
+		}
+
+		cat = parent.user_data;
+
+		iter.user_data = cat;
+		iter.user_data2 = &cat.results[0];
+		iter.user_data3 = 0.to_pointer ();
+
+		return true;
+	}
+
+	public bool iter_has_child (Gtk.TreeIter iter) {
+		if (iter.user_data2 == null) {
+			CategoryNode *cat;
+
+			cat = iter.user_data;
+			return (cat.results.length > 0);
+		}
+
+		return false;
+	}
+
+	public int iter_n_children (Gtk.TreeIter? iter) {
+		if (iter == null) {
+			return filled_categories_count ();
+		}
+
+		if (iter.user_data2 != null) {
+			// a result doesn't have children
+			return 0;
+		}
+
+		CategoryNode *cat = iter.user_data;
+
+		return cat.results.length;
+	}
+
+	public bool iter_next (ref Gtk.TreeIter iter) {
+		CategoryNode *cat;
+		int i;
+
+		cat = iter.user_data;
+
+		if (iter.user_data2 == null) {
+			i = find_nth_category_index (cat, 1);
+
+			if (i < 0 || i >= categories.length) {
+				return false;
+			}
+
+			iter.user_data = &categories[i];
+
+			return true;
+		} else {
+			// Result node
+			i = (int) iter.user_data3;
+			i++;
+
+			if (i >= cat.results.length) {
+				return false;
+			}
+
+			iter.user_data2 = &cat.results[i];
+			iter.user_data3 = i.to_pointer ();
+
+			return true;
+		}
+	}
+
+	public bool iter_nth_child (out Gtk.TreeIter iter,
+	                            Gtk.TreeIter?    parent,
+	                            int              n) {
+		CategoryNode *cat;
+
+		if (parent != null) {
+			cat = parent.user_data;
+
+			if (n >= cat.results.length) {
+				return false;
+			}
+
+			iter.user_data = cat;
+			iter.user_data2 = &cat.results[n];
+			iter.user_data3 = n.to_pointer ();
+			return true;
+		} else {
+			int index;
+
+			index = find_nth_category_index (null, n);
+
+			if (index < 0 ||
+			    index >= categories.length) {
+				return false;
+			}
+
+			cat = &categories[index];
+			iter.user_data = cat;
+
+			return true;
+		}
+	}
+
+	public bool iter_parent (out Gtk.TreeIter iter,
+	                         Gtk.TreeIter     child) {
+		if (child.user_data2 != null) {
+			// child within a category
+			iter.user_data = child.user_data;
+			return true;
+		}
+
+		return false;
+	}
+
+	public void ref_node (Gtk.TreeIter iter) {
+	}
+
+	public void unref_node (Gtk.TreeIter iter) {
+	}
+
+	public ResultStore (int _n_columns) {
+		running_operations = new GenericArray<Operation?> ();
+		delayed_operations = new GenericArray<Operation?> ();
+		n_columns = _n_columns;
+	}
+
+	public void add_query (Tracker.Query.Type type, ...) {
+		var l = va_list ();
+		string str = null;
+		string [] args = null;
+		CategoryNode cat;
+		TreeIter iter;
+		TreePath path;
+
+		do {
+			str = l.arg ();
+
+			if (str != null) {
+				args += str;
+			}
+		} while (str != null);
+
+		if (args.length != n_columns) {
+			warning ("Arguments and number of columns doesn't match");
+			return;
+		}
+
+		cat = CategoryNode ();
+		cat.type = type;
+		cat.args = args;
+		cat.results = new ResultNode[0];
+
+		categories += cat;
+
+		iter = TreeIter ();
+		iter.user_data = &categories[categories.length - 1];
+		path = this.get_path (iter);
+
+		row_inserted (path, iter);
+	}
+
+	public bool has_results () {
+		return filled_categories_count () > 0;
+	}
+}
diff --git a/src/tracker-needle/tracker-view.vala b/src/tracker-needle/tracker-view.vala
index 814285d..27addbf 100644
--- a/src/tracker-needle/tracker-view.vala
+++ b/src/tracker-needle/tracker-view.vala
@@ -32,14 +32,14 @@ public class Tracker.View : ScrolledWindow {
 		private set;
 	}
 
-	public ListStore store {
+	public ResultStore store {
 		get;
 		private set;
 	}
 
 	private Widget view = null;
 
-	public View (Display? _display = Display.NO_RESULTS, ListStore? _store) {
+	public View (Display? _display = Display.NO_RESULTS, ResultStore? _store) {
 		set_policy (PolicyType.NEVER, PolicyType.AUTOMATIC);
 
 		display = _display;
@@ -49,17 +49,59 @@ public class Tracker.View : ScrolledWindow {
 			debug ("using store:%p", store);
 		} else {
 			// Setup treeview
-			store = new ListStore (10,
-			                       typeof (Gdk.Pixbuf),  // Icon small
-			                       typeof (Gdk.Pixbuf),  // Icon big
-			                       typeof (string),      // URN
-			                       typeof (string),      // URL
-			                       typeof (string),      // Title
-			                       typeof (string),      // Subtitle
-			                       typeof (string),      // Column 2
-			                       typeof (string),      // Column 3
-			                       typeof (string),      // Tooltip
-			                       typeof (bool));       // Category hint
+			store = new ResultStore (6);
+
+			store.add_query (Tracker.Query.Type.APPLICATIONS,
+			                 "?urn",
+			                 "tracker:coalesce(nfo:softwareCmdLine(?urn), ?urn)",
+			                 "tracker:coalesce(nie:title(?urn), nfo:fileName(?urn))",
+			                 "nie:comment(?urn)",
+			                 "\"\"",
+			                 "\"\"");
+
+			store.add_query (Tracker.Query.Type.IMAGES,
+			                 "?urn",
+			                 "nie:url(?urn)",
+			                 "tracker:coalesce(nie:title(?urn), nfo:fileName(?urn))",
+			                 "fn:string-join((nfo:height(?urn), nfo:width(?urn)), \" x \")",
+			                 "nfo:fileSize(?urn)",
+			                 "nie:url(?urn)");
+			store.add_query (Tracker.Query.Type.MUSIC,
+			                 "?urn",
+			                 "nie:url(?urn)",
+			                 "tracker:coalesce(nie:title(?urn), nfo:fileName(?urn))",
+			                 "fn:string-join((?performer, ?album), \" - \")",
+			                 "nfo:duration(?urn)",
+			                 "nie:url(?urn)");
+			store.add_query (Tracker.Query.Type.VIDEOS,
+			                 "?urn",
+			                 "nie:url(?urn)",
+			                 "tracker:coalesce(nie:title(?urn), nfo:fileName(?urn))",
+			                 "\"\"",
+			                 "nfo:duration(?urn)",
+			                 "nie:url(?urn)");
+			store.add_query (Tracker.Query.Type.DOCUMENTS,
+			                 "?urn",
+			                 "nie:url(?urn)",
+			                 "tracker:coalesce(nie:title(?urn), nfo:fileName(?urn))",
+			                 "tracker:coalesce(nco:fullname(?creator), nco:fullname(?publisher))",
+			                 "fn:concat(nfo:pageCount(?urn), \" Pages\")",
+			                 "nie:url(?urn)");
+			store.add_query (Tracker.Query.Type.MAIL,
+			                 "?urn",
+			                 "nie:url(?urn)",
+			                 "tracker:coalesce(nco:fullname(?sender), nco:nickname(?sender), nco:emailAddress(?sender))",
+			                 "tracker:coalesce(nmo:messageSubject(?urn))",
+			                 "nmo:receivedDate(?urn)",
+			                 "fn:concat(\"To: \", tracker:coalesce(nco:fullname(?to), nco:nickname(?to), nco:emailAddress(?to)))");
+			store.add_query (Tracker.Query.Type.FOLDERS,
+			                 "?urn",
+			                 "nie:url(?urn)",
+			                 "tracker:coalesce(nie:title(?urn), nfo:fileName(?urn))",
+			                 "tracker:coalesce(nie:url(?parent), \"\")",
+			                 "nfo:fileLastModified(?urn)",
+			                 "?tooltip");
+
 			debug ("Creating store:%p", store);
 		}
 
@@ -121,11 +163,13 @@ public class Tracker.View : ScrolledWindow {
 			tv.set_rules_hint (false);
 			tv.set_grid_lines (TreeViewGridLines.VERTICAL);
 			tv.set_headers_visible (true);
+			tv.set_fixed_height_mode (true);
 
 			var renderer1 = new CellRendererPixbuf ();
 			var renderer2 = new Tracker.CellRendererText ();
 
 			col = new TreeViewColumn ();
+			col.set_sizing (TreeViewColumnSizing.FIXED);
 			col.pack_start (renderer1, false);
 			col.add_attribute (renderer1, "pixbuf", 0);
 			renderer1.xpad = 5;
@@ -146,6 +190,7 @@ public class Tracker.View : ScrolledWindow {
 
 			var renderer3 = new Tracker.CellRendererText ();
 			col = new TreeViewColumn ();
+			col.set_sizing (TreeViewColumnSizing.FIXED);
 			col.pack_start (renderer3, true);
 			col.add_attribute (renderer3, "text", 6);
 			col.set_title (_("Last Changed"));
@@ -154,6 +199,7 @@ public class Tracker.View : ScrolledWindow {
 
 			var renderer4 = new Tracker.CellRendererText ();
 			col = new TreeViewColumn ();
+			col.set_sizing (TreeViewColumnSizing.FIXED);
 			col.pack_start (renderer4, true);
 			col.add_attribute (renderer4, "text", 7);
 			col.set_title (_("Size"));
@@ -168,49 +214,51 @@ public class Tracker.View : ScrolledWindow {
 			TreeView tv = (TreeView) view;
 
 			tv.set_model (store);
-			tv.set_tooltip_column (8);
+			tv.set_tooltip_column (5);
 			tv.set_rules_hint (false);
 			tv.set_grid_lines (TreeViewGridLines.NONE);
 			tv.set_headers_visible (false);
 
 			var renderer1 = new CellRendererPixbuf ();
-			var renderer2 = new Tracker.CellRendererText ();
+			var renderer2 = new Gtk.CellRendererText ();
 
 			col = new TreeViewColumn ();
-			col.pack_start (renderer1, false);
-			col.add_attribute (renderer1, "pixbuf", 0);
-			renderer1.xpad = 5;
-			renderer1.ypad = 5;
+			col.set_sizing (TreeViewColumnSizing.FIXED);
+			// col.pack_start (renderer1, false);
+			// col.add_attribute (renderer1, "pixbuf", 0);
+			// renderer1.xpad = 5;
+			// renderer1.ypad = 5;
 
 			col.pack_start (renderer2, true);
-			col.add_attribute (renderer2, "text", 4);
-			col.add_attribute (renderer2, "subtext", 5);
+			col.set_cell_data_func (renderer2, text_renderer_func);
+//			col.add_attribute (renderer2, "text", 1); //4);
+//			col.add_attribute (renderer2, "subtext", 5);
 			renderer2.ellipsize = Pango.EllipsizeMode.MIDDLE;
-			renderer2.show_fixed_height = true;
+//			renderer2.show_fixed_height = true;
 
 			col.set_title (_("Item"));
 			col.set_resizable (true);
 			col.set_expand (true);
 			col.set_sizing (TreeViewColumnSizing.AUTOSIZE);
-			col.set_cell_data_func (renderer1, cell_renderer_func);
-			col.set_cell_data_func (renderer2, cell_renderer_func);
+//			col.set_cell_data_func (renderer1, cell_renderer_func);
 			tv.append_column (col);
 
-//			var renderer3 = new Tracker.CellRendererText ();
+//			var renderer3 = new Gtk.CellRendererText ();
 //			col = new TreeViewColumn ();
 //			col.pack_start (renderer3, true);
-//			col.add_attribute (renderer3, "text", 6);
+//			col.add_attribute (renderer3, "text", 3);
 //			col.set_title (_("Item Detail"));
 //			col.set_cell_data_func (renderer3, cell_renderer_func);
 //			tv.append_column (col);
 
-			var renderer4 = new Tracker.CellRendererText ();
-			col = new TreeViewColumn ();
-			col.pack_start (renderer4, true);
-			col.add_attribute (renderer4, "text", 7);
-			col.set_title (_("Size"));
-			col.set_cell_data_func (renderer4, cell_renderer_func);
-			tv.append_column (col);
+// 			var renderer4 = new Tracker.CellRendererText ();
+// 			col = new TreeViewColumn ();
+// 			col.set_sizing (TreeViewColumnSizing.FIXED);
+// 			col.pack_start (renderer4, true);
+// 			col.add_attribute (renderer4, "text", 4);
+// 			col.set_title (_("Size"));
+//			col.set_cell_data_func (renderer4, cell_renderer_func);
+// 			tv.append_column (col);
 
 			break;
 		}
@@ -256,5 +304,29 @@ public class Tracker.View : ScrolledWindow {
 			cell.set ("cell-background-gdk", null);
 		}
 	}
+
+	private void text_renderer_func (CellLayout   cell_layout,
+	                                 CellRenderer cell,
+	                                 TreeModel    tree_model,
+	                                 TreeIter     iter) {
+		string text, subtext;
+		string markup = null;
+
+		tree_model.get (iter, 2, out text, 3, out subtext, -1);
+
+		if (text != null) {
+			markup = Markup.escape_text (text);
+		}
+
+		if (subtext != null) {
+			markup += "\n<small><span color='grey'>%s</span></small>".printf (Markup.escape_text (subtext));
+		}
+
+		if (markup == null) {
+			markup = "<span color='grey'>%s</span>".printf (_("Loading..."));
+		}
+
+		cell.set ("markup", markup);
+	}
 }
 



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