[gtksourceview/wip/completion-model: 1/17] CompletionModel: new implementation



commit 09292fe73ac1928af047da98da030e7e07658d5e
Author: SÃbastien Wilmet <swilmet gnome org>
Date:   Mon Jan 14 23:12:11 2013 +0100

    CompletionModel: new implementation
    
    The previous implementation supported several populations of proposals.
    Between two populations, the model kept the common proposals, to avoid
    emitting useless "row-deleted" and "row-inserted" signals.
    
    But there is a simpler solution. During a population, we remove the
    model from the GtkTreeView. So the "row-inserted" and "row-deleted"
    signals are not needed. Once the population is finished, we set the
    model to the GtkTreeView, so the rows are correctly displayed.
    
    With the new implementation, a model is used for only one population.
    When a new population occurs, the old model is destroyed, and we create
    a new one. It simplifies a lot the code.
    
    And it's also more efficient, since we avoid completely all the
    "row-inserted" and "row-deleted" signals during a population.
    
    I've tested with five providers containing each 10000 proposals. With
    the previous implementation, the completion window takes maybe 30
    seconds to appear, while with the new implementation, it appears in
    approximately one second!
    
    There is still the problem when we change the visible providers. In this
    case the "row-deleted" and "row-inserted" signals are emitted. But it
    would not be difficult to use the same trick as for the populations.

 gtksourceview/gtksourcecompletionmodel.c |  851 ++++++++++++++++++++++++++++--
 1 files changed, 809 insertions(+), 42 deletions(-)
---
diff --git a/gtksourceview/gtksourcecompletionmodel.c b/gtksourceview/gtksourcecompletionmodel.c
index b0a540d..20dfe38 100644
--- a/gtksourceview/gtksourcecompletionmodel.c
+++ b/gtksourceview/gtksourcecompletionmodel.c
@@ -3,6 +3,7 @@
  * This file is part of GtkSourceView
  *
  * Copyright (C) 2009 - Jesse van den Kieboom <jessevdk gnome org>
+ * Copyright (C) 2013 - SÃbastien Wilmet <swilmet gnome org>
  *
  * GtkSourceView is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -25,9 +26,45 @@
 
 #define GTK_SOURCE_COMPLETION_MODEL_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE((object), GTK_SOURCE_TYPE_COMPLETION_MODEL, GtkSourceCompletionModelPrivate))
 
+typedef struct
+{
+	GtkSourceCompletionModel *model;
+	GtkSourceCompletionProvider *completion_provider;
+
+	/* List of ProposalInfo. If the header is visible, it is included. */
+	GQueue *proposals;
+
+	/* By default, all providers are visible. But with Ctrl+{left, right},
+	 * the user can switch between providers. In this case, only one
+	 * provider is visible, and the others are hidden. */
+	guint visible : 1;
+} ProviderInfo;
+
+typedef struct
+{
+	/* Node from model->priv->providers */
+	GList *provider_node;
+
+	/* For the header, the completion proposal is NULL. */
+	GtkSourceCompletionProposal *completion_proposal;
+
+	/* For the "changed" signal emitted by the proposal.
+	 * When the node is freed, the signal is disconnected. */
+	gulong changed_id;
+} ProposalInfo;
+
 struct _GtkSourceCompletionModelPrivate
 {
 	GType column_types[GTK_SOURCE_COMPLETION_MODEL_N_COLUMNS];
+
+	/* List of ProviderInfo sorted by priority in descending order. */
+	GList *providers;
+
+	/* List of GtkSourceCompletionProvider. If NULL, all providers are
+	 * visible. */
+	GList *visible_providers;
+
+	guint show_headers : 1;
 };
 
 enum
@@ -48,6 +85,281 @@ G_DEFINE_TYPE_WITH_CODE (GtkSourceCompletionModel,
                          G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_MODEL,
                                                 tree_model_iface_init))
 
