/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; coding: utf-8 -*- */
/* gtksourcecompletion.c
* This file is part of GtkSourceView
*
* Copyright (C) 2007 -2009 Jesús Barbero Rodríguez <chuchiperriman@gmail.com>
* 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
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* GtkSourceView 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
/**
* SECTION:completion
* @title: GtkSourceCompletion
* @short_description: Main Completion Object
*
* The completion system helps the user when he writes some text, such
* as words, command names, functions, and suchlike. Proposals can be
* shown, to complete the text the user is writing. Each proposal can
* contain an additional piece of information, that is displayed when
* the "Details" button is active.
*
* Proposals are created via a #GtkSourceCompletionProvider. There can
* be for example a provider to complete words (see
* #GtkSourceCompletionWords), another provider for the completion of
* function's names, etc. To add a provider, call
* gtk_source_completion_add_provider().
*
* When the completion is activated, a #GtkSourceCompletionContext object is
* created. The providers are asked whether they match the context, with
* gtk_source_completion_provider_match(). If a provider doesn't match the
* context, it will not be visible in the completion window. On the
* other hand, if the provider matches the context, its proposals will
* be displayed.
*
* When several providers match, they are all shown in the completion
* window, but one can switch between providers: see the
* #GtkSourceCompletion::move-page signal. It is also possible to
* activate the first proposals with key bindings, see the
* #GtkSourceCompletion:accelerators property.
*
* The #GtkSourceCompletionProposal interface represents a proposal.
* The #GtkSourceCompletionItem class is a simple implementation of this
* interface.
*
* If a proposal contains extra information (see
* gtk_source_completion_provider_get_info_widget()), it will be
* displayed in a #GtkSourceCompletionInfo window, which appears when
* the "Details" button is clicked.
*
* A #GtkSourceCompletionInfo window can also be used to display
* calltips. When no proposals are available, it can be useful to
* display extra information like a function's prototype (number of
* parameters, types of parameters, etc).
*
* Each #GtkSourceView object is associated with a #GtkSourceCompletion
* instance. This instance can be obtained with
* gtk_source_view_get_completion(). The #GtkSourceView class contains also the
* #GtkSourceView::show-completion signal.
*
* A same #GtkSourceCompletionProvider object can be used for several
* #GtkSourceCompletion.
*
* # GtkSourceCompletion as GtkBuildable
*
* The GtkSourceCompletion implementation of the #GtkBuildable interface exposes
* the info window object (see gtk_source_completion_get_info_window()) with the
* internal-child "info_window".
*
* An example of a UI definition fragment with GtkSourceCompletion:
* |[
* <object class="GtkSourceCompletion">
* <property name="select_on_show">False</property>
* <child internal-child="info_window">
* <object class="GtkSourceCompletionInfo">
* <property name="border_width">6</property>
* </object>
* </child>
* </object>
* ]|
*/
/* Idea to improve the code: use a composite widget template. This class is not
* a GtkWidget, so some refactoring needs to be done, to have a subclass of
* GtkSourceCompletionInfo for the main completion window.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include "gtksourcecompletion.h"
#include "gtksourcecompletion-private.h"
#include "gtksourcecompletionmodel.h"
#include "gtksourcecompletioncontext.h"
#include "gtksourcecompletioninfo.h"
#include "gtksourcecompletionproposal.h"
#include "gtksourcecompletionprovider.h"
#include "gtksourcecompletioncontainer.h"
#include "gtksourcebuffer.h"
#include "gtksourceview.h"
#include "gtksourceview-i18n.h"
enum
{
SHOW,
HIDE,
POPULATE_CONTEXT,
/* Actions */
ACTIVATE_PROPOSAL,
MOVE_CURSOR,
MOVE_PAGE,
N_SIGNALS
};
enum
{
PROP_0,
PROP_VIEW,
PROP_REMEMBER_INFO_VISIBILITY,
PROP_SELECT_ON_SHOW,
PROP_SHOW_HEADERS,
PROP_SHOW_ICONS,
PROP_ACCELERATORS,
PROP_AUTO_COMPLETE_DELAY,
PROP_PROVIDER_PAGE_SIZE,
PROP_PROPOSAL_PAGE_SIZE
};
struct _GtkSourceCompletionPrivate
{
GtkSourceCompletionInfo *main_window;
GtkSourceCompletionInfo *info_window;
/* Bottom bar, containing the "Details" button and the selection image
* and label. */
GtkWidget *bottom_bar;
/* Image and label in the bottom bar, on the right, for showing which
* provider(s) are selected. */
GtkImage *selection_image;
GtkLabel *selection_label;
/* The default widget for the info window */
GtkLabel *default_info;
/* The "Details" button, for showing the info window */
GtkToggleButton *info_button;
/* List of proposals */
GtkTreeView *tree_view_proposals;
GtkCellRenderer *cell_renderer_proposal;
/* Completion management */
GtkSourceCompletionModel *model_proposals;
GList *providers;
GtkSourceCompletionContext *context;
GList *active_providers;
GList *running_providers;
guint show_timed_out_id;
GtkTextBuffer *buffer;
GList *auto_completion_selection;
GtkSourceCompletionContext *auto_completion_context;
/* Number of times the interactive completion is blocked */
guint block_interactive_num;
/* Properties */
/* Weak reference to the view. You must check if view != NULL before
* using it.
*/
GtkSourceView *view;
guint num_accelerators;
guint auto_complete_delay;
guint proposal_page_size;
guint provider_page_size;
guint remember_info_visibility : 1;
guint select_on_show : 1;
guint show_headers : 1;
guint show_icons : 1;
};
static guint signals[N_SIGNALS];
static void gtk_source_completion_buildable_interface_init (GtkBuildableIface *iface);
G_DEFINE_TYPE_WITH_CODE (GtkSourceCompletion, gtk_source_completion, G_TYPE_OBJECT,
G_ADD_PRIVATE (GtkSourceCompletion)
G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
gtk_source_completion_buildable_interface_init))
static void
scroll_to_iter (GtkSourceCompletion *completion,
GtkTreeIter *iter)
{
GtkTreePath *path;
GtkTreeIter prev_iter = *iter;
path = gtk_tree_model_get_path (GTK_TREE_MODEL (completion->priv->model_proposals),
iter);
gtk_tree_view_scroll_to_cell (completion->priv->tree_view_proposals,
path, NULL,
FALSE, 0, 0);
gtk_tree_path_free (path);
if (gtk_source_completion_model_iter_previous (completion->priv->model_proposals, &prev_iter) &&
gtk_source_completion_model_iter_is_header (completion->priv->model_proposals, &prev_iter))
{
/* If we want to scroll to the first proposal of a provider,
* it's better to show the header too, if there is a header.
* We first scroll to the proposal, and then to the
* header, so we are sure that the proposal is visible.
*/
path = gtk_tree_model_get_path (GTK_TREE_MODEL (completion->priv->model_proposals),
&prev_iter);
gtk_tree_view_scroll_to_cell (completion->priv->tree_view_proposals,
path, NULL,
FALSE, 0, 0);
gtk_tree_path_free (path);
}
}
/* Returns %TRUE if a proposal is selected.
* Call g_object_unref() on @provider and @proposal when no longer needed.
*/
static gboolean
get_selected_proposal (GtkSourceCompletion *completion,
GtkSourceCompletionProvider **provider,
GtkSourceCompletionProposal **proposal)
{
GtkTreeIter iter;
GtkTreeSelection *selection;
selection = gtk_tree_view_get_selection (completion->priv->tree_view_proposals);
if (!gtk_tree_selection_get_selected (selection, NULL, &iter))
{
return FALSE;
}
if (gtk_source_completion_model_iter_is_header (completion->priv->model_proposals, &iter))
{
return FALSE;
}
if (provider != NULL)
{
gtk_tree_model_get (GTK_TREE_MODEL (completion->priv->model_proposals), &iter,
GTK_SOURCE_COMPLETION_MODEL_COLUMN_PROVIDER, provider,
-1);
}
if (proposal != NULL)
{
gtk_tree_model_get (GTK_TREE_MODEL (completion->priv->model_proposals), &iter,
GTK_SOURCE_COMPLETION_MODEL_COLUMN_PROPOSAL, proposal,
-1);
}
return TRUE;
}
/* Returns %TRUE if the first proposal is selected. */
static gboolean
check_first_selected (GtkSourceCompletion *completion)
{
GtkTreeSelection *selection;
GtkTreeIter iter;
if (get_selected_proposal (completion, NULL, NULL) ||
!completion->priv->select_on_show)
{
return FALSE;
}
if (!gtk_source_completion_model_first_proposal (completion->priv->model_proposals, &iter))
{
return FALSE;
}
selection = gtk_tree_view_get_selection (completion->priv->tree_view_proposals);
gtk_tree_selection_select_iter (selection, &iter);
scroll_to_iter (completion, &iter);
return TRUE;
}
static void
get_iter_at_insert (GtkSourceCompletion *completion,
GtkTextIter *iter)
{
gtk_text_buffer_get_iter_at_mark (completion->priv->buffer,
iter,
gtk_text_buffer_get_insert (completion->priv->buffer));
}
static GList *
select_providers (GList *providers,
GtkSourceCompletionContext *context)
{
GtkTextIter context_iter;
GList *selection = NULL;
GList *l;
if (!gtk_source_completion_context_get_iter (context, &context_iter))
{
return NULL;
}
for (l = providers; l != NULL; l = l->next)
{
GtkSourceCompletionProvider *provider = l->data;
gboolean good_activation = (gtk_source_completion_provider_get_activation (provider) &
gtk_source_completion_context_get_activation (context)) != 0;
if (good_activation &&
gtk_source_completion_provider_match (provider, context))
{
selection = g_list_prepend (selection, provider);
}
}
return g_list_reverse (selection);
}
static gint
minimum_auto_complete_delay (GtkSourceCompletion *completion,
GList *providers)
{
gint min_delay = completion->priv->auto_complete_delay;
while (providers != NULL)
{
GtkSourceCompletionProvider *provider = providers->data;
gint delay = gtk_source_completion_provider_get_interactive_delay (provider);
if (0 <= delay && delay < min_delay)
{
min_delay = delay;
}
providers = g_list_next (providers);
}
return min_delay;
}
static void
reset_completion (GtkSourceCompletion *completion)
{
if (completion->priv->show_timed_out_id != 0)
{
g_source_remove (completion->priv->show_timed_out_id);
completion->priv->show_timed_out_id = 0;
}
if (completion->priv->context != NULL)
{
/* Inform providers of cancellation through the context */
_gtk_source_completion_context_cancel (completion->priv->context);
g_clear_object (&completion->priv->context);
}
g_list_free (completion->priv->running_providers);
g_list_free (completion->priv->active_providers);
completion->priv->running_providers = NULL;
completion->priv->active_providers = NULL;
}
/* A separator is a character like (, a space etc. An _ is not a separator. */
static gboolean
is_separator (const gunichar ch)
{
if (g_unichar_isprint (ch) &&
(g_unichar_isalnum (ch) || ch == g_utf8_get_char ("_")))
{
return FALSE;
}
return TRUE;
}
/* Assigns @start_word to the start position of the word, and @end_word to the
* end position.
*/
static void
get_word_iter (GtkTextBuffer *buffer,
GtkTextIter *start_word,
GtkTextIter *end_word)
{
gtk_text_buffer_get_iter_at_mark (buffer,
end_word,
gtk_text_buffer_get_insert (buffer));
*start_word = *end_word;
while (gtk_text_iter_backward_char (start_word))
{
gunichar ch = gtk_text_iter_get_char (start_word);
if (is_separator (ch))
{
gtk_text_iter_forward_char (start_word);
return;
}
}
}
static void
replace_current_word (GtkTextBuffer *buffer,
const gchar *new_text)
{
GtkTextIter word_start;
GtkTextIter word_end;
get_word_iter (buffer, &word_start, &word_end);
gtk_text_buffer_begin_user_action (buffer);
gtk_text_buffer_delete (buffer, &word_start, &word_end);
if (new_text != NULL)
{
gtk_text_buffer_insert (buffer, &word_start, new_text, -1);
}
gtk_text_buffer_end_user_action (buffer);
}
static void
update_window_position (GtkSourceCompletion *completion)
{
GtkSourceCompletionProvider *provider;
GtkSourceCompletionProposal *proposal;
GtkTextIter iter;
gboolean iter_set = FALSE;
if (completion->priv->view == NULL)
{
return;
}
/* The model can be modified while there is no completion context, for
* example when the headers are shown or hidden. This triggers a signal
* to update the window position, but if there is no completion context,
* no need to update the window position (the window is normally hidden
* in this case). When a new population is done, this function will be
* called again, so no problem.
*/
if (completion->priv->context == NULL)
{
return;
}
if (get_selected_proposal (completion, &provider, &proposal))
{
GtkTextIter context_iter;
gboolean valid_context;
valid_context = gtk_source_completion_context_get_iter (completion->priv->context,
&context_iter);
if (valid_context &&
gtk_source_completion_provider_get_start_iter (provider,
completion->priv->context,
proposal,
&iter))
{
iter_set = TRUE;
}
g_object_unref (provider);
g_object_unref (proposal);
}
if (!iter_set)
{
GtkTextIter end_word;
get_word_iter (completion->priv->buffer, &iter, &end_word);
}
gtk_source_completion_info_move_to_iter (completion->priv->main_window,
GTK_TEXT_VIEW (completion->priv->view),
&iter);
}
static void
set_info_widget (GtkSourceCompletion *completion,
GtkWidget *new_widget)
{
GtkWidget *cur_widget = gtk_bin_get_child (GTK_BIN (completion->priv->info_window));
if (cur_widget == new_widget)
{
return;
}
if (cur_widget != NULL)
{
gtk_container_remove (GTK_CONTAINER (completion->priv->info_window), cur_widget);
}
gtk_container_add (GTK_CONTAINER (completion->priv->info_window), new_widget);
}
static void
update_proposal_info_state (GtkSourceCompletion *completion)
{
GtkSourceCompletionProvider *provider = NULL;
GtkSourceCompletionProposal *proposal = NULL;
GtkWidget *info_widget;
if (!get_selected_proposal (completion, &provider, &proposal))
{
gtk_widget_set_sensitive (GTK_WIDGET (completion->priv->info_button), FALSE);
return;
}
info_widget = gtk_source_completion_provider_get_info_widget (provider, proposal);
if (info_widget != NULL)
{
set_info_widget (completion, info_widget);
gtk_widget_set_sensitive (GTK_WIDGET (completion->priv->info_button), TRUE);
gtk_source_completion_provider_update_info (provider,
proposal,
completion->priv->info_window);
}
else
{
gchar *text = gtk_source_completion_proposal_get_info (proposal);
if (text != NULL)
{
set_info_widget (completion, GTK_WIDGET (completion->priv->default_info));
gtk_widget_set_sensitive (GTK_WIDGET (completion->priv->info_button), TRUE);
gtk_label_set_markup (completion->priv->default_info, text);
g_free (text);
}
else
{
gtk_widget_set_sensitive (GTK_WIDGET (completion->priv->info_button), FALSE);
}
}
g_object_unref (provider);
g_object_unref (proposal);
}
static void
update_info_window_visibility (GtkSourceCompletion *completion)
{
if (gtk_widget_get_sensitive (GTK_WIDGET (completion->priv->info_button)) &&
gtk_toggle_button_get_active (completion->priv->info_button))
{
gtk_widget_show (GTK_WIDGET (completion->priv->info_window));
}
else
{
gtk_widget_hide (GTK_WIDGET (completion->priv->info_window));
}
}
static void
update_proposal_info (GtkSourceCompletion *completion)
{
update_proposal_info_state (completion);
update_info_window_visibility (completion);
}
static void
gtk_source_completion_show_default (GtkSourceCompletion *completion)
{
if (completion->priv->view == NULL)
{
return;
}
gtk_widget_show (GTK_WIDGET (completion->priv->main_window));
/* Do the autosize when the widget is visible. It doesn't work if it is
* done before.
*/
gtk_tree_view_columns_autosize (completion->priv->tree_view_proposals);
if (!completion->priv->remember_info_visibility)
{
gtk_toggle_button_set_active (completion->priv->info_button, FALSE);
}
update_proposal_info (completion);
gtk_widget_grab_focus (GTK_WIDGET (completion->priv->view));
}
static void
gtk_source_completion_hide_default (GtkSourceCompletion *completion)
{
gtk_widget_hide (GTK_WIDGET (completion->priv->info_window));
gtk_widget_hide (GTK_WIDGET (completion->priv->main_window));
}
static void
gtk_source_completion_proposals_size_allocate (GtkSourceCompletion *completion,
GtkAllocation *allocation,
GtkWidget *widget)
{
GtkTreeViewColumn *column;
gint cell_offset = 0;
gint column_offset;
gint focus_padding;
gint horizontal_separator;
gint x_offset = 0;
if (!gtk_widget_get_realized (GTK_WIDGET (completion->priv->tree_view_proposals)))
{
return;
}
gtk_widget_style_get (GTK_WIDGET (completion->priv->tree_view_proposals),
"focus-padding", &focus_padding,
"horizontal-separator", &horizontal_separator,
NULL);
column = gtk_tree_view_get_column (completion->priv->tree_view_proposals, 1);
column_offset = gtk_tree_view_column_get_x_offset (column);
gtk_tree_view_column_cell_get_position (column,
completion->priv->cell_renderer_proposal,
&cell_offset,
NULL);
x_offset = column_offset + cell_offset + horizontal_separator + focus_padding;
gtk_tree_view_convert_bin_window_to_widget_coords (completion->priv->tree_view_proposals,
x_offset,
0,
&x_offset,
NULL);
gtk_widget_translate_coordinates (GTK_WIDGET (completion->priv->tree_view_proposals),
GTK_WIDGET (completion->priv->main_window),
x_offset,
0,
&x_offset,
NULL);
_gtk_source_completion_info_set_xoffset (completion->priv->main_window, -x_offset);
}
static void
gtk_source_completion_activate_proposal (GtkSourceCompletion *completion)
{
GtkSourceCompletionProvider *provider = NULL;
GtkSourceCompletionProposal *proposal = NULL;
GtkTextIter insert_iter;
GtkTextIter context_iter;
gboolean valid_context;
gboolean activated;
if (completion->priv->view == NULL)
{
return;
}
if (!get_selected_proposal (completion, &provider, &proposal))
{
return;
}
get_iter_at_insert (completion, &insert_iter);
gtk_source_completion_block_interactive (completion);
activated = gtk_source_completion_provider_activate_proposal (provider, proposal, &insert_iter);
valid_context = (completion->priv->context != NULL &&
gtk_source_completion_context_get_iter (completion->priv->context,
&context_iter));
if (!activated && valid_context)
{
GtkTextIter start_iter;
gchar *text = gtk_source_completion_proposal_get_text (proposal);
gboolean has_start = gtk_source_completion_provider_get_start_iter (provider,
completion->priv->context,
proposal,
&start_iter);
if (has_start)
{
gtk_text_buffer_begin_user_action (completion->priv->buffer);
gtk_text_buffer_delete (completion->priv->buffer, &start_iter, &insert_iter);
gtk_text_buffer_insert (completion->priv->buffer, &start_iter, text, -1);
gtk_text_buffer_end_user_action (completion->priv->buffer);
}
else
{
replace_current_word (completion->priv->buffer, text);
}
g_free (text);
}
gtk_source_completion_unblock_interactive (completion);
gtk_source_completion_hide (completion);
g_object_unref (provider);
g_object_unref (proposal);
}
static void
update_info_position (GtkSourceCompletion *completion)
{
GdkScreen *screen;
gint x, y;
gint width, height;
gint screen_width;
gint info_width;
gtk_window_get_position (GTK_WINDOW (completion->priv->main_window), &x, &y);
gtk_window_get_size (GTK_WINDOW (completion->priv->main_window), &width, &height);
gtk_window_get_size (GTK_WINDOW (completion->priv->info_window), &info_width, NULL);
screen = gtk_window_get_screen (GTK_WINDOW (completion->priv->main_window));
screen_width = gdk_screen_get_width (screen);
/* Determine on which side to place it */
if (x + width + info_width >= screen_width)
{
x -= info_width;
}
else
{
x += width;
}
gtk_window_move (GTK_WINDOW (completion->priv->info_window), x, y);
}
static GtkSourceCompletionProvider *
get_visible_provider (GtkSourceCompletion *completion)
{
GList *visible = gtk_source_completion_model_get_visible_providers (completion->priv->model_proposals);
if (visible != NULL)
{
return GTK_SOURCE_COMPLETION_PROVIDER (visible->data);
}
else
{
return NULL;
}
}
static void
get_num_visible_providers (GtkSourceCompletion *completion,
guint *num,
guint *current)
{
GList *providers = gtk_source_completion_model_get_providers (completion->priv->model_proposals);
GtkSourceCompletionProvider *visible = get_visible_provider (completion);
*num = g_list_length (providers);
*current = 0;
if (visible != NULL)
{
gint idx = g_list_index (providers, visible);
g_return_if_fail (idx != -1);
*current = idx + 1;
}
g_list_free (providers);
}
static void
update_selection_label (GtkSourceCompletion *completion)
{
guint pos;
guint num;
gchar *name;
gchar *selection_text;
GtkSourceCompletionProvider *visible;
get_num_visible_providers (completion, &num, &pos);
if (num <= 1)
{
/* At most one provider. All the proposals are shown. */
gtk_image_clear (completion->priv->selection_image);
gtk_widget_hide (GTK_WIDGET (completion->priv->selection_label));
return;
}
visible = get_visible_provider (completion);
if (visible == NULL)
{
/* Translators: "All" is used as a label in the status bar of the
popup, telling that all completion pages are shown. */
name = g_strdup_printf("<b>%s</b>", _("All"));
gtk_image_clear (completion->priv->selection_image);
}
else
{
gchar *temp_name = gtk_source_completion_provider_get_name (visible);
name = g_markup_escape_text (temp_name, -1);
g_free (temp_name);
gtk_image_set_from_pixbuf (completion->priv->selection_image,
gtk_source_completion_provider_get_icon (visible));
}
selection_text = g_strdup_printf ("<small>%s (%d/%d)</small>", name, pos + 1, num + 1);
gtk_label_set_markup (completion->priv->selection_label, selection_text);
gtk_widget_show (GTK_WIDGET (completion->priv->selection_label));
g_free (selection_text);
g_free (name);
}
static gboolean
get_next_iter (GtkSourceCompletion *completion,
gint num,
GtkTreeIter *iter)
{
GtkTreeSelection *selection;
gboolean has_selection;
selection = gtk_tree_view_get_selection (completion->priv->tree_view_proposals);
has_selection = gtk_tree_selection_get_selected (selection, NULL, iter);
if (!has_selection)
{
return gtk_source_completion_model_first_proposal (completion->priv->model_proposals,
iter);
}
while (num > 0)
{
if (!gtk_source_completion_model_next_proposal (completion->priv->model_proposals, iter))
{
return gtk_source_completion_model_last_proposal (completion->priv->model_proposals,
iter);
}
num--;
}
return TRUE;
}
static gboolean
get_previous_iter (GtkSourceCompletion *completion,
gint num,
GtkTreeIter *iter)
{
GtkTreeSelection *selection;
gboolean has_selection;
selection = gtk_tree_view_get_selection (completion->priv->tree_view_proposals);
has_selection = gtk_tree_selection_get_selected (selection, NULL, iter);
if (!has_selection)
{
return gtk_source_completion_model_last_proposal (completion->priv->model_proposals,
iter);
}
while (num > 0)
{
if (!gtk_source_completion_model_previous_proposal (completion->priv->model_proposals,
iter))
{
return gtk_source_completion_model_first_proposal (completion->priv->model_proposals,
iter);
}
num--;
}
return TRUE;
}
static void
gtk_source_completion_move_cursor (GtkSourceCompletion *completion,
GtkScrollStep step,
gint num)
{
GtkTreeIter iter;
gboolean ok;
if (step == GTK_SCROLL_ENDS)
{
if (num > 0)
{
ok = gtk_source_completion_model_last_proposal (completion->priv->model_proposals,
&iter);
}
else
{
ok = gtk_source_completion_model_first_proposal (completion->priv->model_proposals,
&iter);
}
}
else
{
if (step == GTK_SCROLL_PAGES)
{
num *= completion->priv->proposal_page_size;
}
if (num > 0)
{
ok = get_next_iter (completion, num, &iter);
}
else
{
ok = get_previous_iter (completion, -1 * num, &iter);
}
}
if (ok)
{
GtkTreeSelection *selection;
selection = gtk_tree_view_get_selection (completion->priv->tree_view_proposals);
gtk_tree_selection_select_iter (selection, &iter);
scroll_to_iter (completion, &iter);
}
}
static GList *
get_last_provider (GtkSourceCompletion *completion)
{
GList *providers = gtk_source_completion_model_get_providers (completion->priv->model_proposals);
GList *ret;
g_return_val_if_fail (providers != NULL, NULL);
if (providers->next == NULL)
{
ret = NULL;
}
else
{
ret = g_list_copy (g_list_last (providers));
}
g_list_free (providers);
return ret;
}
static GList *
providers_cycle_forward (GList *all_providers,
GList *position,
gint num)
{
GList *l = position;
gint i;
if (all_providers == NULL || all_providers->next == NULL)
{
return NULL;
}
for (i = 0; i < num; i++)
{
l = l == NULL ? all_providers : l->next;
}
return l;
}
static GList *
providers_cycle_backward (GList *all_providers,
GList *position,
gint num)
{
gint i;
GList *l = position;
GList *end = g_list_last (all_providers);
if (all_providers == NULL || all_providers->next == NULL)
{
return NULL;
}
for (i = 0; i < num; i++)
{
l = l == NULL ? end : l->prev;
}
return l;
}
static GList *
get_next_provider (GtkSourceCompletion *completion,
gint num)
{
GList *providers;
GList *visible_providers;
GList *position;
GList *ret;
providers = gtk_source_completion_model_get_providers (completion->priv->model_proposals);
visible_providers = gtk_source_completion_model_get_visible_providers (completion->priv->model_proposals);
if (visible_providers == NULL)
{
position = NULL;
}
else
{
position = g_list_find (providers, visible_providers->data);
}
position = providers_cycle_forward (providers, position, num);
if (position == NULL)
{
ret = NULL;
}
else
{
ret = g_list_append (NULL, position->data);
}
g_list_free (providers);
return ret;
}
static GList *
get_previous_provider (GtkSourceCompletion *completion,
gint num)
{
GList *providers;
GList *visible_providers;
GList *position;
GList *ret;
providers = gtk_source_completion_model_get_providers (completion->priv->model_proposals);
visible_providers = gtk_source_completion_model_get_visible_providers (completion->priv->model_proposals);
if (visible_providers == NULL)
{
position = NULL;
}
else
{
position = g_list_find (providers, visible_providers->data);
}
position = providers_cycle_backward (providers, position, num);
if (position == NULL)
{
ret = NULL;
}
else
{
ret = g_list_append (NULL, position->data);
}
g_list_free (providers);
return ret;
}
static void
gtk_source_completion_move_page (GtkSourceCompletion *completion,
GtkScrollStep step,
gint num)
{
GList *visible_providers = NULL;
if (step == GTK_SCROLL_ENDS)
{
if (num > 0)
{
visible_providers = get_last_provider (completion);
}
else
{
visible_providers = NULL;
}
}
else
{
if (step == GTK_SCROLL_PAGES)
{
num *= completion->priv->provider_page_size;
}
if (num > 0)
{
visible_providers = get_next_provider (completion, num);
}
else
{
visible_providers = get_previous_provider (completion, -1 * num);
}
}
gtk_tree_view_set_model (completion->priv->tree_view_proposals, NULL);
gtk_tree_view_columns_autosize (completion->priv->tree_view_proposals);
gtk_source_completion_model_set_visible_providers (completion->priv->model_proposals,
visible_providers);
gtk_tree_view_set_model (completion->priv->tree_view_proposals,
GTK_TREE_MODEL (completion->priv->model_proposals));
update_selection_label (completion);
check_first_selected (completion);
g_list_free (visible_providers);
}
/* Begins at 0. Returns -1 if no accelerators available for @iter. */
static gint
get_accel_at_iter (GtkSourceCompletion *completion,
GtkTreeIter *iter)
{
GtkTreeIter it;
guint accel;
if (gtk_source_completion_model_iter_is_header (completion->priv->model_proposals, iter))
{
return -1;
}
if (!gtk_source_completion_model_first_proposal (completion->priv->model_proposals, &it))
{
g_return_val_if_reached (-1);
}
for (accel = 0; accel < completion->priv->num_accelerators; accel++)
{
if (gtk_source_completion_model_iter_equal (completion->priv->model_proposals,
iter,
&it))
{
return accel;
}
if (!gtk_source_completion_model_next_proposal (completion->priv->model_proposals, &it))
{
return -1;
}
}
return -1;
}
static void
render_proposal_accelerator_func (GtkTreeViewColumn *column,
GtkCellRenderer *cell,
GtkTreeModel *model,
GtkTreeIter *iter,
GtkSourceCompletion *completion)
{
gint accel = get_accel_at_iter (completion, iter);
gchar *text = NULL;
if (accel != -1)
{
text = g_strdup_printf ("<small><b>%d</b></small>", (accel + 1) % 10);
}
g_object_set (cell, "markup", text, NULL);
g_free (text);
}
static gboolean
activate_by_accelerator (GtkSourceCompletion *completion,
gint num)
{
GtkTreeSelection *selection;
GtkTreeIter iter;
gint i;
if (completion->priv->num_accelerators == 0)
{
return FALSE;
}
num = num == 0 ? 9 : num - 1;
if (num < 0 || completion->priv->num_accelerators <= (guint)num)
{
return FALSE;
}
if (!gtk_source_completion_model_first_proposal (completion->priv->model_proposals, &iter))
{
return FALSE;
}
for (i = 0; i < num; i++)
{
if (!gtk_source_completion_model_next_proposal (completion->priv->model_proposals, &iter))
{
return FALSE;
}
}
selection = gtk_tree_view_get_selection (completion->priv->tree_view_proposals);
gtk_tree_selection_select_iter (selection, &iter);
gtk_source_completion_activate_proposal (completion);
return TRUE;
}
static void
selection_changed_cb (GtkTreeSelection *selection,
GtkSourceCompletion *completion)
{
update_proposal_info (completion);
if (get_selected_proposal (completion, NULL, NULL))
{
update_window_position (completion);
}
}
static gboolean
gtk_source_completion_configure_event (GtkWidget *widget,
GdkEventConfigure *event,
GtkSourceCompletion *completion)
{
update_info_position (completion);
return FALSE;
}
static gboolean
hide_completion_cb (GtkSourceCompletion *completion)
{
gtk_source_completion_hide (completion);
return FALSE;
}
static gboolean
view_key_press_event_cb (GtkSourceView *view,
GdkEventKey *event,
GtkSourceCompletion *completion)
{
static gboolean mnemonic_keyval_set = FALSE;
static guint mnemonic_keyval = GDK_KEY_VoidSymbol;
GdkModifierType mod;
GtkBindingSet *binding_set;
if (!gtk_widget_get_visible (GTK_WIDGET (completion->priv->main_window)))
{
return FALSE;
}
if (G_UNLIKELY (!mnemonic_keyval_set))
{
const gchar *label_text = gtk_button_get_label (GTK_BUTTON (completion->priv->info_button));
GtkWidget *label = gtk_label_new_with_mnemonic (label_text);
g_object_ref_sink (label);
mnemonic_keyval = gtk_label_get_mnemonic_keyval (GTK_LABEL (label));
mnemonic_keyval_set = TRUE;
g_object_unref (label);
}
mod = gtk_accelerator_get_default_mod_mask () & event->state;
/* Handle info button mnemonic */
if ((mod & GDK_MOD1_MASK) != 0 &&
event->keyval == mnemonic_keyval &&
gtk_widget_get_sensitive (GTK_WIDGET (completion->priv->info_button)))
{
gtk_toggle_button_set_active (completion->priv->info_button,
!gtk_toggle_button_get_active (completion->priv->info_button));
return TRUE;
}
if ((mod & GDK_MOD1_MASK) != 0 &&
GDK_KEY_0 <= event->keyval && event->keyval <= GDK_KEY_9)
{
if (activate_by_accelerator (completion, event->keyval - GDK_KEY_0))
{
return TRUE;
}
}
binding_set = gtk_binding_set_by_class (GTK_SOURCE_COMPLETION_GET_CLASS (completion));
if (gtk_binding_set_activate (binding_set,
event->keyval,
event->state,
G_OBJECT (completion)))
{
return TRUE;
}
return FALSE;
}
static void
buffer_mark_set_cb (GtkTextBuffer *buffer,
GtkTextIter *iter,
GtkTextMark *mark,
GtkSourceCompletion *completion)
{
if (mark == gtk_text_buffer_get_insert (buffer))
{
gtk_source_completion_hide (completion);
}
}
static void
update_transient_for_info (GObject *window,
GParamSpec *spec,
GtkSourceCompletion *completion)
{
gtk_window_set_transient_for (GTK_WINDOW (completion->priv->info_window),
gtk_window_get_transient_for (GTK_WINDOW (completion->priv->main_window)));
}
static void
replace_model (GtkSourceCompletion *completion)
{
if (completion->priv->model_proposals != NULL)
{
g_object_unref (completion->priv->model_proposals);
}
completion->priv->model_proposals = gtk_source_completion_model_new ();
gtk_source_completion_model_set_show_headers (completion->priv->model_proposals,
completion->priv->show_headers);
}
/* Takes ownership of @providers and @context. */
static void
update_completion (GtkSourceCompletion *completion,
GList *providers,
GtkSourceCompletionContext *context)
{
GList *item;
GtkTextIter context_iter;
gboolean valid_context;
/* Copy the parameters, because they can be freed by reset_completion(). */
GList *providers_copy = g_list_copy (providers);
GtkSourceCompletionContext *context_copy = g_object_ref_sink (context);
/* Make sure to first cancel any running completion */
reset_completion (completion);
completion->priv->context = context_copy;
completion->priv->running_providers = g_list_copy (providers_copy);
completion->priv->active_providers = g_list_copy (providers_copy);
/* Create a new CompletionModel */
gtk_tree_view_set_model (completion->priv->tree_view_proposals, NULL);
gtk_tree_view_columns_autosize (completion->priv->tree_view_proposals);
replace_model (completion);
valid_context = gtk_source_completion_context_get_iter (context_copy, &context_iter);
if (valid_context)
{
for (item = providers_copy; item != NULL; item = g_list_next (item))
{
GtkSourceCompletionProvider *provider = item->data;
gtk_source_completion_provider_populate (provider, context_copy);
}
}
g_list_free (providers_copy);
}
static gboolean
auto_completion_final (GtkSourceCompletion *completion)
{
/* Store and set to NULL because update_completion will cancel the last
completion, which will also remove the timeout source which in turn
would free these guys */
GtkSourceCompletionContext *context = completion->priv->auto_completion_context;
GList *selection = completion->priv->auto_completion_selection;
completion->priv->auto_completion_context = NULL;
completion->priv->auto_completion_selection = NULL;
update_completion (completion, selection, context);
g_list_free (selection);
g_object_unref (context);
return G_SOURCE_REMOVE;
}
static void
auto_completion_destroy (GtkSourceCompletion *completion)
{
if (completion->priv->auto_completion_context != NULL)
{
g_object_unref (completion->priv->auto_completion_context);
completion->priv->auto_completion_context = NULL;
}
g_list_free (completion->priv->auto_completion_selection);
completion->priv->auto_completion_selection = NULL;
}
static void
start_interactive_completion (GtkSourceCompletion *completion,
GtkTextIter *iter)
{
GtkSourceCompletionContext *context;
GList *providers;
gint delay;
reset_completion (completion);
/* Create the context */
context = gtk_source_completion_create_context (completion, iter);
g_object_ref_sink (context);
g_object_set (context,
"activation",
GTK_SOURCE_COMPLETION_ACTIVATION_INTERACTIVE,
NULL);
g_signal_emit (completion, signals[POPULATE_CONTEXT], 0, context);
/* Select providers */
providers = select_providers (completion->priv->providers, context);
if (providers == NULL)
{
g_object_unref (context);
return;
}
/* Create the timeout */
delay = minimum_auto_complete_delay (completion, providers);
completion->priv->auto_completion_context = context;
completion->priv->auto_completion_selection = providers;
completion->priv->show_timed_out_id =
g_timeout_add_full (G_PRIORITY_DEFAULT,
delay,
(GSourceFunc)auto_completion_final,
completion,
(GDestroyNotify)auto_completion_destroy);
}
static void
update_active_completion (GtkSourceCompletion *completion,
GtkTextIter *new_iter)
{
GList *selected_providers;
g_assert (completion->priv->context != NULL);
g_object_set (completion->priv->context,
"iter", new_iter,
NULL);
selected_providers = select_providers (completion->priv->providers,
completion->priv->context);
if (selected_providers != NULL)
{
update_completion (completion,
selected_providers,
completion->priv->context);
g_list_free (selected_providers);
}
else
{
gtk_source_completion_hide (completion);
}
}
static void
buffer_delete_range_cb (GtkTextBuffer *buffer,
GtkTextIter *start,
GtkTextIter *end,
GtkSourceCompletion *completion)
{
if (completion->priv->context != NULL)
{
update_active_completion (completion, start);
}
}
static void
buffer_insert_text_cb (GtkTextBuffer *buffer,
GtkTextIter *location,
gchar *text,
gint len,
GtkSourceCompletion *completion)
{
if (completion->priv->context != NULL)
{
update_active_completion (completion, location);
}
else
{
start_interactive_completion (completion, location);
}
}
static void
update_bottom_bar_visibility (GtkSourceCompletion *completion)
{
GList *providers;
guint nb_providers;
providers = gtk_source_completion_model_get_providers (completion->priv->model_proposals);
nb_providers = g_list_length (providers);
g_list_free (providers);
if (nb_providers > 1)
{
gtk_widget_show (completion->priv->bottom_bar);
return;
}
if (gtk_source_completion_model_has_info (completion->priv->model_proposals))
{
gtk_widget_show (completion->priv->bottom_bar);
}
else
{
gtk_widget_hide (completion->priv->bottom_bar);
}
}
static void
style_context_changed (GtkStyleContext *style_context,
GtkSourceCompletion *completion)
{
PangoFontDescription *font_desc = NULL;
gtk_style_context_save (style_context);
gtk_style_context_set_state (style_context, GTK_STATE_FLAG_NORMAL);
gtk_style_context_get (style_context,
gtk_style_context_get_state (style_context),
GTK_STYLE_PROPERTY_FONT, &font_desc,
NULL);
gtk_style_context_restore (style_context);
/*
* Work around issue where when a proposal provides "<b>markup</b>" and
* the weight is set in the font description, the <b> markup will not
* have it's weight respected. This seems to be happening because the
* weight mask is getting set in pango_font_description_from_string()
* even if the the value is set to normal. That matter is complicated
* because PangoAttrFontDesc and PangoAttrWeight will both have the
* same starting offset in the PangoLayout.
* https://bugzilla.gnome.org/show_bug.cgi?id=755968
*/
if (PANGO_WEIGHT_NORMAL == pango_font_description_get_weight (font_desc))
{
pango_font_description_unset_fields (font_desc, PANGO_FONT_MASK_WEIGHT);
}
g_object_set (completion->priv->cell_renderer_proposal,
"font-desc", font_desc,
NULL);
pango_font_description_free (font_desc);
}
static void
populating_done (GtkSourceCompletion *completion,
GtkSourceCompletionContext *context)
{
if (gtk_source_completion_model_is_empty (completion->priv->model_proposals, TRUE))
{
gtk_source_completion_hide (completion);
return;
}
gtk_tree_view_set_model (completion->priv->tree_view_proposals,
GTK_TREE_MODEL (completion->priv->model_proposals));
update_selection_label (completion);
update_bottom_bar_visibility (completion);
if (!check_first_selected (completion))
{
/* Update the window position only if the first proposal is not
* selected, because if it is selected, the window position will
* already be updated.
*/
update_window_position (completion);
}
if (!gtk_widget_get_visible (GTK_WIDGET (completion->priv->main_window)))
{
g_signal_emit (completion, signals[SHOW], 0);
}
}
static void
gtk_source_completion_dispose (GObject *object)
{
GtkSourceCompletion *completion = GTK_SOURCE_COMPLETION (object);
reset_completion (completion);
if (completion->priv->view != NULL)
{
g_object_remove_weak_pointer (G_OBJECT (completion->priv->view),
(gpointer *)&completion->priv->view);
completion->priv->view = NULL;
}
g_clear_object (&completion->priv->buffer);
g_clear_object (&completion->priv->default_info);
g_clear_object (&completion->priv->model_proposals);
if (completion->priv->info_window != NULL)
{
gtk_widget_destroy (GTK_WIDGET (completion->priv->info_window));
completion->priv->info_window = NULL;
}
if (completion->priv->main_window != NULL)
{
gtk_widget_destroy (GTK_WIDGET (completion->priv->main_window));
completion->priv->main_window = NULL;
}
g_list_free_full (completion->priv->providers, g_object_unref);
completion->priv->providers = NULL;
G_OBJECT_CLASS (gtk_source_completion_parent_class)->dispose (object);
}
/* Unconditionnally block interactive completion, without taking into account
* priv->block_interactive_num.
* g_signal_handlers_block_by_func() has a counter too, so you may think that
* block_interactive_num is useless. But it is useful when the buffer changes,
* to keep the signal handler blocked on the new buffer.
*/
static void
block_interactive (GtkSourceCompletion *completion)
{
g_signal_handlers_block_by_func (completion->priv->buffer,
buffer_insert_text_cb,
completion);
g_signal_handlers_block_by_func (completion->priv->buffer,
buffer_delete_range_cb,
completion);
}
static void
connect_buffer (GtkSourceCompletion *completion)
{
GtkTextBuffer *new_buffer = NULL;
if (completion->priv->view != NULL)
{
new_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (completion->priv->view));
}
if (completion->priv->buffer == new_buffer)
{
return;
}
if (completion->priv->buffer != NULL)
{
g_signal_handlers_disconnect_by_func (completion->priv->buffer,
buffer_mark_set_cb,
completion);
g_signal_handlers_disconnect_by_func (completion->priv->buffer,
gtk_source_completion_block_interactive,
completion);
g_signal_handlers_disconnect_by_func (completion->priv->buffer,
gtk_source_completion_unblock_interactive,
completion);
g_signal_handlers_disconnect_by_func (completion->priv->buffer,
buffer_delete_range_cb,
completion);
g_signal_handlers_disconnect_by_func (completion->priv->buffer,
buffer_insert_text_cb,
completion);
reset_completion (completion);
g_object_unref (completion->priv->buffer);
}
completion->priv->buffer = new_buffer;
if (new_buffer == NULL)
{
return;
}
g_object_ref (completion->priv->buffer);
g_signal_connect_object (new_buffer,
"mark-set",
G_CALLBACK (buffer_mark_set_cb),
completion,
G_CONNECT_AFTER);
g_signal_connect_object (new_buffer,
"undo",
G_CALLBACK (gtk_source_completion_block_interactive),
completion,
G_CONNECT_SWAPPED);
g_signal_connect_object (new_buffer,
"undo",
G_CALLBACK (gtk_source_completion_unblock_interactive),
completion,
G_CONNECT_SWAPPED | G_CONNECT_AFTER);
g_signal_connect_object (new_buffer,
"redo",
G_CALLBACK (gtk_source_completion_block_interactive),
completion,
G_CONNECT_SWAPPED);
g_signal_connect_object (new_buffer,
"redo",
G_CALLBACK (gtk_source_completion_unblock_interactive),
completion,
G_CONNECT_SWAPPED | G_CONNECT_AFTER);
g_signal_connect_object (new_buffer,
"delete-range",
G_CALLBACK (buffer_delete_range_cb),
completion,
G_CONNECT_AFTER);
g_signal_connect_object (new_buffer,
"insert-text",
G_CALLBACK (buffer_insert_text_cb),
completion,
G_CONNECT_AFTER);
if (completion->priv->block_interactive_num > 0)
{
block_interactive (completion);
}
}
static void
connect_view (GtkSourceCompletion *completion,
GtkSourceView *view)
{
g_assert (completion->priv->view == NULL);
completion->priv->view = view;
g_object_add_weak_pointer (G_OBJECT (view),
(gpointer *)&completion->priv->view);
g_signal_connect_object (completion->priv->view,
"focus-out-event",
G_CALLBACK (hide_completion_cb),
completion,
G_CONNECT_SWAPPED);
g_signal_connect_object (completion->priv->view,
"button-press-event",
G_CALLBACK (hide_completion_cb),
completion,
G_CONNECT_SWAPPED);
g_signal_connect_object (completion->priv->view,
"key-press-event",
G_CALLBACK (view_key_press_event_cb),
completion,
0);
g_signal_connect_object (completion->priv->view,
"paste-clipboard",
G_CALLBACK (gtk_source_completion_block_interactive),
completion,
G_CONNECT_SWAPPED);
g_signal_connect_object (completion->priv->view,
"paste-clipboard",
G_CALLBACK (gtk_source_completion_unblock_interactive),
completion,
G_CONNECT_SWAPPED | G_CONNECT_AFTER);
connect_buffer (completion);
g_signal_connect_object (completion->priv->view,
"notify::buffer",
G_CALLBACK (connect_buffer),
completion,
G_CONNECT_SWAPPED);
}
static void
gtk_source_completion_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GtkSourceCompletion *completion;
g_return_if_fail (GTK_SOURCE_IS_COMPLETION (object));
completion = GTK_SOURCE_COMPLETION (object);
switch (prop_id)
{
case PROP_VIEW:
g_value_set_object (value, completion->priv->view);
break;
case PROP_REMEMBER_INFO_VISIBILITY:
g_value_set_boolean (value, completion->priv->remember_info_visibility);
break;
case PROP_SELECT_ON_SHOW:
g_value_set_boolean (value, completion->priv->select_on_show);
break;
case PROP_SHOW_HEADERS:
g_value_set_boolean (value, completion->priv->show_headers);
break;
case PROP_SHOW_ICONS:
g_value_set_boolean (value, completion->priv->show_icons);
break;
case PROP_ACCELERATORS:
g_value_set_uint (value, completion->priv->num_accelerators);
break;
case PROP_AUTO_COMPLETE_DELAY:
g_value_set_uint (value, completion->priv->auto_complete_delay);
break;
case PROP_PROPOSAL_PAGE_SIZE:
g_value_set_uint (value, completion->priv->proposal_page_size);
break;
case PROP_PROVIDER_PAGE_SIZE:
g_value_set_uint (value, completion->priv->provider_page_size);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gtk_source_completion_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GtkSourceCompletion *completion;
g_return_if_fail (GTK_SOURCE_IS_COMPLETION (object));
completion = GTK_SOURCE_COMPLETION (object);
switch (prop_id)
{
case PROP_VIEW:
connect_view (completion, g_value_get_object (value));
break;
case PROP_REMEMBER_INFO_VISIBILITY:
completion->priv->remember_info_visibility = g_value_get_boolean (value);
break;
case PROP_SELECT_ON_SHOW:
completion->priv->select_on_show = g_value_get_boolean (value);
break;
case PROP_SHOW_HEADERS:
completion->priv->show_headers = g_value_get_boolean (value);
if (completion->priv->model_proposals != NULL)
{
gtk_source_completion_model_set_show_headers (completion->priv->model_proposals,
completion->priv->show_headers);
}
break;
case PROP_SHOW_ICONS:
completion->priv->show_icons = g_value_get_boolean (value);
break;
case PROP_ACCELERATORS:
completion->priv->num_accelerators = g_value_get_uint (value);
break;
case PROP_AUTO_COMPLETE_DELAY:
completion->priv->auto_complete_delay = g_value_get_uint (value);
break;
case PROP_PROPOSAL_PAGE_SIZE:
completion->priv->proposal_page_size = g_value_get_uint (value);
break;
case PROP_PROVIDER_PAGE_SIZE:
completion->priv->provider_page_size = g_value_get_uint (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static gboolean
selection_func (GtkTreeSelection *selection,
GtkTreeModel *model,
GtkTreePath *path,
gboolean path_currently_selected,
GtkSourceCompletion *completion)
{
GtkTreeIter iter;
gtk_tree_model_get_iter (model, &iter, path);
if (gtk_source_completion_model_iter_is_header (completion->priv->model_proposals,
&iter))
{
/* A header must never be selected */
g_return_val_if_fail (!path_currently_selected, TRUE);
return FALSE;
}
else
{
return TRUE;
}
}
static void
accelerators_notify_cb (GtkSourceCompletion *completion,
GParamSpec *pspec,
GtkTreeViewColumn *column)
{
gtk_tree_view_column_set_visible (column, completion->priv->num_accelerators > 0);
}
static void
cell_icon_func (GtkTreeViewColumn *column,
GtkCellRenderer *cell,
GtkTreeModel *model,
GtkTreeIter *iter,
gpointer data)
{
GdkPixbuf *pixbuf;
gchar *icon_name;
GIcon *gicon;
gboolean set = FALSE;
gtk_tree_model_get (model, iter,
GTK_SOURCE_COMPLETION_MODEL_COLUMN_ICON, &pixbuf,
GTK_SOURCE_COMPLETION_MODEL_COLUMN_ICON_NAME, &icon_name,
GTK_SOURCE_COMPLETION_MODEL_COLUMN_GICON, &gicon,
-1);
if (pixbuf != NULL)
{
g_object_set (cell, "pixbuf", pixbuf, NULL);
g_object_unref (pixbuf);
set = TRUE;
}
if (icon_name != NULL)
{
g_object_set (cell, "icon-name", icon_name, NULL);
g_free (icon_name);
set = TRUE;
}
if (gicon != NULL)
{
g_object_set (cell, "gicon", gicon, NULL);
g_object_unref (gicon);
set = TRUE;
}
if (!set)
{
g_object_set (cell, "icon-name", NULL, NULL);
}
}
static void
init_tree_view (GtkSourceCompletion *completion,
GtkBuilder *builder)
{
GtkTreeSelection *selection;
GtkTreeViewColumn *column;
GtkCellRenderer *cell_renderer;
GtkStyleContext *style_context;
GdkRGBA* background_color = NULL;
GdkRGBA foreground_color;
completion->priv->tree_view_proposals = GTK_TREE_VIEW (gtk_builder_get_object (builder, "tree_view_proposals"));
g_signal_connect_swapped (completion->priv->tree_view_proposals,
"row-activated",
G_CALLBACK (gtk_source_completion_activate_proposal),
completion);
g_signal_connect_swapped (completion->priv->tree_view_proposals,
"size-allocate",
G_CALLBACK (gtk_source_completion_proposals_size_allocate),
completion);
/* Selection */
selection = gtk_tree_view_get_selection (completion->priv->tree_view_proposals);
gtk_tree_selection_set_select_function (selection,
(GtkTreeSelectionFunc)selection_func,
completion,
NULL);
g_signal_connect (selection,
"changed",
G_CALLBACK (selection_changed_cb),
completion);
/* Icon cell renderer */
cell_renderer = GTK_CELL_RENDERER (gtk_builder_get_object (builder, "cell_renderer_icon"));
column = GTK_TREE_VIEW_COLUMN (gtk_builder_get_object (builder, "tree_view_column_icon"));
/* We use a cell function instead of plain attributes for the icon since
* the pixbuf renderer will not renderer any icon if pixbuf is set to NULL.
* See https://bugzilla.gnome.org/show_bug.cgi?id=753510
*/
gtk_tree_view_column_set_cell_data_func (column,
cell_renderer,
cell_icon_func,
NULL,
NULL);
gtk_tree_view_column_set_attributes (column, cell_renderer,
"cell-background-set", GTK_SOURCE_COMPLETION_MODEL_COLUMN_IS_HEADER,
NULL);
style_context = gtk_widget_get_style_context (GTK_WIDGET (completion->priv->tree_view_proposals));
gtk_style_context_save (style_context);
gtk_style_context_set_state (style_context, GTK_STATE_FLAG_INSENSITIVE);
gtk_style_context_get (style_context,
gtk_style_context_get_state (style_context),
"background-color", &background_color,
NULL);
gtk_style_context_get_color (style_context,
gtk_style_context_get_state (style_context),
&foreground_color);
gtk_style_context_restore (style_context);
g_object_set (cell_renderer,
"cell-background-rgba", background_color,
NULL);
g_object_bind_property (completion, "show-icons",
cell_renderer, "visible",
G_BINDING_SYNC_CREATE);
/* Proposal text cell renderer */
cell_renderer = GTK_CELL_RENDERER (gtk_builder_get_object (builder, "cell_renderer_proposal"));
completion->priv->cell_renderer_proposal = cell_renderer;
column = GTK_TREE_VIEW_COLUMN (gtk_builder_get_object (builder, "tree_view_column_proposal"));
gtk_tree_view_column_set_attributes (column, cell_renderer,
"markup", GTK_SOURCE_COMPLETION_MODEL_COLUMN_MARKUP,
"cell-background-set", GTK_SOURCE_COMPLETION_MODEL_COLUMN_IS_HEADER,
"foreground-set", GTK_SOURCE_COMPLETION_MODEL_COLUMN_IS_HEADER,
NULL);
g_object_set (cell_renderer,
"foreground-rgba", &foreground_color,
"cell-background-rgba", background_color,
NULL);
/* Accelerators cell renderer */
column = GTK_TREE_VIEW_COLUMN (gtk_builder_get_object (builder, "tree_view_column_accelerator"));
cell_renderer = GTK_CELL_RENDERER (gtk_builder_get_object (builder, "cell_renderer_accelerator"));
gtk_tree_view_column_set_attributes (column,
cell_renderer,
"cell-background-set", GTK_SOURCE_COMPLETION_MODEL_COLUMN_IS_HEADER,
NULL);
g_object_set (cell_renderer,
"foreground-rgba", &foreground_color,
"cell-background-rgba", background_color,
NULL);
gtk_tree_view_column_set_cell_data_func (column,
cell_renderer,
(GtkTreeCellDataFunc)render_proposal_accelerator_func,
completion,
NULL);
g_signal_connect_object (completion,
"notify::accelerators",
G_CALLBACK (accelerators_notify_cb),
column,
0);
gdk_rgba_free (background_color);
}
static void
init_main_window (GtkSourceCompletion *completion,
GtkBuilder *builder)
{
if (completion->priv->view == NULL)
{
return;
}
completion->priv->main_window = GTK_SOURCE_COMPLETION_INFO (gtk_builder_get_object (builder, "main_window"));
completion->priv->info_button = GTK_TOGGLE_BUTTON (gtk_builder_get_object (builder, "info_button"));
completion->priv->selection_image = GTK_IMAGE (gtk_builder_get_object (builder, "selection_image"));
completion->priv->selection_label = GTK_LABEL (gtk_builder_get_object (builder, "selection_label"));
completion->priv->bottom_bar = GTK_WIDGET (gtk_builder_get_object (builder, "bottom_bar"));
gtk_container_set_border_width (GTK_CONTAINER (completion->priv->main_window), 0);
gtk_window_set_attached_to (GTK_WINDOW (completion->priv->main_window),
GTK_WIDGET (completion->priv->view));
g_signal_connect (completion->priv->main_window,
"configure-event",
G_CALLBACK (gtk_source_completion_configure_event),
completion);
g_signal_connect_swapped (completion->priv->main_window,
"size-allocate",
G_CALLBACK (update_window_position),
completion);
g_signal_connect (completion->priv->main_window,
"delete-event",
G_CALLBACK (gtk_widget_hide_on_delete),
NULL);
g_signal_connect (completion->priv->main_window,
"notify::transient-for",
G_CALLBACK (update_transient_for_info),
completion);
g_signal_connect_swapped (completion->priv->info_button,
"toggled",
G_CALLBACK (update_info_window_visibility),
completion);
}
static void
init_info_window (GtkSourceCompletion *completion)
{
completion->priv->info_window = gtk_source_completion_info_new ();
g_object_ref_sink (completion->priv->info_window);
gtk_window_set_attached_to (GTK_WINDOW (completion->priv->info_window),
GTK_WIDGET (completion->priv->main_window));
g_signal_connect_swapped (completion->priv->info_window,
"size-allocate",
G_CALLBACK (update_info_position),
completion);
/* Default info widget */
completion->priv->default_info = GTK_LABEL (gtk_label_new (NULL));
g_object_ref_sink (completion->priv->default_info);
gtk_widget_show (GTK_WIDGET (completion->priv->default_info));
}
static void
connect_style_context (GtkSourceCompletion *completion)
{
GtkStyleContext *style_context;
if (completion->priv->view == NULL)
{
return;
}
style_context = gtk_widget_get_style_context (GTK_WIDGET (completion->priv->view));
g_signal_connect_object (style_context,
"changed",
G_CALLBACK (style_context_changed),
completion,
G_CONNECT_AFTER);
style_context_changed (style_context, completion);
}
static void
gtk_source_completion_constructed (GObject *object)
{
GtkSourceCompletion *completion = GTK_SOURCE_COMPLETION (object);
GError *error = NULL;
GtkBuilder *builder = gtk_builder_new ();
GtkSourceCompletionContainer *container = _gtk_source_completion_container_new ();
g_object_ref_sink (container);
gtk_builder_set_translation_domain (builder, GETTEXT_PACKAGE);
/* GtkSourceCompletionContainer is a private type. */
gtk_builder_expose_object (builder, "completion_container", G_OBJECT (container));
gtk_builder_add_from_resource (builder,
"/org/gnome/gtksourceview/ui/gtksourcecompletion.ui",
&error);
if (error != NULL)
{
g_error ("Error while loading the completion UI: %s", error->message);
}
init_tree_view (completion, builder);
init_main_window (completion, builder);
init_info_window (completion);
connect_style_context (completion);
g_object_unref (builder);
g_object_unref (container);
G_OBJECT_CLASS (gtk_source_completion_parent_class)->constructed (object);
}
static void
gtk_source_completion_class_init (GtkSourceCompletionClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GtkBindingSet *binding_set;
object_class->get_property = gtk_source_completion_get_property;
object_class->set_property = gtk_source_completion_set_property;
object_class->dispose = gtk_source_completion_dispose;
object_class->constructed = gtk_source_completion_constructed;
klass->show = gtk_source_completion_show_default;
klass->hide = gtk_source_completion_hide_default;
klass->move_cursor = gtk_source_completion_move_cursor;
klass->move_page = gtk_source_completion_move_page;
klass->activate_proposal = gtk_source_completion_activate_proposal;
/**
* GtkSourceCompletion:view:
*
* The #GtkSourceView bound to the completion object.
*/
g_object_class_install_property (object_class,
PROP_VIEW,
g_param_spec_object ("view",
"View",
"The GtkSourceView bound to the completion",
GTK_SOURCE_TYPE_VIEW,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS));
/**
* GtkSourceCompletion:remember-info-visibility:
*
* Determines whether the visibility of the info window should be
* saved when the completion is hidden, and restored when the completion
* is shown again.
*/
g_object_class_install_property (object_class,
PROP_REMEMBER_INFO_VISIBILITY,
g_param_spec_boolean ("remember-info-visibility",
"Remember Info Visibility",
"Remember the last info window visibility state",
FALSE,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT |
G_PARAM_STATIC_STRINGS));
/**
* GtkSourceCompletion:select-on-show:
*
* Determines whether the first proposal should be selected when the
* completion is first shown.
*/
g_object_class_install_property (object_class,
PROP_SELECT_ON_SHOW,
g_param_spec_boolean ("select-on-show",
"Select on Show",
"Select first proposal when completion is shown",
TRUE,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT |
G_PARAM_STATIC_STRINGS));
/**
* GtkSourceCompletion:show-headers:
*
* Determines whether provider headers should be shown in the proposal
* list. It can be useful to disable when there is only one provider.
*/
g_object_class_install_property (object_class,
PROP_SHOW_HEADERS,
g_param_spec_boolean ("show-headers",
"Show Headers",
"Show provider headers when proposals from multiple providers are available",
TRUE,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT |
G_PARAM_STATIC_STRINGS));
/**
* GtkSourceCompletion:show-icons:
*
* Determines whether provider and proposal icons should be shown in
* the completion popup.
*/
g_object_class_install_property (object_class,
PROP_SHOW_ICONS,
g_param_spec_boolean ("show-icons",
"Show Icons",
"Show provider and proposal icons in the completion popup",
TRUE,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT |
G_PARAM_STATIC_STRINGS));
/**
* GtkSourceCompletion:accelerators:
*
* Number of keyboard accelerators to show for the first proposals. For
* example, to activate the first proposal, the user can press
* <keycombo><keycap>Alt</keycap><keycap>1</keycap></keycombo>.
*/
g_object_class_install_property (object_class,
PROP_ACCELERATORS,
g_param_spec_uint ("accelerators",
"Accelerators",
"Number of proposal accelerators to show",
0,
10,
5,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT |
G_PARAM_STATIC_STRINGS));
/**
* GtkSourceCompletion:auto-complete-delay:
*
* Determines the popup delay (in milliseconds) at which the completion
* will be shown for interactive completion.
*/
g_object_class_install_property (object_class,
PROP_AUTO_COMPLETE_DELAY,
g_param_spec_uint ("auto-complete-delay",
"Auto Complete Delay",
"Completion popup delay for interactive completion",
0,
G_MAXUINT,
250,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT |
G_PARAM_STATIC_STRINGS));
/**
* GtkSourceCompletion:provider-page-size:
*
* The scroll page size of the provider pages in the completion window.
*
* See the #GtkSourceCompletion::move-page signal.
*/
g_object_class_install_property (object_class,
PROP_PROVIDER_PAGE_SIZE,
g_param_spec_uint ("provider-page-size",
"Provider Page Size",
"Provider scrolling page size",
1,
G_MAXUINT,
5,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT |
G_PARAM_STATIC_STRINGS));
/**
* GtkSourceCompletion:proposal-page-size:
*
* The scroll page size of the proposals in the completion window. In
* other words, when <keycap>PageDown</keycap> or
* <keycap>PageUp</keycap> is pressed, the selected
* proposal becomes the one which is located one page size backward or
* forward.
*
* See also the #GtkSourceCompletion::move-cursor signal.
*/
g_object_class_install_property (object_class,
PROP_PROPOSAL_PAGE_SIZE,
g_param_spec_uint ("proposal-page-size",
"Proposal Page Size",
"Proposal scrolling page size",
1,
G_MAXUINT,
5,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT |
G_PARAM_STATIC_STRINGS));
/**
* GtkSourceCompletion::show:
* @completion: The #GtkSourceCompletion who emits the signal
*
* Emitted when the completion window is shown. The default handler
* will actually show the window.
*/
signals[SHOW] =
g_signal_new ("show",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (GtkSourceCompletionClass, show),
NULL, NULL, NULL,
G_TYPE_NONE, 0);
/**
* GtkSourceCompletion::hide:
* @completion: The #GtkSourceCompletion who emits the signal
*
* Emitted when the completion window is hidden. The default handler
* will actually hide the window.
*/
signals[HIDE] =
g_signal_new ("hide",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (GtkSourceCompletionClass, hide),
NULL, NULL, NULL,
G_TYPE_NONE, 0);
/**
* GtkSourceCompletion::populate-context:
* @completion: The #GtkSourceCompletion who emits the signal
* @context: The #GtkSourceCompletionContext for the current completion
*
* Emitted just before starting to populate the completion with providers.
* You can use this signal to add additional attributes in the context.
*/
signals[POPULATE_CONTEXT] =
g_signal_new ("populate-context",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (GtkSourceCompletionClass, populate_context),
NULL, NULL, NULL,
G_TYPE_NONE,
1,
GTK_SOURCE_TYPE_COMPLETION_CONTEXT);
/* Actions */
/**
* GtkSourceCompletion::move-cursor:
* @completion: The #GtkSourceCompletion who emits the signal
* @step: The #GtkScrollStep by which to move the cursor
* @num: The amount of steps to move the cursor
*
* The #GtkSourceCompletion::move-cursor signal is a keybinding
* signal which gets emitted when the user initiates a cursor
* movement.
*
* The <keycap>Up</keycap>, <keycap>Down</keycap>,
* <keycap>PageUp</keycap>, <keycap>PageDown</keycap>,
* <keycap>Home</keycap> and <keycap>End</keycap> keys are bound to the
* normal behavior expected by those keys.
*
* When @step is equal to %GTK_SCROLL_PAGES, the page size is defined by
* the #GtkSourceCompletion:proposal-page-size property. It is used for
* the <keycap>PageDown</keycap> and <keycap>PageUp</keycap> keys.
*
* Applications should not connect to it, but may emit it with
* g_signal_emit_by_name() if they need to control the cursor
* programmatically.
*/
signals [MOVE_CURSOR] =
g_signal_new ("move-cursor",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (GtkSourceCompletionClass, move_cursor),
NULL, NULL, NULL,
G_TYPE_NONE,
2,
GTK_TYPE_SCROLL_STEP,
G_TYPE_INT);
/**
* GtkSourceCompletion::move-page:
* @completion: The #GtkSourceCompletion who emits the signal
* @step: The #GtkScrollStep by which to move the page
* @num: The amount of steps to move the page
*
* The #GtkSourceCompletion::move-page signal is a keybinding
* signal which gets emitted when the user initiates a page
* movement (i.e. switches between provider pages).
*
* <keycombo><keycap>Control</keycap><keycap>Left</keycap></keycombo>
* is for going to the previous provider.
* <keycombo><keycap>Control</keycap><keycap>Right</keycap></keycombo>
* is for going to the next provider.
* <keycombo><keycap>Control</keycap><keycap>Home</keycap></keycombo>
* is for displaying all the providers.
* <keycombo><keycap>Control</keycap><keycap>End</keycap></keycombo>
* is for going to the last provider.
*
* When @step is equal to #GTK_SCROLL_PAGES, the page size is defined by
* the #GtkSourceCompletion:provider-page-size property.
*
* Applications should not connect to it, but may emit it with
* g_signal_emit_by_name() if they need to control the page selection
* programmatically.
*/
signals [MOVE_PAGE] =
g_signal_new ("move-page",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (GtkSourceCompletionClass, move_page),
NULL, NULL, NULL,
G_TYPE_NONE,
2,
GTK_TYPE_SCROLL_STEP,
G_TYPE_INT);
/**
* GtkSourceCompletion::activate-proposal:
* @completion: The #GtkSourceCompletion who emits the signal
*
* The #GtkSourceCompletion::activate-proposal signal is a
* keybinding signal which gets emitted when the user initiates
* a proposal activation.
*
* Applications should not connect to it, but may emit it with
* g_signal_emit_by_name() if they need to control the proposal
* activation programmatically.
*/
signals [ACTIVATE_PROPOSAL] =
g_signal_new ("activate-proposal",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (GtkSourceCompletionClass, activate_proposal),
NULL, NULL, NULL,
G_TYPE_NONE, 0);
/* Key bindings */
binding_set = gtk_binding_set_by_class (klass);
gtk_binding_entry_add_signal (binding_set,
GDK_KEY_Down,
0,
"move-cursor",
2,
GTK_TYPE_SCROLL_STEP, GTK_SCROLL_STEPS,
G_TYPE_INT, 1);
gtk_binding_entry_add_signal (binding_set,
GDK_KEY_Page_Down,
0,
"move-cursor",
2,
GTK_TYPE_SCROLL_STEP, GTK_SCROLL_PAGES,
G_TYPE_INT, 1);
gtk_binding_entry_add_signal (binding_set,
GDK_KEY_Up,
0,
"move-cursor",
2,
GTK_TYPE_SCROLL_STEP, GTK_SCROLL_STEPS,
G_TYPE_INT, -1);
gtk_binding_entry_add_signal (binding_set,
GDK_KEY_Page_Up,
0,
"move-cursor",
2,
GTK_TYPE_SCROLL_STEP, GTK_SCROLL_PAGES,
G_TYPE_INT, -1);
gtk_binding_entry_add_signal (binding_set,
GDK_KEY_Home,
0,
"move-cursor",
2,
GTK_TYPE_SCROLL_STEP, GTK_SCROLL_ENDS,
G_TYPE_INT, -1);
gtk_binding_entry_add_signal (binding_set,
GDK_KEY_End,
0,
"move-cursor",
2,
GTK_TYPE_SCROLL_STEP, GTK_SCROLL_ENDS,
G_TYPE_INT, 1);
gtk_binding_entry_add_signal (binding_set,
GDK_KEY_Escape,
0,
"hide",
0);
gtk_binding_entry_add_signal (binding_set,
GDK_KEY_Return,
0,
"activate-proposal",
0);
gtk_binding_entry_add_signal (binding_set,
GDK_KEY_Tab,
0,
"activate-proposal",
0);
gtk_binding_entry_add_signal (binding_set,
GDK_KEY_Left,
GDK_CONTROL_MASK,
"move-page",
2,
GTK_TYPE_SCROLL_STEP, GTK_SCROLL_STEPS,
G_TYPE_INT, -1);
gtk_binding_entry_add_signal (binding_set,
GDK_KEY_Right,
GDK_CONTROL_MASK,
"move-page",
2,
GTK_TYPE_SCROLL_STEP, GTK_SCROLL_STEPS,
G_TYPE_INT, 1);
gtk_binding_entry_add_signal (binding_set,
GDK_KEY_Home,
GDK_CONTROL_MASK,
"move-page",
2,
GTK_TYPE_SCROLL_STEP, GTK_SCROLL_ENDS,
G_TYPE_INT, -1);
gtk_binding_entry_add_signal (binding_set,
GDK_KEY_End,
GDK_CONTROL_MASK,
"move-page",
2,
GTK_TYPE_SCROLL_STEP, GTK_SCROLL_ENDS,
G_TYPE_INT, 1);
}
static void
gtk_source_completion_init (GtkSourceCompletion *completion)
{
completion->priv = gtk_source_completion_get_instance_private (completion);
}
static GObject *
gtk_source_completion_buildable_get_internal_child (GtkBuildable *buildable,
GtkBuilder *builder,
const gchar *childname)
{
GtkSourceCompletion *completion = GTK_SOURCE_COMPLETION (buildable);
if (g_strcmp0 (childname, "info_window") == 0)
{
return G_OBJECT (gtk_source_completion_get_info_window (completion));
}
return NULL;
}
static void
gtk_source_completion_buildable_interface_init (GtkBuildableIface *iface)
{
iface->get_internal_child = gtk_source_completion_buildable_get_internal_child;
}
void
_gtk_source_completion_add_proposals (GtkSourceCompletion *completion,
GtkSourceCompletionContext *context,
GtkSourceCompletionProvider *provider,
GList *proposals,
gboolean finished)
{
GList *item;
g_return_if_fail (GTK_SOURCE_IS_COMPLETION (completion));
g_return_if_fail (GTK_SOURCE_IS_COMPLETION_CONTEXT (context));
g_return_if_fail (GTK_SOURCE_IS_COMPLETION_PROVIDER (provider));
g_return_if_fail (completion->priv->context == context);
item = g_list_find (completion->priv->running_providers, provider);
g_return_if_fail (item != NULL);
gtk_source_completion_model_add_proposals (completion->priv->model_proposals,
provider,
proposals);
if (finished)
{
/* Remove provider from list of running providers */
completion->priv->running_providers =
g_list_delete_link (completion->priv->running_providers,
item);
if (completion->priv->running_providers == NULL)
{
populating_done (completion, context);
}
}
}
/**
* gtk_source_completion_show:
* @completion: a #GtkSourceCompletion.
* @providers: (element-type GtkSource.CompletionProvider) (nullable):
* a list of #GtkSourceCompletionProvider, or %NULL.
* @context: (transfer floating): The #GtkSourceCompletionContext
* with which to start the completion.
*
* Starts a new completion with the specified #GtkSourceCompletionContext and
* a list of potential candidate providers for completion.
*
* It can be convenient for showing a completion on-the-fly, without the need to
* add or remove providers to the #GtkSourceCompletion.
*
* Another solution is to add providers with
* gtk_source_completion_add_provider(), and implement
* gtk_source_completion_provider_match() for each provider.
*
* Returns: %TRUE if it was possible to the show completion window.
*/
gboolean
gtk_source_completion_show (GtkSourceCompletion *completion,
GList *providers,
GtkSourceCompletionContext *context)
{
GList *selected_providers;
g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION (completion), FALSE);
g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_CONTEXT (context), FALSE);
if (completion->priv->view == NULL)
{
return FALSE;
}
/* Make sure to clear any active completion */
reset_completion (completion);
/* We need to take owenership of the context right before doing
anything so we don't leak it or get a crash emitting the signal */
g_object_ref_sink (context);
if (providers == NULL)
{
g_object_unref (context);
return FALSE;
}
/* Populate the context */
g_signal_emit (completion, signals[POPULATE_CONTEXT], 0, context);
/* From the providers, select the ones that match the context */
selected_providers = select_providers (providers, context);
if (selected_providers == NULL)
{
g_object_unref (context);
gtk_source_completion_hide (completion);
return FALSE;
}
update_completion (completion, selected_providers, context);
g_list_free (selected_providers);
g_object_unref (context);
return TRUE;
}
/**
* gtk_source_completion_get_providers:
* @completion: a #GtkSourceCompletion.
*
* Get list of providers registered on @completion. The returned list is owned
* by the completion and should not be freed.
*
* Returns: (element-type GtkSource.CompletionProvider) (transfer none):
* list of #GtkSourceCompletionProvider.
*/
GList *
gtk_source_completion_get_providers (GtkSourceCompletion *completion)
{
g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION (completion), NULL);
return completion->priv->providers;
}
GQuark
gtk_source_completion_error_quark (void)
{
static GQuark quark = 0;
if (G_UNLIKELY (quark == 0))
{
quark = g_quark_from_static_string ("gtk-source-completion-error-quark");
}
return quark;
}
/**
* gtk_source_completion_new:
* @view: a #GtkSourceView.
*
* Creates a new #GtkSourceCompletion associated with @view.
*
* Returns: a new #GtkSourceCompletion.
*/
GtkSourceCompletion *
gtk_source_completion_new (GtkSourceView *view)
{
g_return_val_if_fail (GTK_SOURCE_IS_VIEW (view), NULL);
return g_object_new (GTK_SOURCE_TYPE_COMPLETION,
"view", view,
NULL);
}
/**
* gtk_source_completion_add_provider:
* @completion: a #GtkSourceCompletion.
* @provider: a #GtkSourceCompletionProvider.
* @error: a #GError.
*
* Add a new #GtkSourceCompletionProvider to the completion object. This will
* add a reference @provider, so make sure to unref your own copy when you
* no longer need it.
*
* Returns: %TRUE if @provider was successfully added, otherwise if @error
* is provided, it will be set with the error and %FALSE is returned.
*/
gboolean
gtk_source_completion_add_provider (GtkSourceCompletion *completion,
GtkSourceCompletionProvider *provider,
GError **error)
{
g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION (completion), FALSE);
g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_PROVIDER (provider), FALSE);
if (g_list_find (completion->priv->providers, provider) != NULL)
{
if (error != NULL)
{
g_set_error (error,
GTK_SOURCE_COMPLETION_ERROR,
GTK_SOURCE_COMPLETION_ERROR_ALREADY_BOUND,
"Provider is already bound to this completion object");
}
return FALSE;
}
completion->priv->providers = g_list_append (completion->priv->providers,
g_object_ref (provider));
if (error != NULL)
{
*error = NULL;
}
return TRUE;
}
/**
* gtk_source_completion_remove_provider:
* @completion: a #GtkSourceCompletion.
* @provider: a #GtkSourceCompletionProvider.
* @error: a #GError.
*
* Remove @provider from the completion.
*
* Returns: %TRUE if @provider was successfully removed, otherwise if @error
* is provided, it will be set with the error and %FALSE is returned.
*/
gboolean
gtk_source_completion_remove_provider (GtkSourceCompletion *completion,
GtkSourceCompletionProvider *provider,
GError **error)
{
GList *item;
g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION (completion), FALSE);
g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_PROVIDER (provider), FALSE);
item = g_list_find (completion->priv->providers, provider);
if (item == NULL)
{
if (error != NULL)
{
g_set_error (error,
GTK_SOURCE_COMPLETION_ERROR,
GTK_SOURCE_COMPLETION_ERROR_NOT_BOUND,
"Provider is not bound to this completion object");
}
return FALSE;
}
completion->priv->providers = g_list_remove_link (completion->priv->providers, item);
g_object_unref (provider);
if (error != NULL)
{
*error = NULL;
}
return TRUE;
}
/**
* gtk_source_completion_hide:
* @completion: a #GtkSourceCompletion.
*
* Hides the completion if it is active (visible).
*/
void
gtk_source_completion_hide (GtkSourceCompletion *completion)
{
g_return_if_fail (GTK_SOURCE_IS_COMPLETION (completion));
reset_completion (completion);
if (gtk_widget_get_visible (GTK_WIDGET (completion->priv->main_window)))
{
g_signal_emit (completion, signals[HIDE], 0);
}
}
/**
* gtk_source_completion_get_info_window:
* @completion: a #GtkSourceCompletion.
*
* The info widget is the window where the completion displays optional extra
* information of the proposal.
*
* Returns: (transfer none): The #GtkSourceCompletionInfo window
* associated with @completion.
*/
GtkSourceCompletionInfo *
gtk_source_completion_get_info_window (GtkSourceCompletion *completion)
{
g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION (completion), NULL);
return completion->priv->info_window;
}
/**
* gtk_source_completion_get_view:
* @completion: a #GtkSourceCompletion.
*
* The #GtkSourceView associated with @completion, or %NULL if the view has been
* destroyed.
*
* Returns: (nullable) (transfer none): The #GtkSourceView associated with
* @completion, or %NULL.
*/
GtkSourceView *
gtk_source_completion_get_view (GtkSourceCompletion *completion)
{
g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION (completion), NULL);
return completion->priv->view;
}
/**
* gtk_source_completion_create_context:
* @completion: a #GtkSourceCompletion.
* @position: (nullable): a #GtkTextIter, or %NULL.
*
* Create a new #GtkSourceCompletionContext for @completion. The position where
* the completion occurs can be specified by @position. If @position is %NULL,
* the current cursor position will be used.
*
* Returns: (transfer floating): a new #GtkSourceCompletionContext.
* The reference being returned is a 'floating' reference,
* so if you invoke gtk_source_completion_show() with this context
* you don't need to unref it.
*/
GtkSourceCompletionContext *
gtk_source_completion_create_context (GtkSourceCompletion *completion,
GtkTextIter *position)
{
GtkTextIter iter;
g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION (completion), NULL);
if (completion->priv->view == NULL)
{
return NULL;
}
if (position == NULL)
{
get_iter_at_insert (completion, &iter);
}
else
{
iter = *position;
}
return _gtk_source_completion_context_new (completion, &iter);
}
/**
* gtk_source_completion_move_window:
* @completion: a #GtkSourceCompletion.
* @iter: a #GtkTextIter.
*
* Move the completion window to a specific iter.
*
* Deprecated: 3.8: Use gtk_source_completion_provider_get_start_iter() instead.
*/
void
gtk_source_completion_move_window (GtkSourceCompletion *completion,
GtkTextIter *iter)
{
g_return_if_fail (GTK_SOURCE_IS_COMPLETION (completion));
g_return_if_fail (iter != NULL);
if (completion->priv->view == NULL)
{
return;
}
if (!gtk_widget_get_visible (GTK_WIDGET (completion->priv->main_window)))
{
return;
}
gtk_source_completion_info_move_to_iter (completion->priv->main_window,
GTK_TEXT_VIEW (completion->priv->view),
iter);
}
/**
* gtk_source_completion_block_interactive:
* @completion: a #GtkSourceCompletion.
*
* Block interactive completion. This can be used to disable interactive
* completion when inserting or deleting text from the buffer associated with
* the completion. Use gtk_source_completion_unblock_interactive() to enable
* interactive completion again.
*
* This function may be called multiple times. It will continue to block
* interactive completion until gtk_source_completion_unblock_interactive()
* has been called the same number of times.
*/
void
gtk_source_completion_block_interactive (GtkSourceCompletion *completion)
{
g_return_if_fail (GTK_SOURCE_IS_COMPLETION (completion));
if (completion->priv->view == NULL)
{
return;
}
if (completion->priv->block_interactive_num == 0)
{
block_interactive (completion);
}
completion->priv->block_interactive_num++;
}
/**
* gtk_source_completion_unblock_interactive:
* @completion: a #GtkSourceCompletion.
*
* Unblock interactive completion. This can be used after using
* gtk_source_completion_block_interactive() to enable interactive completion
* again.
*/
void
gtk_source_completion_unblock_interactive (GtkSourceCompletion *completion)
{
g_return_if_fail (GTK_SOURCE_IS_COMPLETION (completion));
if (completion->priv->view == NULL)
{
return;
}
if (completion->priv->block_interactive_num == 1)
{
g_signal_handlers_unblock_by_func (completion->priv->buffer,
buffer_insert_text_cb,
completion);
g_signal_handlers_unblock_by_func (completion->priv->buffer,
buffer_delete_range_cb,
completion);
}
if (completion->priv->block_interactive_num > 0)
{
completion->priv->block_interactive_num--;
}
}