Index: src/plugins/tracker/totem-tracker-widget.c
===================================================================
--- src/plugins/tracker/totem-tracker-widget.c	(revisión: 4912)
+++ src/plugins/tracker/totem-tracker-widget.c	(copia de trabajo)
@@ -29,6 +29,7 @@
 #include <stdlib.h>
 #include <libintl.h>
 #include <tracker.h>
+#include <libtracker-gtk/tracker-tag-bar.h>
 #include <tracker-client.h>
 #include <glib/gi18n-lib.h>
 #include <libgnomevfs/gnome-vfs.h>
@@ -38,6 +39,7 @@
 #include "totem-cell-renderer-video.h"
 #include "totem-playlist.h"
 #include "totem-video-list.h"
+#include "tracker-tag-cloud.h"
 
 
 #define TRACKER_SERVICE			"org.freedesktop.Tracker"
@@ -60,6 +62,10 @@
 
 	GtkListStore *result_store;
 	TotemVideoList *result_list;
+	TrackerTagBar *tag_bar;
+	TrackerTagCloud *tag_cloud;
+
+	TrackerClient *client;
 };
 
 enum {
@@ -82,7 +88,9 @@
 						 guint property_id,
 						 const GValue *value,
 						 GParamSpec *pspec);
+static void do_search_cb (GtkWidget *search_widget, TotemTrackerWidget *widget);
 
+
 static void totem_tracker_widget_class_init (TotemTrackerWidgetClass *klass)
 {
 	GObjectClass *object_class;
@@ -120,6 +128,7 @@
 	}
 }
 
+/* Miscellaneous functions */
 static void populate_result (TotemTrackerWidget *widget, char *result)
 {
 	GtkTreeIter iter;
@@ -174,13 +183,73 @@
 
 	return count;
 }
+/* End of miscellaneous functions */
 
+/* Callback functions */
 struct SearchResultsData {
 	TotemTrackerWidget *widget;
 	char *search_text;
 	TrackerClient *client;
 };
 
+static void on_video_clicked_cb (GtkTreeView *tree, GtkTreePath *path, GtkTreeViewColumn *column,
+	TotemTrackerWidget *widget)
+{
+	GtkTreeIter iter;	
+	GValue file_value = {0,};
+	
+	/* Put visible the tag widget */
+	gtk_widget_show (GTK_WIDGET (widget->priv->tag_bar));
+
+	/* Get the path of the video and pass it to the tag bar widget */
+	gtk_tree_model_get_iter (GTK_TREE_MODEL (widget->priv->result_store), &iter, path);
+	gtk_tree_model_get_value (GTK_TREE_MODEL (widget->priv->result_store), &iter, FILE_COLUMN, &file_value);
+
+	tracker_tag_bar_set_uri (widget->priv->tag_bar, SERVICE_VIDEOS,
+				gnome_vfs_get_local_path_from_uri (g_value_get_string (&file_value)));
+
+	g_value_unset (&file_value);
+}
+
+static void on_tag_cloud_clicked (TrackerTagCloud *cloud, gchar *tag, TotemTrackerWidget *widget)
+{
+	gtk_entry_set_text (GTK_ENTRY (widget->priv->search_entry), tag);
+	do_search_cb (widget->priv->search_entry, widget);
+}
+
+static void tracker_keywords_get_list_cb (GPtrArray *tags, GError *error, TotemTrackerWidget *widget) 
+{
+	TrackerTagCloudModel *model;
+
+	if (error) {
+		g_warning ("Error getting tags : %s", error->message);
+		g_error_free (error);
+		return;
+	}
+
+	model = tracker_tag_cloud_model_new (10, 18);
+
+	guint i;
+	for (i = 0; i < tags->len; i++) {
+		gchar **names = tags->pdata [i];
+		if (names) {
+			gchar *name = names [0];
+			gchar *aux = names [1];
+		
+			if (strlen (name) > 2) {
+				guint c;
+				for (c = 0; c < atoi (aux); c++) {
+					tracker_tag_cloud_model_add_tag (model, name);
+				}
+			}
+		}
+	}
+
+	tracker_tag_cloud_set_model (TRACKER_TAG_CLOUD (widget->priv->tag_cloud), model);
+
+	tracker_disconnect (widget->priv->client);
+}
+
 static void search_results_cb (char **result, GError *error, gpointer userdata)
 {
 	struct SearchResultsData *data = (struct SearchResultsData*) userdata;
@@ -200,7 +269,8 @@
 		 * Showing 10-20 of 128 matches
 		 * This is similar to what web searches use, eg. Google on the top-right of their search results page show:
 		 * Personalized Results 1 - 10 of about 4,130,000 for foobar */
-		label = g_strdup_printf (ngettext("Showing %i - %i of %i match", "Showing %i - %i of %i matches", data->widget->priv->total_result_count),
+		label = g_strdup_printf (ngettext("Showing %i - %i of %i match", "Showing %i - %i of %i matches", 
+				data->widget->priv->total_result_count),
 					 data->widget->priv->current_result_page * TOTEM_TRACKER_MAX_RESULTS_SIZE, 
 					 next_page > data->widget->priv->total_result_count ? data->widget->priv->total_result_count : next_page,
 					 data->widget->priv->total_result_count);
@@ -222,7 +292,7 @@
 	g_free (data);
 }
 
-static void do_search (GtkWidget *button, TotemTrackerWidget *widget)
+static void do_search_cb (GtkWidget *search_widget, TotemTrackerWidget *widget)
 {
 	TrackerClient *client;
 	struct SearchResultsData *data;
@@ -256,22 +326,34 @@
 				   search_results_cb, data);
 }
 
-static void go_next (GtkWidget *button, TotemTrackerWidget *widget)
+static void on_next_clicked_cb (GtkWidget *button, TotemTrackerWidget *widget)
 {
 	if (widget->priv->current_result_page < widget->priv->total_result_count / TOTEM_TRACKER_MAX_RESULTS_SIZE)
 		widget->priv->current_result_page ++;
 
-	do_search (button, widget);
+	do_search_cb (button, widget);
 }
 
-static void go_previous (GtkWidget *button, TotemTrackerWidget *widget)
+static void on_previous_clicked_cb (GtkWidget *button, TotemTrackerWidget *widget)
 {
 	if (widget->priv->current_result_page > 0)
 		widget->priv->current_result_page --;
 
-	do_search (button, widget);
+	do_search_cb (button, widget);
 }
+/* End of callbacks functions */
 