+/* Utilities functions */
+
+static gboolean
+is_header (ProposalInfo *info)
+{
+	g_assert (info != NULL);
+
+	return info->completion_proposal == NULL;
+}
+
+static gboolean
+is_provider_visible (GtkSourceCompletionModel    *model,
+		     GtkSourceCompletionProvider *provider)
+{
+	if (model->priv->visible_providers == NULL)
+	{
+		return TRUE;
+	}
+
+	return g_list_find (model->priv->visible_providers, provider) != NULL;
+}
+
+static gboolean
+get_iter_from_index (GtkSourceCompletionModel *model,
+                     GtkTreeIter              *iter,
+                     gint                      idx)
+{
+	gint provider_index = 0;
+	GList *l;
+	ProviderInfo *info;
+
+	if (idx < 0)
+	{
+		return FALSE;
+	}
+
+	/* Find the provider */
+	for (l = model->priv->providers; l != NULL; l = l->next)
+	{
+		gint new_index;
+		info = l->data;
+
+		if (!info->visible)
+		{
+			continue;
+		}
+
+		new_index = provider_index + info->proposals->length;
+
+		if (idx < new_index)
+		{
+			break;
+		}
+
+		provider_index = new_index;
+	}
+
+	if (l == NULL)
+	{
+		return FALSE;
+	}
+
+	/* Find the node inside the provider */
+	iter->user_data = g_queue_peek_nth_link (info->proposals, idx - provider_index);
+
+	return iter->user_data != NULL;
+}
+
+static gint
+get_provider_start_index (GtkSourceCompletionModel *model,
+			  ProviderInfo		   *info)
+{
+	gint start_index = 0;
+	GList *l;
+
+	g_assert (info != NULL);
+
+	for (l = model->priv->providers; l != NULL; l = l->next)
+	{
+		ProviderInfo *cur_info = l->data;
+
+		if (cur_info == info)
+		{
+			break;
+		}
+
+		if (cur_info->visible)
+		{
+			start_index += cur_info->proposals->length;
+		}
+	}
+
+	/* The provider must be in the list. */
+	g_assert (l != NULL);
+
+	return start_index;
+}
+
+static GtkTreePath *
+get_proposal_path (GtkSourceCompletionModel *model,
+		   GList                    *proposal_node)
+{
+	ProposalInfo *proposal_info;
+	ProviderInfo *provider_info;
+	gint idx;
+
+	if (proposal_node == NULL)
+	{
+		return NULL;
+	}
+
+	proposal_info = proposal_node->data;
+	provider_info = proposal_info->provider_node->data;
+
+	idx = get_provider_start_index (model, provider_info);
+	idx += g_queue_link_index (provider_info->proposals, proposal_node);
+
+	return gtk_tree_path_new_from_indices (idx, -1);
+}
+
+/* Returns the first visible provider after @provider. It can be @provider
+ * itself. Returns NULL if not found. */
+static GList *
+find_next_visible_provider (GList *provider)
+{
+	GList *l;
+
+	for (l = provider; l != NULL; l = l->next)
+	{
+		ProviderInfo *info = l->data;
+
+		if (info->visible)
+		{
+			return l;
+		}
+	}
+
+	return NULL;
+}
+
+/* Returns the first visible provider before @provider. It can be @provider
+ * itself. Returns NULL if not found. */
+static GList *
+find_previous_visible_provider (GList *provider)
+{
+	GList *l;
+
+	for (l = provider; l != NULL; l = l->prev)
+	{
+		ProviderInfo *info = l->data;
+
+		if (info->visible)
+		{
+			return l;
+		}
+	}
+
+	return NULL;
+}
+
+static GList *
+get_provider_node (GtkSourceCompletionModel    *model,
+		   GtkSourceCompletionProvider *provider)
+{
+	GList *l;
+
+	for (l = model->priv->providers; l != NULL; l = l->next)
+	{
+		ProviderInfo *provider_info = l->data;
+
+		if (provider_info->completion_provider == provider)
+		{
+			return l;
+		}
+	}
+
+	return NULL;
+}
+
+/* Remove providers or proposals */
+
+static void
+proposal_info_free (gpointer data)
+{
+	ProposalInfo *info = data;
+
+	if (data == NULL)
+	{
+		return;
+	}
+
+	if (info->completion_proposal != NULL)
+	{
+		if (info->changed_id != 0)
+		{
+			g_signal_handler_disconnect (info->completion_proposal,
+			                             info->changed_id);
+		}
+
+		g_object_unref (info->completion_proposal);
+	}
+
+	g_slice_free (ProposalInfo, data);
+}
+
+static void
+provider_info_free (gpointer data)
+{
+	ProviderInfo *info = data;
+
+	if (data == NULL)
+	{
+		return;
+	}
+
+	g_object_unref (info->completion_provider);
+	g_queue_free_full (info->proposals, (GDestroyNotify)proposal_info_free);
+	g_slice_free (ProviderInfo, data);
+}
+
+/* Show/hide header */
+
+static void
+add_header (GList *provider_node)
+{
+	ProviderInfo *provider_info = provider_node->data;
+	ProposalInfo *header = g_slice_new0 (ProposalInfo);
+
+	header->provider_node = provider_node;
+
+	g_queue_push_head (provider_info->proposals, header);
+}
+
+/* Add the header, and emit the "row-inserted" signal. */
+static void
+show_header (GtkSourceCompletionModel *model,
+	     GList                    *provider_node)
+{
+	ProviderInfo *provider_info = provider_node->data;
+
+	add_header (provider_node);
+
+	if (provider_info->visible)
+	{
+		GtkTreePath *path = get_proposal_path (model, provider_info->proposals->head);
+		GtkTreeIter iter;
+
+		iter.user_data = provider_info->proposals->head;
+		gtk_tree_model_row_inserted (GTK_TREE_MODEL (model), path, &iter);
+
+		gtk_tree_path_free (path);
+	}
+}
+
+/* Remove the header, and emit the "row-deleted" signal. */
+static void
+hide_header (GtkSourceCompletionModel *model,
+	     GList                    *provider_node)
+{
+	ProviderInfo *provider_info = provider_node->data;
+	ProposalInfo *proposal_info = g_queue_pop_head (provider_info->proposals);
+
+	g_assert (provider_info->proposals->length > 0);
+	g_assert (is_header (proposal_info));
+
+	proposal_info_free (proposal_info);
+
+	if (provider_info->visible)
+	{
+		GtkTreePath *path = get_proposal_path (model, provider_info->proposals->head);
+		gtk_tree_model_row_deleted (GTK_TREE_MODEL (model), path);
+		gtk_tree_path_free (path);
+	}
+}
+
 /* Interface implementation */
 
 static GtkTreeModelFlags
