[gtksourceview/wip/completion-model: 1/17] CompletionModel: new implementation
- From: SÃbastien Wilmet <swilmet src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gtksourceview/wip/completion-model: 1/17] CompletionModel: new implementation
- Date: Tue, 22 Jan 2013 18:51:32 +0000 (UTC)
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]