+static void init_tag_cloud (TotemTrackerWidget *widget)
+{
+	widget->priv->client = tracker_connect (TRUE);
+	if (!widget->priv->client) {
+		g_warning ("Error trying to get the tracker client");
+		return;
+	}
+
+	tracker_keywords_get_list_async (widget->priv->client, SERVICE_VIDEOS, tracker_keywords_get_list_cb, widget);
+}
+
 static void init_result_list (TotemTrackerWidget *widget)
 {
 	/* Initialize the store result list */
@@ -311,6 +393,7 @@
 	GtkWidget *pager_box;		/* box that holds the next andd previous buttons */
 	GtkWidget *search_box;		/* the search box contains the search entry and the search button */
 	GtkScrolledWindow *scroll;	/* make the result list scrollable */
+	GtkExpander *tag_cloud_expander;
 
 	widget->priv = g_new0 (TotemTrackerWidgetPrivate, 1);
 
@@ -331,12 +414,25 @@
 	gtk_container_add (GTK_CONTAINER (search_box), widget->priv->search_button);	
 	gtk_box_pack_start (GTK_BOX (v_box), search_box, FALSE, FALSE, 2);
 
+	/* The tag cloud added to an expander */
+   	widget->priv->tag_cloud = tracker_tag_cloud_new ();
+	tag_cloud_expander = gtk_expander_new (_("Tag cloud"));
+	gtk_container_add (GTK_CONTAINER (tag_cloud_expander), widget->priv->tag_cloud);
+	gtk_box_pack_start (GTK_BOX (v_box), GTK_WIDGET (tag_cloud_expander), FALSE, FALSE, 2);	
+	
+	/* Initialize the tag cloud */
+	init_tag_cloud (widget);
+
 	/* Insert the result list and initialize the viewport */
 	scroll = GTK_SCROLLED_WINDOW (gtk_scrolled_window_new (NULL, NULL));
 	gtk_scrolled_window_set_policy (scroll, GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
 	gtk_scrolled_window_add_with_viewport (scroll, GTK_WIDGET (widget->priv->result_list));
 	gtk_container_add (GTK_CONTAINER (v_box), GTK_WIDGET (scroll));
 
+	/* Insert the tracker tag bar */
+	widget->priv->tag_bar = tracker_tag_bar_new ();
+	gtk_box_pack_start (GTK_BOX (v_box), GTK_WIDGET (widget->priv->tag_bar), FALSE, FALSE, 2);
+
 	/* Initialise the pager box */
 	pager_box = gtk_hbox_new (FALSE, 2);
 	widget->priv->next_button = gtk_button_new_from_stock (GTK_STOCK_GO_FORWARD);
@@ -360,14 +456,25 @@
 
 	/* Connect the search button clicked signal and the search entry  */
 	g_signal_connect (G_OBJECT (widget->priv->search_button), "clicked",
-			  G_CALLBACK (do_search), (gpointer) widget);
+			  G_CALLBACK (do_search_cb), (gpointer) widget);
 	g_signal_connect (G_OBJECT (widget->priv->search_entry), "activate",
-			  G_CALLBACK (do_search), (gpointer) widget);
+			  G_CALLBACK (do_search_cb), (gpointer) widget);
 	/* Connect the pager buttons */
 	g_signal_connect (G_OBJECT (widget->priv->next_button), "clicked",
-			  G_CALLBACK (go_next), (gpointer) widget);
+			  G_CALLBACK (on_next_clicked_cb), (gpointer) widget);
 	g_signal_connect (G_OBJECT (widget->priv->previous_button), "clicked",
-			  G_CALLBACK (go_previous), (gpointer) widget);
+			  G_CALLBACK (on_previous_clicked_cb), (gpointer) widget);
+
+	/* Connect the result list clicked */
+	g_signal_connect (G_OBJECT (widget->priv->result_list), "row-activated",
+			 G_CALLBACK (on_video_clicked_cb), (gpointer) widget);
+
+	/* Hide the tracker tag bar */
+	gtk_widget_hide (GTK_WIDGET (widget->priv->tag_bar));
+
+	/* Connect the tag cloud clicked signal */
+        g_signal_connect (widget->priv->tag_cloud, "clicked",
+                          G_CALLBACK (on_tag_cloud_clicked), widget);
 }
 
 GtkWidget *totem_tracker_widget_new (TotemObject *totem)