@@ -68,12 +380,12 @@ tree_model_get_n_columns (GtkTreeModel *tree_model)
 
 static GType
 tree_model_get_column_type (GtkTreeModel *tree_model,
-			    gint          index)
+			    gint          idx)
 {
 	g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_MODEL (tree_model), G_TYPE_INVALID);
-	g_return_val_if_fail (0 <= index && index < GTK_SOURCE_COMPLETION_MODEL_N_COLUMNS, G_TYPE_INVALID);
+	g_return_val_if_fail (0 <= idx && idx < GTK_SOURCE_COMPLETION_MODEL_N_COLUMNS, G_TYPE_INVALID);
 
-	return GTK_SOURCE_COMPLETION_MODEL (tree_model)->priv->column_types[index];
+	return GTK_SOURCE_COMPLETION_MODEL (tree_model)->priv->column_types[idx];
 }
 
 static gboolean
@@ -91,9 +403,7 @@ tree_model_get_iter (GtkTreeModel *tree_model,
 	model = GTK_SOURCE_COMPLETION_MODEL (tree_model);
 	indices = gtk_tree_path_get_indices (path);
 
-	/* TODO */
-
-	return FALSE;
+	return get_iter_from_index (model, iter, indices[0]);
 }
 
 static GtkTreePath *
@@ -108,9 +418,7 @@ tree_model_get_path (GtkTreeModel *tree_model,
 
 	model = GTK_SOURCE_COMPLETION_MODEL (tree_model);
 
-	/* TODO */
-
-	return NULL;
+	return get_proposal_path (model, iter->user_data);
 }
 
 static void
@@ -119,23 +427,123 @@ tree_model_get_value (GtkTreeModel *tree_model,
 		      gint          column,
 		      GValue       *value)
 {
+	GList *proposal_node;
+	ProposalInfo *proposal_info;
+	ProviderInfo *provider_info;
+	GtkSourceCompletionProposal *completion_proposal;
+	GtkSourceCompletionProvider *completion_provider;
+
 	g_return_if_fail (GTK_SOURCE_IS_COMPLETION_MODEL (tree_model));
 	g_return_if_fail (iter != NULL);
 	g_return_if_fail (iter->user_data != NULL);
 	g_return_if_fail (0 <= column && column < GTK_SOURCE_COMPLETION_MODEL_N_COLUMNS);
 
-	/* TODO */
+	proposal_node = iter->user_data;
+	proposal_info = proposal_node->data;
+	provider_info = proposal_info->provider_node->data;
+	completion_proposal = proposal_info->completion_proposal;
+	completion_provider = provider_info->completion_provider;
+
+	g_value_init (value, GTK_SOURCE_COMPLETION_MODEL (tree_model)->priv->column_types[column]);
+
+	switch (column)
+	{
+		case GTK_SOURCE_COMPLETION_MODEL_COLUMN_PROVIDER:
+			g_value_set_object (value, completion_provider);
+			break;
+
+		case GTK_SOURCE_COMPLETION_MODEL_COLUMN_PROPOSAL:
+			g_value_set_object (value, completion_proposal);
+			break;
+
+		case GTK_SOURCE_COMPLETION_MODEL_COLUMN_LABEL:
+			if (completion_proposal != NULL)
+			{
+				gchar *label = gtk_source_completion_proposal_get_label (completion_proposal);
+				g_value_take_string (value, label);
+			}
+			else
+			{
+				g_value_take_string (value, NULL);
+			}
+			break;
+
+		case GTK_SOURCE_COMPLETION_MODEL_COLUMN_MARKUP:
+			if (completion_proposal != NULL)
+			{
+				gchar *markup = gtk_source_completion_proposal_get_markup (completion_proposal);
+				g_value_take_string (value, markup);
+			}
+			else
+			{
+				g_value_take_string (value, NULL);
+			}
+			break;
+
+		case GTK_SOURCE_COMPLETION_MODEL_COLUMN_ICON:
+			if (is_header (proposal_info))
+			{
+				GdkPixbuf *icon = gtk_source_completion_provider_get_icon (completion_provider);
+				g_value_set_object (value, (gpointer)icon);
+			}
+			else
+			{
+				GdkPixbuf *icon = gtk_source_completion_proposal_get_icon (completion_proposal);
+				g_value_set_object (value, (gpointer)icon);
+			}
+			break;
+
+		default:
+			g_assert_not_reached ();
+	}
 }
 
 static gboolean
 tree_model_iter_next (GtkTreeModel *tree_model,
 		      GtkTreeIter  *iter)
 {
+	ProposalInfo *proposal_info;
+	GList *proposal_node;
+	GList *cur_provider;
+
 	g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_MODEL (tree_model), FALSE);
 	g_return_val_if_fail (iter != NULL, FALSE);
+	g_return_val_if_fail (iter->user_data != NULL, FALSE);
 
-	/* TODO */
-	return FALSE;
+	proposal_node = iter->user_data;
+	proposal_info = proposal_node->data;
+
+	/* Find the right provider, which must be visible */
+
+	cur_provider = proposal_info->provider_node;
+
+	if (proposal_node->next == NULL)
+	{
+		cur_provider = g_list_next (cur_provider);
+	}
+
+	cur_provider = find_next_visible_provider (cur_provider);
+
+	if (cur_provider == NULL)
+	{
+		return FALSE;
+	}
+
+	/* Find the proposal inside the provider */
+
+	if (cur_provider == proposal_info->provider_node)
+	{
+		iter->user_data = g_list_next (proposal_node);
+	}
+	else
+	{
+		ProviderInfo *info = cur_provider->data;
+		iter->user_data = info->proposals->head;
+	}
+
+	g_assert (iter->user_data != NULL);
+
+	return TRUE;
 }
 
 static gboolean
@@ -153,8 +561,7 @@ tree_model_iter_children (GtkTreeModel *tree_model,
 	}
 	else
 	{
-		/* TODO */
-		return FALSE;
+		return get_iter_from_index (GTK_SOURCE_COMPLETION_MODEL (tree_model), iter, 0);
 	}
 }
 
@@ -173,18 +580,31 @@ static gint
 tree_model_iter_n_children (GtkTreeModel *tree_model,
 			    GtkTreeIter  *iter)
 {
+	GtkSourceCompletionModel *model;
+	GList *l;
+	gint num_nodes = 0;
+
 	g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_MODEL (tree_model), 0);
 	g_return_val_if_fail (iter == NULL || iter->user_data != NULL, 0);
 
-	if (iter == NULL)
+	if (iter != NULL)
 	{
-		/* TODO */
 		return 0;
 	}