Index: src/plugins/tracker/tracker-tag-cloud-model.c
===================================================================
--- src/plugins/tracker/tracker-tag-cloud-model.c	(revisión: 0)
+++ src/plugins/tracker/tracker-tag-cloud-model.c	(revisión: 0)
@@ -0,0 +1,405 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+ *
+ * tracker-tag-cloud-model.c: Model for the TrackerTagCloud widget
+ *
+ * Copyright (C) 2007 Marko Anastasov <marko@marko.anastasov.name>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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 Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA  02110-1301, USA.
+ */
+
+#include <math.h>
+#include "tracker-tag-cloud-model.h"
+
+typedef struct _CloudStats CloudStats;
+typedef struct _TagData TagData;
+
+enum {
+	CHANGED,
+	LAST_SIGNAL
+};
+
+struct _CloudStats
+{
+	int font_range;
+
+	int min_tag_count;
+	int max_tag_count;
+
+	double min_log;
+	double max_log;
+	double log_range;
+};
+
+struct _TagData
+{
+    int count;
+    int font_size;
+};
+
+struct _TrackerTagCloudModelPrivate
+{
+	CloudStats *stats;
+
+	GHashTable *tags;
+
+	gint min_font_size;
+	gint max_font_size;
+};
+
+GType tracker_tag_cloud_model_get_type ();
+static void tracker_tag_cloud_model_dispose (GObject *object);
+static void tracker_tag_cloud_model_finalize (GObject *object);
+static void tracker_tag_cloud_model_install_signals (GObjectClass *gobject_class);
+static void cloud_stats_init (CloudStats *stats);
+
+static guint tag_cloud_model_signals[LAST_SIGNAL] = { 0 };
+
+static GObjectClass *parent_class = NULL;
+
+/*** GObject stuff ***/
+
+static void
+tracker_tag_cloud_model_class_init (TrackerTagCloudModelClass *klass)
+{
+	GObjectClass *gobject_class = (GObjectClass *) klass;
+
+	parent_class = g_type_class_peek_parent (klass);
+
+	gobject_class->dispose  = tracker_tag_cloud_model_dispose;
+	gobject_class->finalize = tracker_tag_cloud_model_finalize;
+
+	tracker_tag_cloud_model_install_signals (gobject_class);
+
+	klass->changed = NULL;
+}
+
+static void
+tracker_tag_cloud_model_install_signals (GObjectClass *gobject_class)
+{
+	tag_cloud_model_signals[CHANGED] =
+		g_signal_new ("changed",
+			      G_TYPE_FROM_CLASS (gobject_class),
+			      G_SIGNAL_RUN_FIRST,
+			      G_STRUCT_OFFSET (TrackerTagCloudModelClass,
+					       changed),
+			      NULL, NULL,
+			      g_cclosure_marshal_VOID__VOID,
+			      G_TYPE_NONE,
+			      0);
+}
+
+static void
+tracker_tag_cloud_model_init (TrackerTagCloudModel *model,
+			      TrackerTagCloudModelClass *class)
+{
+	g_return_if_fail (TRACKER_IS_TAG_CLOUD_MODEL (model));
+
+	/* allocate private data */
+	model->priv = g_new0 (TrackerTagCloudModelPrivate, 1);
+
+	model->priv->stats = g_new0 (CloudStats, 1);
+	cloud_stats_init (model->priv->stats);
+
+	model->priv->tags = g_hash_table_new_full (g_str_hash, g_str_equal,
+						   g_free, g_free);
+}
+
+static void
+tracker_tag_cloud_model_dispose (GObject *object)
+{
+	/* TrackerTagCloudModel *model = (TrackerTagCloudModel *) object; */
+
+	G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+tracker_tag_cloud_model_finalize (GObject *object)
+{
+	TrackerTagCloudModel *model = (TrackerTagCloudModel *) object;
+
+	g_free (model->priv->stats);
+	model->priv->stats = NULL;
+
+	g_hash_table_destroy (model->priv->tags);
+	model->priv->tags = NULL;
+
+	G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+GType
+tracker_tag_cloud_model_get_type (void)
+{
+	static GType type = 0;
+
+	if (type == 0) {
+		static const GTypeInfo info = {
+			sizeof (TrackerTagCloudModelClass),
+			(GBaseInitFunc) NULL,       /* base_init */
+			(GBaseFinalizeFunc) NULL,   /* base_finalize */
+			(GClassInitFunc) tracker_tag_cloud_model_class_init,
+			NULL,   /* class_finalize */
+			NULL,   /* class_data */
+			sizeof (TrackerTagCloudModel),
+			0,      /* n_preallocs */
+			(GInstanceInitFunc) tracker_tag_cloud_model_init
+		};
+
+		type = g_type_register_static (G_TYPE_OBJECT,
+					       "TrackerTagCloudModel",
+					       &info, 0);
+	}
+
+	return type;
+}
+
+/*** Implementation ***/
+
+static void
+cloud_stats_init (CloudStats *stats)
+{
+	if (! stats) return;
+
+	stats->font_range = 0;
+	stats->min_tag_count = 0;
+	stats->max_tag_count = 0;
+	stats->min_log = 0;
+	stats->max_log = 0;
+	stats->log_range = 1;
+}
+
+/* The following two functions have been copied from tracker-indexer.c */
+
+static gint
+prepend_key_pointer (gpointer key, gpointer value, gpointer data)
+{
+  	GSList **plist = data;
+  	*plist = g_slist_prepend (*plist, key);
+  	return 1;
+}
+
+
+static GSList * 
+g_hash_table_key_slist (GHashTable *table)
+{
+  	GSList *rv = NULL;
+  	g_hash_table_foreach (table, (GHFunc) prepend_key_pointer, &rv);
+  	return rv;
+}
+
+typedef struct
+{
+	int min;
+	int max;
+} TagCountRecord;
+
+static void
+find_min_max_tag_count (const gchar *key,
+			const TagData *tag_data,
+			TagCountRecord *count_record)
+{
+	if (tag_data->count < count_record->min) {
+		count_record->min = tag_data->count;
+		return;
+	}
+
+	if (tag_data->count > count_record->max)
+		count_record->max = tag_data->count;
+}
+
+static void
+calculate_stats (TrackerTagCloudModel *model)
+{
+	CloudStats *stats;
+	GSList *keys = NULL;
+	TagCountRecord *count_record = NULL;
+	gchar *first_key;
+	TagData *first_tag_data_item;
+
+	stats = model->priv->stats;
+
+	stats->font_range =
+		model->priv->max_font_size - model->priv->min_font_size;
+
+	if (g_hash_table_size (model->priv->tags) == 0) return;
+
+	/* Find the min and max tag count values */
+	keys = g_hash_table_key_slist (model->priv->tags);
+	count_record = g_new0 (TagCountRecord, 1);
+
+	first_key = (gchar *) g_slist_nth_data (keys, 0);
+
+	first_tag_data_item = (TagData *)
+		g_hash_table_lookup (model->priv->tags, first_key);
+	count_record->min = first_tag_data_item->count;
+	count_record->max = first_tag_data_item->count;
+
+	g_hash_table_foreach (model->priv->tags,
+			      (GHFunc) find_min_max_tag_count,
+			      count_record);
+
+	/* Update stats */
+	stats->min_tag_count = count_record->min;
+	stats->max_tag_count = count_record->max;
+
+	stats->min_log = log(count_record->min);
+	stats->max_log = log(count_record->max);
+
+	if (stats->max_log == stats->min_log)
+		stats->log_range = 1;
+	else
+		stats->log_range = stats->max_log - stats->min_log;
+
+	g_free (count_record);
+}
+
+static void
+calculate_font_size (const gchar *key,
+		     TagData *tag_data,
+		     TrackerTagCloudModel *model)
+{
+	CloudStats *stats;
+	gdouble count_ratio;
+
+	stats = model->priv->stats;
+
+	count_ratio =
+		(log(tag_data->count) - stats->min_log) / stats->log_range;
+
+	tag_data->font_size =
+		model->priv->min_font_size +
+		(gint) (stats->font_range * count_ratio);
+}
+
+static void
+update_font_sizes (TrackerTagCloudModel *model)
+{
+	g_hash_table_foreach (model->priv->tags,
+			      (GHFunc) calculate_font_size,
+			      model);
+}
+
+static void
+update_data_and_notify (TrackerTagCloudModel *model)
+{
+	calculate_stats (model);
+	update_font_sizes (model);
+
+	g_signal_emit (model, tag_cloud_model_signals [CHANGED], 0);
+}
+
+TrackerTagCloudModel *
+tracker_tag_cloud_model_new (gint min_font_size, gint max_font_size)
+{
+	TrackerTagCloudModel *model;
+
+	model = g_object_new (TYPE_TRACKER_TAG_CLOUD_MODEL, NULL);
+
+	model->priv->min_font_size = min_font_size;
+	model->priv->max_font_size = max_font_size;
+  
+	return model;
+}
+
+void
+tracker_tag_cloud_model_add_tag (TrackerTagCloudModel* model, const gchar* tag)
+{
+	TagData* tag_data = NULL;
+	gchar *tag_str;
+
+	g_return_if_fail (TRACKER_IS_TAG_CLOUD_MODEL (model));
+
+	if ((tag_data = g_hash_table_lookup (model->priv->tags, tag)) == NULL) {
+		tag_data = g_new0 (TagData, 1);
+		tag_data->count = 1;
+		tag_str = g_strdup (tag);
+		g_hash_table_insert (model->priv->tags, tag_str, tag_data);
+	} else {
+		tag_data->count++;
+	}
+
+	update_data_and_notify (model);
+}
+
+static void
+load_font_size_hash (const gchar *key,
+		     const TagData *tag_data,
+		     GHashTable *hash)
+{
+	gchar *key_copy = NULL;
+	int *count = NULL;
+
+	key_copy = g_strdup (key);
+
+	count = g_new0 (int, 1);
+	*count = tag_data->font_size;
+
+	g_hash_table_insert (hash, key_copy, count);
+}
+
+GHashTable *
+tracker_tag_cloud_model_get_font_sizes (TrackerTagCloudModel* model)
+{
+	GHashTable *hash = NULL;
+
+	g_return_val_if_fail (TRACKER_IS_TAG_CLOUD_MODEL (model), NULL);
+
+	hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+
+	g_hash_table_foreach (model->priv->tags,
+			      (GHFunc) load_font_size_hash,
+			      hash);
+
+	return hash;
+}
+
+gint
+tracker_tag_cloud_model_get_min_font_size (TrackerTagCloudModel* model)
+{
+	g_return_val_if_fail (TRACKER_IS_TAG_CLOUD_MODEL (model), 0);
+
+	return model->priv->min_font_size;
+}
+
+void
+tracker_tag_cloud_model_set_min_font_size (TrackerTagCloudModel* model,
+					   gint value)
+{
+	g_return_if_fail (TRACKER_IS_TAG_CLOUD_MODEL (model));
+	g_return_if_fail (value > 0);
+
+	model->priv->min_font_size = value;
+
+	update_data_and_notify (model);
+}
+
+gint tracker_tag_cloud_model_get_max_font_size (TrackerTagCloudModel* model)
+{
+	g_return_val_if_fail (TRACKER_IS_TAG_CLOUD_MODEL (model), 0);
+
+	return model->priv->max_font_size;
+}
+
+void
+tracker_tag_cloud_model_set_max_font_size (TrackerTagCloudModel* model,
+					   gint value)
+{
+	g_return_if_fail (TRACKER_IS_TAG_CLOUD_MODEL (model));
+	g_return_if_fail (value > 0);
+
+	model->priv->max_font_size = value;
+
+	update_data_and_notify (model);
+}
Index: src/plugins/tracker/Makefile.am
===================================================================
--- src/plugins/tracker/Makefile.am	(revisión: 4912)
+++ src/plugins/tracker/Makefile.am	(copia de trabajo)
@@ -1,5 +1,6 @@
 modules_flags = -export_dynamic -avoid-version -module
 
+
 plugindir = $(PLUGINDIR)/tracker
 plugin_LTLIBRARIES = libtracker.la
 
@@ -20,9 +21,9 @@
 	-DTOTEM_PLUGIN_DIR=\""$(libdir)/totem/plugins"\"\
 	$(DISABLE_DEPRECATED)
 
-libtracker_la_SOURCES = totem-tracker.c totem-tracker-widget.c
+libtracker_la_SOURCES = totem-tracker.c totem-tracker-widget.c tracker-tag-cloud.c tracker-tag-cloud-model.c
 libtracker_la_LDFLAGS = $(modules_flags)
-libtracker_la_LIBADD = -ltrackerclient
+libtracker_la_LIBADD = -ltrackerclient -ltracker-gtk -lgoocanvas
 libtracker_la_CPPFLAGS = $(common_defines)
 
 libtracker_la_CFLAGS = \
@@ -30,6 +31,7 @@
 	$(WARN_CFLAGS)		\
 	$(DBUS_CFLAGS)		\
 	$(AM_CFLAGS)		\
+	-I/usr/include/goocanvas-1.0 \
 	-I$(top_srcdir)/src	\
 	-I$(top_srcdir)/src/plugins
 
Index: src/plugins/tracker/tracker-tag-cloud.c
===================================================================
--- src/plugins/tracker/tracker-tag-cloud.c	(revisión: 0)
+++ src/plugins/tracker/tracker-tag-cloud.c	(revisión: 0)
@@ -0,0 +1,694 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+ *
+ * tracker-tag-cloud.c: Tracker tag cloud widget
+ *
+ * Copyright (C) 2007 Marko Anastasov <marko@marko.anastasov.name>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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 Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA  02110-1301, USA.
+ */
+
+#include <goocanvas.h>
+#include "tracker-tag-cloud.h"
+
+#define TRACKER_TAG_CLOUD_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), TRACKER_TYPE_TAG_CLOUD, TrackerTagCloudPrivate))
+
+
+#define TAG_SPACING 8
+#define TAG_BORDER_MIN 5
+#define TAG_BORDER_MAX (TAG_BORDER_MIN * 4)
+
+enum FILL_COLOUR
+{
+	FILL_COLOUR_BLUE       = 0x0000ffff,
+	FILL_COLOUR_LIGHT_BLUE = 0x6060ffff,
+	FILL_COLOUR_RED        = 0xff0000ff
+};
+
+/* We want to give the canvas some space smaller then what the scrolled
+ * window has been allocated, so that the scroll bars do not show up by default.
+ */
+#define CANVAS_OFFSET 30
+
+typedef struct _TagItem TagItem;
+typedef struct _ButtonPressCbData ButtonPressCbData;
+
+enum
+{
+	PROP_0,
+	PROP_RANDOMIZE_BORDERS,
+	PROP_UNDERLINE_TAGS,
+	PROP_LIGHTEN_RARE_TAGS
+};
+
+enum
+{
+	CLICKED,
+	LAST_SIGNAL
+};
+
+struct _TagItem
+{
+	GooCanvasItem *text_item;
+	gchar *tag;
+	guint fill_colour;
+};
+
+struct _TrackerTagCloudPrivate
+{
+	GSList *tag_items;
+	GooCanvasItem *root_item;
+
+	TrackerTagCloudModel *model;
+
+	GRand *grand;
+	gboolean randomize_borders;
+
+	gboolean underline_tags;
+	gboolean lighten_rare_tags;
+
+	int alloc_width;
+	int alloc_height;
+
+	GSList *button_press_data;
+};
+
+static void tracker_tag_cloud_finalize (GObject *obj);
+static void tracker_tag_cloud_init (TrackerTagCloud *cloud);
+static void tracker_tag_cloud_install_signals (GObjectClass *gobject_class);
+static void tracker_tag_cloud_install_properties (GObjectClass *gobject_class);
+static void tracker_tag_cloud_get_property (GObject *object,
+					    guint prop_id,
+					    GValue *value,
+					    GParamSpec *pspec);
+static void tracker_tag_cloud_set_property (GObject *object,
+					    guint prop_id,
+					    const GValue *value,
+					    GParamSpec *pspec);
+static void tracker_tag_cloud_size_allocate (GtkWidget *widget,
+					     GtkAllocation *allocation);
+static void free_tag_items_slist (GSList **list);
+static void free_button_press_cb_data (GSList **list);
+static void draw_tags (TrackerTagCloud *cloud);
+
+static GtkScrolledWindowClass *parent_class = NULL;
+static guint tag_cloud_signals[LAST_SIGNAL] = { 0 };
+
+G_DEFINE_TYPE(TrackerTagCloud, tracker_tag_cloud, GTK_TYPE_SCROLLED_WINDOW);
+
+static void
+tracker_tag_cloud_class_init (TrackerTagCloudClass *klass)
+{
+	GObjectClass   *gobject_class = G_OBJECT_CLASS (klass);
+	GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+	parent_class = g_type_class_peek_parent (klass);
+
+	gobject_class->finalize = tracker_tag_cloud_finalize;
+
+	widget_class->size_allocate = tracker_tag_cloud_size_allocate;
+
+	gobject_class->set_property = tracker_tag_cloud_set_property;
+	gobject_class->get_property = tracker_tag_cloud_get_property;
+
+	g_type_class_add_private (klass, sizeof (TrackerTagCloudPrivate));
+
+	tracker_tag_cloud_install_signals (gobject_class);
+	tracker_tag_cloud_install_properties (gobject_class);
+}
+
+static void
+tracker_tag_cloud_install_signals (GObjectClass *gobject_class)
+{
+	tag_cloud_signals[CLICKED] =
+		g_signal_new ("clicked",
+			      G_TYPE_FROM_CLASS (gobject_class),
+			      G_SIGNAL_RUN_FIRST,
+			      G_STRUCT_OFFSET (TrackerTagCloudClass,
+					       clicked),
+			      NULL, NULL,
+			      g_cclosure_marshal_VOID__POINTER,
+			      G_TYPE_NONE,
+			      1,
+			      G_TYPE_POINTER);
+}
+
+static void
+tracker_tag_cloud_install_properties (GObjectClass *gobject_class)
+{
+	g_object_class_install_property (gobject_class,
+					 PROP_RANDOMIZE_BORDERS,
+					 g_param_spec_boolean (
+						 "randomize-borders",
+						 "Randomize borders",
+						 "Whether the borders of tag lines are of random length",
+						 TRUE,
+						 G_PARAM_READWRITE));
+
+	g_object_class_install_property (gobject_class,
+					 PROP_UNDERLINE_TAGS,
+					 g_param_spec_boolean (
+						 "underline-tags",
+						 "Underline tags",
+						 "Whether to display the tags underlined",
+						 FALSE,
+						 G_PARAM_READWRITE));
+
+	g_object_class_install_property (gobject_class,
+					 PROP_LIGHTEN_RARE_TAGS,
+					 g_param_spec_boolean (
+						 "lighten-rare-tags",
+						 "Lighten rare tags",
+						 "Whether to draw the tags of half the largest font size or less in a lighter shade",
+						 TRUE,
+						 G_PARAM_READWRITE));
+}
+
+static void
+tracker_tag_cloud_finalize (GObject *obj)
+{
+	TrackerTagCloud *cloud = (TrackerTagCloud *) obj;
+	TrackerTagCloudPrivate *priv = TRACKER_TAG_CLOUD_GET_PRIVATE (cloud);
+
+	free_tag_items_slist (&(priv->tag_items));
+	g_rand_free (priv->grand);
+	free_button_press_cb_data (&(priv->button_press_data));
+
+	if (G_OBJECT_CLASS(parent_class)->finalize != NULL)
+		G_OBJECT_CLASS(parent_class)->finalize(obj);
+}
+
+static void
+tracker_tag_cloud_init (TrackerTagCloud *cloud)
+{
+	TrackerTagCloudPrivate *priv = TRACKER_TAG_CLOUD_GET_PRIVATE (cloud);
+
+	cloud->canvas = NULL;
+
+	priv->tag_items = NULL;
+	priv->root_item = NULL;
+
+	priv->grand = g_rand_new ();
+
+	priv->randomize_borders = TRUE;
+	priv->underline_tags = FALSE;
+	priv->lighten_rare_tags = TRUE;
+
+	priv->alloc_width = 0;
+	priv->alloc_height = 0;
+
+	priv->button_press_data = NULL;
+
+	gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (cloud),
+					     GTK_SHADOW_IN);
+
+	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (cloud),
+					GTK_POLICY_AUTOMATIC,
+					GTK_POLICY_AUTOMATIC);
+}
+
+static void
+tracker_tag_cloud_get_property (GObject *object,
+				guint prop_id,
+				GValue *value,
+				GParamSpec *pspec)
+{
+	TrackerTagCloud *cloud;
+	TrackerTagCloudPrivate *priv;
+  
+	cloud = TRACKER_TAG_CLOUD (object);
+	priv = TRACKER_TAG_CLOUD_GET_PRIVATE (cloud);
+
+	switch (prop_id) {
+	case PROP_RANDOMIZE_BORDERS:
+		g_value_set_boolean (value, priv->randomize_borders);
+		break;
+	case PROP_UNDERLINE_TAGS:
+		g_value_set_boolean (value, priv->underline_tags);
+		break;
+	case PROP_LIGHTEN_RARE_TAGS:
+		g_value_set_boolean (value, priv->lighten_rare_tags);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+tracker_tag_cloud_set_property (GObject *object,
+				guint prop_id,
+				const GValue *value,
+				GParamSpec *pspec)
+{
+	TrackerTagCloud *cloud;
+	TrackerTagCloudPrivate *priv;
+  
+	cloud = TRACKER_TAG_CLOUD (object);
+	priv = TRACKER_TAG_CLOUD_GET_PRIVATE (cloud);
+
+	switch (prop_id) {
+	case PROP_RANDOMIZE_BORDERS:
+		priv->randomize_borders = g_value_get_boolean (value);
+		break;
+	case PROP_UNDERLINE_TAGS:
+		priv->underline_tags = g_value_get_boolean (value);
+		break;
+	case PROP_LIGHTEN_RARE_TAGS:
+		priv->lighten_rare_tags = g_value_get_boolean (value);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+GtkWidget *
+tracker_tag_cloud_new ()
+{
+	GtkWidget *widget;
+
+	widget = g_object_new (TRACKER_TYPE_TAG_CLOUD, NULL);
+
+	return widget;
+}
+
+GtkWidget *
+tracker_tag_cloud_new_with_model (TrackerTagCloudModel *model)
+{
+	GtkWidget *widget;
+
+	widget = g_object_new (TRACKER_TYPE_TAG_CLOUD, NULL);
+	tracker_tag_cloud_set_model (TRACKER_TAG_CLOUD (widget), model);
+
+	return widget;
+}
+
+static void
+tracker_tag_cloud_size_allocate (GtkWidget *widget, GtkAllocation *alloc)
+{
+	TrackerTagCloud *cloud = (TrackerTagCloud *) widget;
+	TrackerTagCloudPrivate *priv = TRACKER_TAG_CLOUD_GET_PRIVATE (cloud);
+	gboolean redraw;
+
+	if (! cloud->canvas) {
+		cloud->canvas = goo_canvas_new();
+
+		gtk_widget_show (GTK_WIDGET (cloud->canvas));
+		gtk_container_add (GTK_CONTAINER (cloud), cloud->canvas);
+
+		priv->root_item =
+			goo_canvas_get_root_item (GOO_CANVAS (cloud->canvas));
+	}
+
+	/* We need to redraw (and re-order) the tags if the widget
+	 * has been resized. 
+	 */
+
+	redraw = ((priv->alloc_width != alloc->width) ||
+		  (priv->alloc_height != alloc->height));
+
+	if (! redraw) {
+		GTK_WIDGET_CLASS(parent_class)->size_allocate (widget, alloc);
+		return;
+	}
+
+	priv->alloc_width = alloc->width;
+	priv->alloc_height = alloc->height;
+
+	draw_tags (cloud);
+
+	GTK_WIDGET_CLASS(parent_class)->size_allocate (widget, alloc);
+}
+
+static void
+model_changed_cb (TrackerTagCloudModel *model, TrackerTagCloud *cloud)
+{
+	draw_tags (cloud);
+}
+
+void
+tracker_tag_cloud_set_model (TrackerTagCloud *cloud,
+			     TrackerTagCloudModel *model)
+{
+	g_return_if_fail (TRACKER_IS_TAG_CLOUD (cloud));
+	g_return_if_fail (TRACKER_IS_TAG_CLOUD_MODEL (model));
+
+	TrackerTagCloudPrivate *priv = TRACKER_TAG_CLOUD_GET_PRIVATE (cloud);
+
+	priv->model = model;
+
+	if (cloud->canvas) draw_tags (cloud);
+
+	g_signal_connect (model,
+			  "changed",
+			  (GCallback) model_changed_cb,
+			  cloud);
+}
+
+/* Struct carrying information for the tag line drawing */
+typedef struct
+{
+	TrackerTagCloud *cloud;
+
+	gdouble canvas_left;
+	gdouble canvas_top;
+	gdouble canvas_right;
+	gdouble canvas_bottom;
+	gint    visible_width;
+	gdouble line_height;
+
+	gint    bold_colour_limit;
+	gint    current_line;
+	gint    tags_in_current_line;
+	gdouble text_item_x;
+	gdouble text_item_y;
+	gint    last_border;
+} TagLineData;
+
+static TagLineData *
+tag_line_data_new (TrackerTagCloud *cloud)
+{
+	TagLineData *data;
+
+	data = g_new0 (TagLineData, 1);
+
+	data->cloud = cloud;
+	data->bold_colour_limit = 0;
+	data->current_line = 0;
+	data->tags_in_current_line = 0;
+	data->text_item_x = TAG_BORDER_MIN;
+	data->text_item_y = 0;
+	data->last_border = TAG_BORDER_MIN;
+
+	return data;
+}
+
+struct _ButtonPressCbData
+{
+	TrackerTagCloud *cloud;
+	TagItem *tag_item;
+};
+
+static ButtonPressCbData *
+button_press_cb_data_new (TrackerTagCloud *cloud, TagItem *tag_item)
+{
+	ButtonPressCbData *data;
+
+	data = g_new0 (ButtonPressCbData, 1);
+	data->cloud = cloud;
+	data->tag_item = tag_item;
+
+	return data;
+}
+
+static void
+free_button_press_cb_data (GSList **list)
+{
+	if (list == NULL) return;
+
+	GSList *it;
+
+	for (it = *list; it; it = it->next) {
+		ButtonPressCbData *bp_data = (ButtonPressCbData *) it->data;
+		g_free (bp_data);
+	}
+
+	g_slist_free (*list);
+}
+
+static gboolean
+button_press_cb (GooCanvasItem* item,
+		 GooCanvasItem* target,
+		 GdkEventButton* event,
+		 ButtonPressCbData* data)
+{
+	g_signal_emit (data->cloud,
+		       tag_cloud_signals [CLICKED],
+		       0,
+		       data->tag_item->tag);
+
+	return TRUE;
+}
+
+static gboolean
+enter_notify_cb (GooCanvasItem* item,
+		 GooCanvasItem* target,
+		 GdkEventMotion* event,
+		 TagItem* tag_item)
+{
+	g_object_set (item, "fill_color_rgba", FILL_COLOUR_RED, NULL);
+
+	return FALSE;
+}
+
+static gboolean
+leave_notify_cb (GooCanvasItem* item,
+		 GooCanvasItem* target,
+		 GdkEventMotion* event,
+		 TagItem* tag_item)
+{
+	g_object_set (item, "fill_color_rgba", tag_item->fill_colour, NULL);
+
+	return FALSE;
+}
+
+static void
+create_lines (const gchar *tag, int *font_size, TagLineData *data)
+{
+	gchar *tag_font_desc;
+	gchar *tag_text;
+	guint fill_colour = FILL_COLOUR_BLUE;
+
+	GooCanvasItem *text_item;
+
+	GooCanvasBounds bounds;
+	gint text_item_width;
+
+	TrackerTagCloud *cloud = data->cloud;
+	TrackerTagCloudPrivate *priv = TRACKER_TAG_CLOUD_GET_PRIVATE (cloud);
+
+	TagItem *new_tag_item;
+	gboolean start_new_line;
+
+	tag_font_desc = g_strdup_printf ("Bitstream vera sans %d", *font_size);
+
+	if (priv->underline_tags)
+		tag_text = g_strdup_printf ("<u> %s </u>", tag);
+	else
+		tag_text = g_strdup (tag);
+
+	data->text_item_y = (data->current_line + 1) * data->line_height;
+
+	if (priv->lighten_rare_tags &&
+	    (*font_size <= data->bold_colour_limit))
+		fill_colour = FILL_COLOUR_LIGHT_BLUE;
+
+	text_item = goo_canvas_text_new (priv->root_item,
+					 tag_text,
+					 data->text_item_x,
+					 data->text_item_y,
+					 -1,
+					 GTK_ANCHOR_WEST,
+					 "fill-color-rgba", fill_colour,
+					 "font", tag_font_desc,
+					 "use-markup", TRUE,
+					 NULL);
+
+	g_free (tag_text);
+	g_free (tag_font_desc);
+
+        /* Determine the width of the created text item, and will it fit in the
+	 * current line. We start a new line when we have at least one tag
+	 * in the current, and the new one is too large for the available
+	 * visible space in the scrolled window.
+	 */
+	goo_canvas_item_get_bounds (text_item, &bounds);
+	text_item_width = (int) (bounds.x2 - bounds.x1);
+	data->text_item_x += text_item_width;
+
+	start_new_line = (((data->text_item_x + data->last_border) >
+			   data->visible_width) &&
+			  (data->tags_in_current_line >= 1));
+
+	if (! start_new_line)
+		data->text_item_x += TAG_SPACING;
+	else {
+		/* Set the coordinates to the beginning of a new line. */
+		++data->current_line;
+
+		if (priv->randomize_borders) {
+			data->last_border = g_rand_int_range (priv->grand,
+							      TAG_BORDER_MIN,
+							      TAG_BORDER_MAX);
+		}
+
+		data->text_item_x = data->last_border;
+		data->text_item_y =
+			(data->current_line + 1) * data->line_height;
+
+		data->tags_in_current_line = 0;
+
+		/* If the canvas is too small vertically or horizontally
+		 * to accept a new text item, expand it accordingly.
+		 */
+		if (data->text_item_y >
+		    (data->canvas_bottom - data->canvas_top)) {
+			/* Expand on the bottom */
+			data->canvas_bottom += data->line_height;
+
+			goo_canvas_set_bounds (GOO_CANVAS (cloud->canvas),
+					       data->canvas_left,
+					       data->canvas_top,
+					       data->canvas_right,
+					       data->canvas_bottom);
+		}
+
+		if (text_item_width >
+		    (data->canvas_right - data->canvas_left)) {
+			/* Expand on the right */
+			data->canvas_right = text_item_width +
+				(TAG_SPACING + data->last_border);
+
+			goo_canvas_set_bounds (GOO_CANVAS (cloud->canvas),
+					       data->canvas_left,
+					       data->canvas_top,
+					       data->canvas_right,
+					       data->canvas_bottom);
+		}
+
+		/* Move the item to the beginning of the new line. */
+		g_object_set (GOO_CANVAS_ITEM (text_item),
+			      "x", data->text_item_x,
+			      "y", data->text_item_y,
+			      NULL);
+
+		data->text_item_x += text_item_width + TAG_SPACING;
+	}
+
+	++(data->tags_in_current_line);
+
+	/* Add the new goo text item to cloud's inventory. */
+	new_tag_item = g_new0 (TagItem, 1);
+
+	new_tag_item->text_item = text_item;
+	new_tag_item->tag = g_strdup (tag);
+	new_tag_item->fill_colour = fill_colour;
+
+	priv->tag_items = g_slist_append (priv->tag_items, new_tag_item);
+
+	/* Connect to text item's signals. */
+	ButtonPressCbData *bp_data =
+		button_press_cb_data_new (cloud, new_tag_item);
+
+	priv->button_press_data =
+		g_slist_append (priv->button_press_data, bp_data);
+
+	g_signal_connect (text_item, "button_press_event",
+			  (GtkSignalFunc) button_press_cb, bp_data);
+	
+	g_signal_connect (text_item, "enter_notify_event",
+			  (GtkSignalFunc) enter_notify_cb, new_tag_item);
+
+	g_signal_connect (text_item, "leave_notify_event",
+			  (GtkSignalFunc) leave_notify_cb, new_tag_item);
+}
+
+static void
+set_tags (TrackerTagCloud *cloud, GHashTable *tags)
+{
+	TrackerTagCloudPrivate *priv = TRACKER_TAG_CLOUD_GET_PRIVATE (cloud);
+	TrackerTagCloudModel *model = priv->model;
+
+	TagLineData *tag_line_data;
+
+	gint min_font_size;
+	gint max_font_size;
+
+	if (g_hash_table_size (tags) == 0) return;
+
+	tag_line_data = tag_line_data_new (cloud);
+
+	goo_canvas_get_bounds (GOO_CANVAS (cloud->canvas),
+			       &(tag_line_data->canvas_left),
+			       &(tag_line_data->canvas_top),
+			       &(tag_line_data->canvas_right),
+			       &(tag_line_data->canvas_bottom));
+
+	tag_line_data->visible_width = (int)
+		(tag_line_data->canvas_right - tag_line_data->canvas_left);
+
+	min_font_size = tracker_tag_cloud_model_get_min_font_size (model);
+	max_font_size = tracker_tag_cloud_model_get_max_font_size (model);
+
+	tag_line_data->line_height = max_font_size * 1.5;
+
+	if (priv->lighten_rare_tags) {
+		tag_line_data->bold_colour_limit =
+			(min_font_size + (max_font_size - min_font_size) / 2);
+	}
+
+	g_hash_table_foreach (tags, (GHFunc) create_lines, tag_line_data);
+
+	g_free (tag_line_data);
+}
+
+static void
+free_tag_items_slist (GSList **tag_items)
+{
+	if (tag_items == NULL) return;
+
+	GSList *it;
+
+	for (it = *tag_items; it; it = it->next) {
+		TagItem *olditem = (TagItem *) it->data;
+
+		if (G_IS_OBJECT (olditem->text_item)) {
+			goo_canvas_item_remove (
+				GOO_CANVAS_ITEM (olditem->text_item));
+		}
+
+		g_free (olditem->tag);
+		g_free (olditem);
+	}
+
+	g_slist_free (*tag_items);
+	*tag_items = NULL;
+}
+
+static void
+draw_tags (TrackerTagCloud *cloud)
+{
+	TrackerTagCloudPrivate *priv = TRACKER_TAG_CLOUD_GET_PRIVATE (cloud);
+	GHashTable *tags;
+
+	/* Remove the text items from canvas, create a new list */
+	free_tag_items_slist (&(priv->tag_items));
+
+	/* Set canvas bounds based on scrolled window's allocation */
+	gtk_widget_set_size_request (cloud->canvas,
+				     priv->alloc_width - CANVAS_OFFSET,
+				     priv->alloc_height - CANVAS_OFFSET);
+
+	goo_canvas_set_bounds (GOO_CANVAS (cloud->canvas),
+			       0, 0,
+			       priv->alloc_width - CANVAS_OFFSET,
+			       priv->alloc_height - CANVAS_OFFSET);
+
+	/* Redraw tag items on the canvas */
+	tags = tracker_tag_cloud_model_get_font_sizes (priv->model);
+	set_tags (cloud, tags);
+	g_hash_table_destroy (tags);
+}
Index: src/plugins/tracker/tracker-tag-cloud-model.h
===================================================================
--- src/plugins/tracker/tracker-tag-cloud-model.h	(revisión: 0)
+++ src/plugins/tracker/tracker-tag-cloud-model.h	(revisión: 0)
@@ -0,0 +1,88 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+ *
+ * tracker-tag-cloud-model.h: Model for the TrackerTagCloud widget
+ *
+ * Copyright (C) 2007 Marko Anastasov <marko@marko.anastasov.name>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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 Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA  02110-1301, USA.
+ */
+
+#ifndef __TRACKER_TAG_CLOUD_MODEL_H__
+#define __TRACKER_TAG_CLOUD_MODEL_H__
+
+#include <glib.h>
+#include <glib-object.h>
+#include <stdlib.h>
+#include <string.h>
+
+G_BEGIN_DECLS
+
+#define TYPE_TRACKER_TAG_CLOUD_MODEL            (tracker_tag_cloud_model_get_type ())
+#define TRACKER_TAG_CLOUD_MODEL(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), TYPE_TRACKER_TAG_CLOUD_MODEL, TrackerTagCloudModel))
+#define TRACKER_TAG_CLOUD_MODEL_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), TYPE_TRACKER_TAG_CLOUD_MODEL, TrackerTagCloudModelClass))
+#define TRACKER_IS_TAG_CLOUD_MODEL(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TYPE_TRACKER_TAG_CLOUD_MODEL))
+#define TRACKER_IS_TAG_CLOUD_MODEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TYPE_TRACKER_TAG_CLOUD_MODEL))
+#define TRACKER_TAG_CLOUD_MODEL_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), TYPE_TRACKER_TAG_CLOUD_MODEL, TrackerTagCloudModelClass))
+
+typedef struct _TrackerTagCloudModel TrackerTagCloudModel;
+typedef struct _TrackerTagCloudModelClass TrackerTagCloudModelClass;
+typedef struct _TrackerTagCloudModelPrivate TrackerTagCloudModelPrivate;
+
+struct _TrackerTagCloudModel
+{
+	GObject parent;
+
+	TrackerTagCloudModelPrivate *priv;
+};
+
+struct _TrackerTagCloudModelClass
+{
+	GObjectClass parent;
+
+	/* Signals */
+	void (* changed) (TrackerTagCloudModel *model);
+
+	/* Padding for future expansion */
+	void (*_tracker_tag_cloud_model_reserved1) (void);
+	void (*_tracker_tag_cloud_model_reserved2) (void);
+	void (*_tracker_tag_cloud_model_reserved3) (void);
+	void (*_tracker_tag_cloud_model_reserved4) (void);
+	void (*_tracker_tag_cloud_model_reserved5) (void);
+	void (*_tracker_tag_cloud_model_reserved6) (void);
+	void (*_tracker_tag_cloud_model_reserved7) (void);
+};
+
+TrackerTagCloudModel * tracker_tag_cloud_model_new               (gint min_font_size,
+								  gint max_font_size);
+
+void                   tracker_tag_cloud_model_add_tag           (TrackerTagCloudModel* model,
+								  const gchar* tag);
+
+GHashTable *           tracker_tag_cloud_model_get_font_sizes    (TrackerTagCloudModel* model);
+
+gint                   tracker_tag_cloud_model_get_min_font_size (TrackerTagCloudModel* model);
+void                   tracker_tag_cloud_model_set_min_font_size (TrackerTagCloudModel* model,
+								  gint value);
+
+gint                   tracker_tag_cloud_model_get_max_font_size (TrackerTagCloudModel* model);
+void                   tracker_tag_cloud_model_set_max_font_size (TrackerTagCloudModel* model,
+								  gint value);
+
+GType                  tracker_tag_cloud_model_get_type          (void);
+
+G_END_DECLS
+
+#endif /* __TRACKER_TRACKER_TAG_CLOUD_MODEL_H__ */
Index: src/plugins/tracker/tracker-tag-cloud.h
===================================================================
--- src/plugins/tracker/tracker-tag-cloud.h	(revisión: 0)
+++ src/plugins/tracker/tracker-tag-cloud.h	(revisión: 0)
@@ -0,0 +1,87 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+ *
+ * tracker-tag-cloud.h: Tracker tag cloud widget
+ *
+ * Copyright (C) 2007 Marko Anastasov <marko@marko.anastasov.name>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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 Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA  02110-1301, USA.
+ */
+
+#ifndef __TRACKER_TAG_CLOUD_H__
+#define __TRACKER_TAG_CLOUD_H__
+
+#include <gtk/gtk.h>
+#include "tracker-tag-cloud-model.h"
+
+G_BEGIN_DECLS
+
+#define TRACKER_TYPE_TAG_CLOUD            (tracker_tag_cloud_get_type ())
+#define TRACKER_TAG_CLOUD(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), TRACKER_TYPE_TAG_CLOUD, TrackerTagCloud))
+#define TRACKER_TAG_CLOUD_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  TRACKER_TYPE_TAG_CLOUD, TrackerTagCloudClass))
+#define TRACKER_IS_TAG_CLOUD(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TRACKER_TYPE_TAG_CLOUD))
+#define TRACKER_IS_TAG_CLOUD_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  TRACKER_TYPE_TAG_CLOUD))
+#define TRACKER_TAG_CLOUD_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  TRACKER_TYPE_TAG_CLOUD, TrackerTagCloudClass))
+
+typedef struct _TrackerTagCloud TrackerTagCloud;
+typedef struct _TrackerTagCloudClass TrackerTagCloudClass;
+typedef struct _TrackerTagCloudPrivate TrackerTagCloudPrivate;
+
+struct _TrackerTagCloud
+{
+	GtkScrolledWindow parent;
+
+	/*< public >*/
+	GtkWidget *canvas;
+
+	/*< private >*/
+	TrackerTagCloudPrivate *priv;
+
+	/* Padding for future expansion */
+	void (*_tracker_tag_cloud_reserved1) (void);
+	void (*_tracker_tag_cloud_reserved2) (void);
+	void (*_tracker_tag_cloud_reserved3) (void);
+	void (*_tracker_tag_cloud_reserved4) (void);
+};
+
+struct _TrackerTagCloudClass
+{
+	GtkScrolledWindowClass parent_class;
+
+	/* Signals */
+	void (* clicked) (TrackerTagCloud *cloud, gchar *tag);
+
+	/* Padding for future expansion */
+	void (*_tracker_tag_cloud_reserved1) (void);
+	void (*_tracker_tag_cloud_reserved2) (void);
+	void (*_tracker_tag_cloud_reserved3) (void);
+	void (*_tracker_tag_cloud_reserved4) (void);
+	void (*_tracker_tag_cloud_reserved5) (void);
+	void (*_tracker_tag_cloud_reserved6) (void);
+	void (*_tracker_tag_cloud_reserved7) (void);
+};
+
+GtkWidget * tracker_tag_cloud_new            (void);
+
+GtkWidget * tracker_tag_cloud_new_with_model (TrackerTagCloudModel *model);
+
+void        tracker_tag_cloud_set_model      (TrackerTagCloud *cloud,
+					      TrackerTagCloudModel *model);
+
+GType       tracker_tag_cloud_get_type       (void);
+
+G_END_DECLS
+
+#endif /* __TRACKER_TAG_CLOUD_H__ */