-	else
+
+	model = GTK_SOURCE_COMPLETION_MODEL (tree_model);
+
+	for (l = model->priv->providers; l != NULL; l = l->next)
 	{
-		return 0;
+		ProviderInfo *info = l->data;
+
+		if (info->visible)
+		{
+			num_nodes += info->proposals->length;
+		}
 	}
+
+	return num_nodes;
 }
 
 static gboolean
@@ -203,8 +623,9 @@ tree_model_iter_nth_child (GtkTreeModel *tree_model,
 	}
 	else
 	{
-		/* TODO */
-		return FALSE;
+		return get_iter_from_index (GTK_SOURCE_COMPLETION_MODEL (tree_model),
+					    iter,
+					    child_num);
 	}
 }
 
@@ -225,7 +646,7 @@ static void
 tree_model_iface_init (gpointer g_iface,
                        gpointer iface_data)
 {
-	GtkTreeModelIface *iface = (GtkTreeModelIface *)g_iface;
+	GtkTreeModelIface *iface = g_iface;
 
 	iface->get_flags = tree_model_get_flags;
 	iface->get_n_columns = tree_model_get_n_columns;
@@ -243,22 +664,18 @@ tree_model_iface_init (gpointer g_iface,
 
 /* Construction and destruction */
 
-GtkSourceCompletionModel*
-gtk_source_completion_model_new (void)
-{
-	return g_object_new (GTK_SOURCE_TYPE_COMPLETION_MODEL, NULL);
-}
-
 static void
 gtk_source_completion_model_dispose (GObject *object)
 {
-	G_OBJECT_CLASS (gtk_source_completion_model_parent_class)->dispose (object);
-}
+	GtkSourceCompletionModel *model = GTK_SOURCE_COMPLETION_MODEL (object);
 
-static void
-gtk_source_completion_model_finalize (GObject *object)
-{
-	G_OBJECT_CLASS (gtk_source_completion_model_parent_class)->finalize (object);
+	g_list_free_full (model->priv->providers, (GDestroyNotify)provider_info_free);
+	model->priv->providers = NULL;
+
+	g_list_free_full (model->priv->visible_providers, g_object_unref);
+	model->priv->visible_providers = NULL;
+
+	G_OBJECT_CLASS (gtk_source_completion_model_parent_class)->dispose (object);
 }
 
 static void
@@ -266,7 +683,6 @@ gtk_source_completion_model_class_init (GtkSourceCompletionModelClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
 
-	object_class->finalize = gtk_source_completion_model_finalize;
 	object_class->dispose = gtk_source_completion_model_dispose;
 
 	signals[PROVIDERS_CHANGED] =
@@ -315,6 +731,10 @@ gtk_source_completion_model_init (GtkSourceCompletionModel *self)
 	self->priv->column_types[GTK_SOURCE_COMPLETION_MODEL_COLUMN_LABEL] = G_TYPE_STRING;
 	self->priv->column_types[GTK_SOURCE_COMPLETION_MODEL_COLUMN_MARKUP] = G_TYPE_STRING;
 	self->priv->column_types[GTK_SOURCE_COMPLETION_MODEL_COLUMN_ICON] = GDK_TYPE_PIXBUF;
+
+	self->priv->show_headers = 1;
+	self->priv->providers = NULL;
+	self->priv->visible_providers = NULL;
 }
 
 /* Population: begin/end populate, add proposals, cancel */
@@ -324,6 +744,8 @@ gtk_source_completion_model_begin_populate (GtkSourceCompletionModel *model,
 					    GList                    *providers)
 {
 	g_return_if_fail (GTK_SOURCE_IS_COMPLETION_MODEL (model));
+
+	gtk_source_completion_model_clear (model);
 }
 
 void
@@ -334,13 +756,119 @@ gtk_source_completion_model_end_populate (GtkSourceCompletionModel    *model,
 	g_return_if_fail (GTK_SOURCE_IS_COMPLETION_PROVIDER (provider));
 }
 
+/* Returns the newly-created provider node */
+static GList *
+create_provider_info (GtkSourceCompletionModel    *model,
+                      GtkSourceCompletionProvider *provider)
+{
+	ProviderInfo *info;
+	gint priority;
+	GList *l;
+	GList *provider_node;
+
+	/* Create the structure */
+
+	info = g_slice_new0 (ProviderInfo);
+	info->model = model;
+	info->completion_provider = g_object_ref (provider);
+	info->proposals = g_queue_new ();
+	info->visible = is_provider_visible (model, provider);
+
+	/* Insert the ProviderInfo in the list */
+
+	priority = gtk_source_completion_provider_get_priority (provider);
+
+	for (l = model->priv->providers; l != NULL; l = l->next)
+	{
+		ProviderInfo *cur_info = l->data;
+		gint cur_priority = gtk_source_completion_provider_get_priority (cur_info->completion_provider);
+
+		if (cur_priority < priority)
+		{
+			break;
+		}
+	}
+
+	model->priv->providers = g_list_insert_before (model->priv->providers, l, info);
+
+	provider_node = g_list_find (model->priv->providers, info);
+
+	/* Insert the header if needed */
+
+	if (model->priv->show_headers)
+	{
+		add_header (provider_node);
+	}
+
+	g_signal_emit (model, signals[PROVIDERS_CHANGED], 0);
+
+	return provider_node;
+}
+
+static void
+on_proposal_changed (GtkSourceCompletionProposal *proposal,
+		     GList                       *proposal_node)
+{
+	ProposalInfo *proposal_info = proposal_node->data;
+	ProviderInfo *provider_info = proposal_info->provider_node->data;
+
+	if (provider_info->visible)
+	{
+		GtkTreeIter iter;
+		GtkTreePath *path;
+
+		iter.user_data = proposal_node;
+		path = get_proposal_path (provider_info->model, proposal_node);
+
+		gtk_tree_model_row_changed (GTK_TREE_MODEL (provider_info->model),
+					    path,
+					    &iter);
+
+		gtk_tree_path_free (path);
+	}
+}
+
+static void
+add_proposal (GtkSourceCompletionProposal *proposal,
+	      GList                       *provider_node)
+{
+	ProviderInfo *provider_info = provider_node->data;
+	ProposalInfo *proposal_info = g_slice_new0 (ProposalInfo);
+
+	proposal_info->provider_node = provider_node;
+	proposal_info->completion_proposal = g_object_ref (proposal);
+
+	g_queue_push_tail (provider_info->proposals, proposal_info);
+
+	proposal_info->changed_id = g_signal_connect (proposal,
+						      "changed",
+						      G_CALLBACK (on_proposal_changed),
+						      provider_info->proposals->tail);
+}
+
 void
 gtk_source_completion_model_add_proposals (GtkSourceCompletionModel    *model,
 					   GtkSourceCompletionProvider *provider,
 					   GList                       *proposals)
 {
+	GList *provider_node = NULL;
+
 	g_return_if_fail (GTK_SOURCE_IS_COMPLETION_MODEL (model));
 	g_return_if_fail (GTK_SOURCE_IS_COMPLETION_PROVIDER (provider));
+
+	if (proposals == NULL)
+	{
+		return;
+	}
+
+	provider_node = get_provider_node (model, provider);
+
+	if (provider_node == NULL)
+	{
+		provider_node = create_provider_info (model, provider);
+	}
+
+	g_list_foreach (proposals, (GFunc)add_proposal, provider_node);
 }
 
 void
@@ -351,11 +879,92 @@ gtk_source_completion_model_cancel (GtkSourceCompletionModel *model)
 
 /* Get/set visible providers */
 
+static void
+show_provider (GtkSourceCompletionModel *model,
+	       ProviderInfo             *provider_info)
+{
+	GtkTreePath *path;
+	GList *l;
+
+	path = get_proposal_path (model, provider_info->proposals->head);
+
+	for (l = provider_info->proposals->head; l != NULL; l = l->next)
+	{
+		GtkTreeIter iter;
+		iter.user_data = l;
+
+		gtk_tree_model_row_inserted (GTK_TREE_MODEL (model), path, &iter);
+
+		gtk_tree_path_next (path);
+	}
+
+	gtk_tree_path_free (path);
+}
+
+static void
+hide_provider (GtkSourceCompletionModel *model,
+	       ProviderInfo             *provider_info)
+{
+	GtkTreePath *path;
+	gint i;
+
+	path = get_proposal_path (model, provider_info->proposals->head);
+
+	for (i = 0; i < provider_info->proposals->length; i++)
+	{
+		gtk_tree_model_row_deleted (GTK_TREE_MODEL (model), path);
+	}
+
+	gtk_tree_path_free (path);
+}
+
+static void
+update_provider_visibility (GtkSourceCompletionModel *model,
+			    ProviderInfo             *provider_info)
+{
+	gboolean new_visible = is_provider_visible (model, provider_info->completion_provider);
+
+	if (new_visible == provider_info->visible)
+	{
+		return;
+	}
+
+	if (new_visible)
+	{
+		provider_info->visible = TRUE;
+		show_provider (model, provider_info);
+	}
+	else
+	{
+		hide_provider (model, provider_info);
+		provider_info->visible = FALSE;
+	}
+}
+
 void
 gtk_source_completion_model_set_visible_providers (GtkSourceCompletionModel *model,
                                                    GList                    *providers)
 {
+	GList *l;
+
 	g_return_if_fail (GTK_SOURCE_IS_COMPLETION_MODEL (model));
+
+	for (l = providers; l != NULL; l = l->next)
+	{
+		g_return_if_fail (GTK_SOURCE_IS_COMPLETION_PROVIDER (l->data));
+	}
+
+	g_list_free_full (model->priv->visible_providers, g_object_unref);
+
+	model->priv->visible_providers = g_list_copy_deep (providers,
+							   (GCopyFunc)g_object_ref,
+							   NULL);
+
+	for (l = model->priv->providers; l != NULL; l = l->next)
+	{
+		ProviderInfo *provider_info = l->data;
+		update_provider_visibility (model, provider_info);
+	}
 }
 
 GList *
@@ -363,7 +972,7 @@ gtk_source_completion_model_get_visible_providers (GtkSourceCompletionModel *mod
 {
 	g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_MODEL (model), NULL);
 
-	return NULL;
+	return model->priv->visible_providers;
 }
 
 /* Other public functions */
@@ -371,7 +980,26 @@ gtk_source_completion_model_get_visible_providers (GtkSourceCompletionModel *mod
 void
 gtk_source_completion_model_clear (GtkSourceCompletionModel *model)
 {
+	GList *provider_node;
+
 	g_return_if_fail (GTK_SOURCE_IS_COMPLETION_MODEL (model));
+
+	for (provider_node = model->priv->providers;
+	     provider_node != NULL;
+	     provider_node = g_list_next (provider_node))
+	{
+		ProviderInfo *provider_info = provider_node->data;
+
+		if (provider_info->visible)
+		{
+			hide_provider (model, provider_info);
+			provider_info->visible = FALSE;
+		}
+	}
+
+	g_list_free_full (model->priv->providers, (GDestroyNotify)provider_info_free);
+	model->priv->providers = NULL;
+
 }
 
 /* If @only_visible is %TRUE, only the visible providers are taken into account. */
@@ -379,69 +1007,202 @@ gboolean
 gtk_source_completion_model_is_empty (GtkSourceCompletionModel *model,
                                       gboolean                  only_visible)
 {
-	g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_MODEL (model), FALSE);
+	GList *l;
 
-	return FALSE;
+	g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_MODEL (model), TRUE);
+
+	for (l = model->priv->providers; l != NULL; l = l->next)
+	{
+		ProviderInfo *info = l->data;
+
+		if (only_visible && !info->visible)
+		{
+			continue;
+		}
+
+		/* A provider can not be empty */
+		return FALSE;
+	}
+
+	return TRUE;
 }
 
+/* Returns the number of proposals. The header is not taken into account. */
 guint
 gtk_source_completion_model_n_proposals (GtkSourceCompletionModel    *model,
                                          GtkSourceCompletionProvider *provider)
 {
+	GList *provider_node;
+	ProviderInfo *provider_info;
+	guint nb_items;
+
 	g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_MODEL (model), 0);
 	g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_PROVIDER (provider), 0);
 
-	return 0;
+	provider_node = get_provider_node (model, provider);
+
+	if (provider_node == NULL)
+	{
+		return 0;
+	}
+
+	provider_info = provider_node->data;
+	nb_items = provider_info->proposals->length;
+
+	if (model->priv->show_headers)
+	{
+		return nb_items - 1;
+	}
+	else
+	{
+		return nb_items;
+	}
 }
 
 void
 gtk_source_completion_model_set_show_headers (GtkSourceCompletionModel *model,
                                               gboolean                  show_headers)
 {
+	GList *l;
+
 	g_return_if_fail (GTK_SOURCE_IS_COMPLETION_MODEL (model));
+
+	if (model->priv->show_headers == show_headers)
+	{
+		return;
+	}
+
+	model->priv->show_headers = show_headers;
+
+	for (l = model->priv->providers; l != NULL; l = l->next)
+	{
+		if (show_headers)
+		{
+			show_header (model, l);
+		}
+		else
+		{
+			hide_header (model, l);
+		}
+	}
 }
 
 gboolean
 gtk_source_completion_model_iter_is_header (GtkSourceCompletionModel *model,
                                             GtkTreeIter              *iter)
 {
+	GList *proposal_node;
+	ProposalInfo *proposal_info;
+
 	g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_MODEL (model), FALSE);
 	g_return_val_if_fail (iter != NULL, FALSE);
 	g_return_val_if_fail (iter->user_data != NULL, FALSE);
 
-	return FALSE;
+	proposal_node = iter->user_data;
+	proposal_info = proposal_node->data;
+
+	return is_header (proposal_info);
 }
 
 gboolean
 gtk_source_completion_model_iter_previous (GtkSourceCompletionModel *model,
                                            GtkTreeIter              *iter)
 {
+	/* This function is the symmetry of tree_model_iter_next(). */
+
+	ProposalInfo *proposal_info;
+	GList *proposal_node;
+	GList *cur_provider;
+
 	g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_MODEL (model), FALSE);
 	g_return_val_if_fail (iter != NULL, FALSE);
 	g_return_val_if_fail (iter->user_data != NULL, FALSE);
 
-	return FALSE;
+	proposal_node = iter->user_data;
+	proposal_info = proposal_node->data;
+
+	/* Find the right provider, which must be visible */
+
+	cur_provider = proposal_info->provider_node;
+
+	if (proposal_node->prev == NULL)
+	{
+		cur_provider = g_list_previous (cur_provider);
+	}
+
+	cur_provider = find_previous_visible_provider (cur_provider);
+
+	if (cur_provider == NULL)
+	{
+		return FALSE;
+	}
+
+	/* Find the proposal inside the provider */
+
+	if (cur_provider == proposal_info->provider_node)
+	{
+		iter->user_data = g_list_previous (proposal_node);
+	}
+	else
+	{
+		ProviderInfo *info = cur_provider->data;
+		iter->user_data = info->proposals->tail;
+	}
+
+	g_assert (iter->user_data != NULL);
+
+	return TRUE;
 }
 
 gboolean
 gtk_source_completion_model_iter_last (GtkSourceCompletionModel *model,
                                        GtkTreeIter              *iter)
 {
+	GList *last_provider;
+	ProviderInfo *provider_info;
+
 	g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_MODEL (model), FALSE);
 	g_return_val_if_fail (iter != NULL, FALSE);
 
-	return FALSE;
+	last_provider = g_list_last (model->priv->providers);
+
+	if (last_provider == NULL)
+	{
+		return FALSE;
+	}
+
+	provider_info = last_provider->data;
+
+	iter->user_data = provider_info->proposals->tail;
+	g_assert (iter->user_data != NULL);
+
+	if (!provider_info->visible)
+	{
+		return gtk_source_completion_model_iter_previous (model, iter);
+	}
+
+	return TRUE;
 }
 
 /* Get all the providers (visible and hidden), sorted by priority in descending
  * order (the highest priority first).
+ * Free the return value with g_list_free().
  */
 GList *
 gtk_source_completion_model_get_providers (GtkSourceCompletionModel *model)
 {
+	GList *l;
+	GList *ret = NULL;
+
 	g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_MODEL (model), NULL);
 
-	return NULL;
+	for (l = model->priv->providers; l != NULL; l = l->next)
+	{
+		ProviderInfo *info = l->data;
+		ret = g_list_prepend (ret, info->completion_provider);
+	}
+
+	return g_list_reverse (ret);
 }
 
 gboolean
@@ -455,3 +1216,9 @@ gtk_source_completion_model_iter_equal (GtkSourceCompletionModel *model,
 
 	return iter1->user_data == iter2->user_data;
 }
+
+GtkSourceCompletionModel*
+gtk_source_completion_model_new (void)
+{
+	return g_object_new (GTK_SOURCE_TYPE_COMPLETION_MODEL, NULL);
+}



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