/* * mootextcompletion.c * * Copyright (C) 2004-2006 by Yevgen Muntyan * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * See COPYING file that comes with this distribution. */ #include "mooedit/mootextcompletion.h" #include "mooutils/moomarshals.h" #include #define MAX_POPUP_LEN 10 struct _MooTextCompletionPrivate { GtkTreeView *treeview; GtkTreeSelection *selection; GtkTreeViewColumn *column; GtkCellRenderer *cell; GtkTreeModel *model; int text_column; GtkTextView *doc; GtkTextBuffer *buffer; GtkTextMark *start; GtkTextMark *end; GtkWidget *popup; GtkScrolledWindow *scrolled_window; guint working : 1; guint visible : 1; }; static void moo_text_completion_dispose (GObject *object); static gboolean moo_text_completion_complete_real (MooTextCompletion *cmpl, GtkTreeModel *model, GtkTreeIter *iter); static void moo_text_completion_ensure_popup (MooTextCompletion *cmpl); static gboolean moo_text_completion_empty (MooTextCompletion *cmpl); static void moo_text_completion_popup (MooTextCompletion *cmpl); static void moo_text_completion_popdown (MooTextCompletion *cmpl); static void moo_text_completion_resize_popup (MooTextCompletion *cmpl); static void moo_text_completion_connect_popup (MooTextCompletion *cmpl); static void moo_text_completion_disconnect_popup(MooTextCompletion *cmpl); G_DEFINE_TYPE (MooTextCompletion, moo_text_completion, G_TYPE_OBJECT) enum { UPDATE, COMPLETE, NUM_SIGNALS }; static guint signals[NUM_SIGNALS]; static void moo_text_completion_dispose (GObject *object) { MooTextCompletion *cmpl = MOO_TEXT_COMPLETION (object); if (cmpl->priv) { moo_text_completion_hide (cmpl); moo_text_completion_set_doc (cmpl, NULL); moo_text_completion_set_model (cmpl, NULL, 0); if (cmpl->priv->popup) gtk_widget_destroy (cmpl->priv->popup); g_free (cmpl->priv); cmpl->priv = NULL; } G_OBJECT_CLASS(moo_text_completion_parent_class)->dispose (object); } static void moo_text_completion_class_init (MooTextCompletionClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); gobject_class->dispose = moo_text_completion_dispose; klass->complete = moo_text_completion_complete_real; signals[UPDATE] = g_signal_new ("update", G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (MooTextCompletionClass, update), NULL, NULL, _moo_marshal_VOID__BOXED_BOXED, G_TYPE_NONE, 2, GTK_TYPE_TEXT_ITER, GTK_TYPE_TEXT_ITER); signals[COMPLETE] = g_signal_new ("complete", G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (MooTextCompletionClass, complete), g_signal_accumulator_true_handled, NULL, _moo_marshal_BOOLEAN__OBJECT_BOXED, G_TYPE_BOOLEAN, 2, GTK_TYPE_TREE_MODEL, GTK_TYPE_TREE_ITER); } static void moo_text_completion_init (MooTextCompletion *cmpl) { cmpl->priv = g_new0 (MooTextCompletionPrivate, 1); cmpl->priv->model = GTK_TREE_MODEL (gtk_list_store_new (1, G_TYPE_STRING)); } static void moo_text_completion_ensure_popup (MooTextCompletion *cmpl) { if (!cmpl->priv->popup) { GtkWidget *scrolled_window, *frame; cmpl->priv->popup = gtk_window_new (GTK_WINDOW_POPUP); gtk_widget_set_size_request (cmpl->priv->popup, -1, -1); gtk_window_set_default_size (GTK_WINDOW (cmpl->priv->popup), 1, 1); gtk_window_set_resizable (GTK_WINDOW (cmpl->priv->popup), FALSE); gtk_widget_add_events (cmpl->priv->popup, GDK_KEY_PRESS_MASK | GDK_BUTTON_PRESS_MASK); scrolled_window = gtk_scrolled_window_new (NULL, NULL); cmpl->priv->scrolled_window = GTK_SCROLLED_WINDOW (scrolled_window); gtk_widget_set_size_request (scrolled_window, -1, -1); gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_window), GTK_SHADOW_ETCHED_IN); /* a nasty hack to get the completions treeview to size nicely */ gtk_widget_set_size_request (GTK_SCROLLED_WINDOW (scrolled_window)->vscrollbar, -1, 0); gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_window), GTK_SHADOW_NONE); frame = gtk_frame_new (NULL); gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT); gtk_container_add (GTK_CONTAINER (frame), scrolled_window); gtk_container_add (GTK_CONTAINER (cmpl->priv->popup), frame); cmpl->priv->treeview = GTK_TREE_VIEW (gtk_tree_view_new ()); gtk_widget_set_size_request (GTK_WIDGET (cmpl->priv->treeview), -1, -1); gtk_container_add (GTK_CONTAINER (scrolled_window), GTK_WIDGET (cmpl->priv->treeview)); gtk_tree_view_set_headers_visible (cmpl->priv->treeview, FALSE); #if GTK_CHECK_VERSION(2,6,0) gtk_tree_view_set_hover_selection (cmpl->priv->treeview, TRUE); #endif gtk_widget_show_all (frame); cmpl->priv->selection = gtk_tree_view_get_selection (cmpl->priv->treeview); gtk_tree_selection_set_mode (cmpl->priv->selection, GTK_SELECTION_SINGLE); cmpl->priv->column = gtk_tree_view_column_new (); cmpl->priv->cell = gtk_cell_renderer_text_new (); gtk_tree_view_column_pack_start (cmpl->priv->column, cmpl->priv->cell, TRUE); gtk_tree_view_column_set_attributes (cmpl->priv->column, cmpl->priv->cell, "text", cmpl->priv->text_column, NULL); gtk_tree_view_append_column (cmpl->priv->treeview, cmpl->priv->column); if (cmpl->priv->model) gtk_tree_view_set_model (cmpl->priv->treeview, cmpl->priv->model); } } gboolean moo_text_completion_show (MooTextCompletion *cmpl, const GtkTextIter *start, const GtkTextIter *end) { g_return_val_if_fail (MOO_IS_TEXT_COMPLETION (cmpl), FALSE); if (cmpl->priv->working) { moo_text_completion_set_region (cmpl, start, end); return cmpl->priv->working; } g_return_val_if_fail (cmpl->priv->doc != NULL, FALSE); g_return_val_if_fail (cmpl->priv->model != NULL, FALSE); moo_text_completion_set_region (cmpl, start, end); if (!moo_text_completion_empty (cmpl)) { cmpl->priv->working = TRUE; moo_text_completion_popup (cmpl); return TRUE; } return FALSE; } void moo_text_completion_update (MooTextCompletion *cmpl) { GtkTextIter start, end; g_return_if_fail (MOO_IS_TEXT_COMPLETION (cmpl)); g_return_if_fail (cmpl->priv->working); gtk_text_buffer_get_iter_at_mark (cmpl->priv->buffer, &start, cmpl->priv->start); gtk_text_buffer_get_iter_at_mark (cmpl->priv->buffer, &end, cmpl->priv->end); g_signal_emit (cmpl, signals[UPDATE], 0, cmpl->priv->model); if (moo_text_completion_empty (cmpl)) moo_text_completion_hide (cmpl); else if (cmpl->priv->visible) moo_text_completion_resize_popup (cmpl); } void moo_text_completion_hide (MooTextCompletion *cmpl) { g_return_if_fail (MOO_IS_TEXT_COMPLETION (cmpl)); if (cmpl->priv->working) { moo_text_completion_popdown (cmpl); cmpl->priv->working = FALSE; } } void moo_text_completion_complete (MooTextCompletion *cmpl) { GtkTreeIter iter; gboolean got_selected, retval; g_return_if_fail (MOO_IS_TEXT_COMPLETION (cmpl)); g_return_if_fail (cmpl->priv->visible); got_selected = gtk_tree_selection_get_selected (cmpl->priv->selection, NULL, &iter); moo_text_completion_popdown (cmpl); if (got_selected) g_signal_emit (cmpl, signals[COMPLETE], 0, cmpl->priv->model, &iter, &retval); moo_text_completion_hide (cmpl); } static gboolean moo_text_completion_empty (MooTextCompletion *cmpl) { return !cmpl->priv->model || gtk_tree_model_iter_n_children (cmpl->priv->model, NULL) == 0; } static void moo_text_completion_popup (MooTextCompletion *cmpl) { GtkWidget *toplevel; if (cmpl->priv->visible) return; g_return_if_fail (cmpl->priv->doc != NULL); moo_text_completion_ensure_popup (cmpl); toplevel = gtk_widget_get_toplevel (GTK_WIDGET (cmpl->priv->doc)); g_return_if_fail (GTK_IS_WINDOW (toplevel)); gtk_widget_realize (cmpl->priv->popup); if (GTK_WINDOW (toplevel)->group) gtk_window_group_add_window (GTK_WINDOW (toplevel)->group, GTK_WINDOW (cmpl->priv->popup)); gtk_window_set_modal (GTK_WINDOW (cmpl->priv->popup), TRUE); moo_text_completion_resize_popup (cmpl); gtk_widget_show (cmpl->priv->popup); cmpl->priv->visible = TRUE; gtk_widget_ensure_style (GTK_WIDGET (cmpl->priv->treeview)); gtk_widget_modify_bg (GTK_WIDGET (cmpl->priv->treeview), GTK_STATE_ACTIVE, >K_WIDGET(cmpl->priv->treeview)->style->base[GTK_STATE_SELECTED]); gtk_widget_modify_base (GTK_WIDGET (cmpl->priv->treeview), GTK_STATE_ACTIVE, >K_WIDGET(cmpl->priv->treeview)->style->base[GTK_STATE_SELECTED]); gtk_grab_add (cmpl->priv->popup); gdk_pointer_grab (cmpl->priv->popup->window, TRUE, GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK, NULL, NULL, GDK_CURRENT_TIME); moo_text_completion_connect_popup (cmpl); } static void moo_text_completion_popdown (MooTextCompletion *cmpl) { if (cmpl->priv->visible) { cmpl->priv->visible = FALSE; moo_text_completion_disconnect_popup (cmpl); gdk_pointer_ungrab (GDK_CURRENT_TIME); gtk_grab_remove (cmpl->priv->popup); gtk_widget_hide (cmpl->priv->popup); } } static void moo_text_completion_resize_popup (MooTextCompletion *cmpl) { GtkWidget *widget = GTK_WIDGET (cmpl->priv->doc); int x, y; int total_items, items, height; GdkScreen *screen; int monitor_num; GdkRectangle monitor, iter_rect; GtkRequisition popup_req; int vert_separator, horiz_separator; GtkTextIter iter; int width; g_return_if_fail (GTK_WIDGET_REALIZED (widget)); g_return_if_fail (cmpl->priv->start && cmpl->priv->end); moo_text_completion_get_region (cmpl, &iter, NULL); gtk_text_view_get_iter_location (cmpl->priv->doc, &iter, &iter_rect); gtk_text_view_buffer_to_window_coords (cmpl->priv->doc, GTK_TEXT_WINDOW_TEXT, iter_rect.x, iter_rect.y, &iter_rect.x, &iter_rect.y); gdk_window_get_origin (widget->window, &x, &y); x += iter_rect.x; y += iter_rect.y; total_items = gtk_tree_model_iter_n_children (cmpl->priv->model, NULL); items = MIN (total_items, MAX_POPUP_LEN); gtk_tree_view_column_cell_get_size (cmpl->priv->column, NULL, NULL, NULL, &width, &height); screen = gtk_widget_get_screen (widget); monitor_num = gdk_screen_get_monitor_at_window (screen, widget->window); gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor); gtk_widget_style_get (GTK_WIDGET (cmpl->priv->treeview), "vertical-separator", &vert_separator, "horizontal-separator", &horiz_separator, NULL); width += 4 * horiz_separator; if (total_items > items) { GtkRequisition scrollbar_req; gtk_widget_size_request (cmpl->priv->scrolled_window->vscrollbar, &scrollbar_req); width += scrollbar_req.width; } width = MAX (width, 100); width = MIN (monitor.width, width); gtk_widget_set_size_request (GTK_WIDGET (cmpl->priv->treeview), width, items * (height + vert_separator)); gtk_widget_set_size_request (cmpl->priv->popup, -1, -1); gtk_widget_size_request (cmpl->priv->popup, &popup_req); if (x < monitor.x) x = monitor.x; else if (x + popup_req.width > monitor.x + monitor.width) x = monitor.x + monitor.width - popup_req.width; if (y + iter_rect.height + popup_req.height <= monitor.y + monitor.height) { y += iter_rect.height; } else { y -= popup_req.height; } gtk_window_move (GTK_WINDOW (cmpl->priv->popup), x, y); } void moo_text_completion_set_doc (MooTextCompletion *cmpl, GtkTextView *doc) { g_return_if_fail (MOO_IS_TEXT_COMPLETION (cmpl)); g_return_if_fail (!doc || GTK_IS_TEXT_VIEW (doc)); if (cmpl->priv->doc == doc) return; moo_text_completion_hide (cmpl); if (cmpl->priv->doc) { if (cmpl->priv->start) { gtk_text_buffer_delete_mark (cmpl->priv->buffer, cmpl->priv->start); gtk_text_buffer_delete_mark (cmpl->priv->buffer, cmpl->priv->end); cmpl->priv->start = NULL; cmpl->priv->end = NULL; } g_object_unref (cmpl->priv->doc); cmpl->priv->doc = NULL; cmpl->priv->buffer = NULL; } if (doc) { cmpl->priv->doc = g_object_ref (doc); cmpl->priv->buffer = gtk_text_view_get_buffer (doc); } } GtkTextView * moo_text_completion_get_doc (MooTextCompletion *cmpl) { g_return_val_if_fail (MOO_IS_TEXT_COMPLETION (cmpl), NULL); return cmpl->priv->doc; } void moo_text_completion_set_model (MooTextCompletion *cmpl, GtkTreeModel *model, int text_column) { g_return_if_fail (MOO_IS_TEXT_COMPLETION (cmpl)); g_return_if_fail (!model || GTK_IS_TREE_MODEL (model)); if (model && gtk_tree_model_get_column_type (model, text_column) != G_TYPE_STRING) { g_critical ("%s: invalid text column", G_STRLOC); return; } moo_text_completion_hide (cmpl); cmpl->priv->text_column = text_column; if (model != cmpl->priv->model) { if (cmpl->priv->model) g_object_unref (cmpl->priv->model); cmpl->priv->model = model; if (cmpl->priv->model) g_object_ref (cmpl->priv->model); } if (cmpl->priv->treeview) { gtk_tree_view_set_model (cmpl->priv->treeview, model); if (model) gtk_tree_view_column_set_attributes (cmpl->priv->column, cmpl->priv->cell, "text", cmpl->priv->text_column, NULL); } } GtkTreeModel * moo_text_completion_get_model (MooTextCompletion *cmpl, int *text_column) { g_return_val_if_fail (MOO_IS_TEXT_COMPLETION (cmpl), NULL); if (text_column && cmpl->priv->model) *text_column = cmpl->priv->text_column; return cmpl->priv->model; } gboolean moo_text_completion_get_region (MooTextCompletion *cmpl, GtkTextIter *start, GtkTextIter *end) { g_return_val_if_fail (MOO_IS_TEXT_COMPLETION (cmpl), FALSE); if (cmpl->priv->start) { if (start) gtk_text_buffer_get_iter_at_mark (cmpl->priv->buffer, start, cmpl->priv->start); if (end) gtk_text_buffer_get_iter_at_mark (cmpl->priv->buffer, end, cmpl->priv->end); return TRUE; } return FALSE; } void moo_text_completion_set_region (MooTextCompletion *cmpl, const GtkTextIter *start, const GtkTextIter *end) { g_return_if_fail (MOO_IS_TEXT_COMPLETION (cmpl)); g_return_if_fail (start && end); g_return_if_fail (cmpl->priv->buffer != NULL); if (gtk_text_iter_compare (start, end) > 0) { const GtkTextIter *tmp = start; start = end; end = tmp; } if (!cmpl->priv->start) { cmpl->priv->start = gtk_text_buffer_create_mark (cmpl->priv->buffer, NULL, start, TRUE); cmpl->priv->end = gtk_text_buffer_create_mark (cmpl->priv->buffer, NULL, end, FALSE); } else { gtk_text_buffer_move_mark (cmpl->priv->buffer, cmpl->priv->start, start); gtk_text_buffer_move_mark (cmpl->priv->buffer, cmpl->priv->end, end); } if (cmpl->priv->working) moo_text_completion_update (cmpl); } /***************************************************************************/ /* Popup events */ static gboolean doc_focus_out (MooTextCompletion *cmpl) { moo_text_completion_hide (cmpl); return FALSE; } static gboolean popup_button_press (MooTextCompletion *cmpl, GdkEventButton *event) { if (event->window == cmpl->priv->popup->window) { int width, height; gdk_drawable_get_size (GDK_DRAWABLE (event->window), &width, &height); if (event->x < 0 || event->x >= width || event->y < 0 || event->y >= height) { moo_text_completion_hide (cmpl); } return TRUE; } else { moo_text_completion_hide (cmpl); return FALSE; } } static void popup_move_selection (MooTextCompletion *cmpl, GdkEventKey *event) { int n_items, current_item, new_item = -1; GtkTreeIter iter; GtkTreeModel *model; n_items = gtk_tree_model_iter_n_children (cmpl->priv->model, NULL); g_return_if_fail (n_items != 0); if (gtk_tree_selection_get_selected (cmpl->priv->selection, &model, &iter)) { GtkTreePath *path = gtk_tree_model_get_path (model, &iter); current_item = gtk_tree_path_get_indices (path) [0]; gtk_tree_path_free (path); } else { current_item = -1; } switch (event->keyval) { case GDK_Down: case GDK_KP_Down: if (current_item < n_items - 1) new_item = current_item + 1; else new_item = -1; break; case GDK_Up: case GDK_KP_Up: if (current_item < 0) new_item = n_items - 1; else new_item = current_item - 1; break; case GDK_Page_Down: case GDK_KP_Page_Down: new_item = current_item + MAX_POPUP_LEN - 1; if (new_item >= n_items) new_item = n_items - 1; break; case GDK_Page_Up: case GDK_KP_Page_Up: new_item = current_item - MAX_POPUP_LEN + 1; if (new_item < 0) new_item = 0; break; case GDK_Tab: case GDK_KP_Tab: if (current_item < n_items - 1) new_item = current_item + 1; else new_item = 0; break; case GDK_ISO_Left_Tab: if (current_item <= 0) new_item = n_items - 1; else new_item = current_item - 1; break; default: g_return_if_reached (); } if (new_item >= 0 && new_item < n_items) { GtkTreePath *path = gtk_tree_path_new_from_indices (new_item, -1); gtk_tree_view_set_cursor (cmpl->priv->treeview, path, NULL, FALSE); gtk_tree_view_scroll_to_cell (cmpl->priv->treeview, path, NULL, FALSE, 0, 0); gtk_tree_path_free (path); } else { gtk_tree_selection_unselect_all (cmpl->priv->selection); } } static gboolean popup_key_press (MooTextCompletion *cmpl, GdkEventKey *event) { switch (event->keyval) { case GDK_Down: case GDK_Up: case GDK_KP_Down: case GDK_KP_Up: case GDK_Page_Down: case GDK_Page_Up: case GDK_Tab: case GDK_KP_Tab: case GDK_ISO_Left_Tab: popup_move_selection (cmpl, event); return TRUE; case GDK_Escape: moo_text_completion_hide (cmpl); return TRUE; case GDK_Return: case GDK_ISO_Enter: case GDK_KP_Enter: moo_text_completion_complete (cmpl); return TRUE; default: return gtk_widget_event (GTK_WIDGET (cmpl->priv->doc), (GdkEvent*) event); } } static gboolean list_button_press (MooTextCompletion *cmpl, GdkEventButton *event) { GtkTreePath *path; GtkTreeIter iter; if (gtk_tree_view_get_path_at_pos (cmpl->priv->treeview, event->x, event->y, &path, NULL, NULL, NULL)) { gtk_tree_model_get_iter (cmpl->priv->model, &iter, path); gtk_tree_selection_select_iter (cmpl->priv->selection, &iter); moo_text_completion_complete (cmpl); gtk_tree_path_free (path); return TRUE; } return FALSE; } static void buffer_delete_range (MooTextCompletion *cmpl, GtkTextIter *start, GtkTextIter *end) { GtkTextIter region_end; int offset; if (gtk_text_iter_compare (start, end) > 0) { GtkTextIter *tmp = start; start = end; end = tmp; } moo_text_completion_get_region (cmpl, NULL, ®ion_end); if (gtk_text_iter_compare (®ion_end, end)) goto hide; offset = gtk_text_iter_get_offset (end) - gtk_text_iter_get_offset (®ion_end); if (ABS (offset) > 1) goto hide; return; hide: moo_text_completion_hide (cmpl); } static void buffer_insert_text (MooTextCompletion *cmpl, GtkTextIter *iter, const char *text, int len) { GtkTextIter region_end; gunichar ch; moo_text_completion_get_region (cmpl, NULL, ®ion_end); if (gtk_text_iter_compare (®ion_end, iter)) goto hide; if (len < 0) len = g_utf8_strlen (text, -1); if (len > 1) goto hide; ch = g_utf8_get_char (text); if (g_unichar_isspace (ch)) { switch (ch) { case ' ': case '\t': break; default: goto hide; } } return; hide: moo_text_completion_hide (cmpl); } static void moo_text_completion_connect_popup (MooTextCompletion *cmpl) { g_return_if_fail (cmpl->priv->doc && cmpl->priv->treeview && cmpl->priv->popup); g_signal_connect_swapped (cmpl->priv->doc, "focus-out-event", G_CALLBACK (doc_focus_out), cmpl); g_signal_connect_swapped (cmpl->priv->popup, "button-press-event", G_CALLBACK (popup_button_press), cmpl); g_signal_connect_swapped (cmpl->priv->popup, "key-press-event", G_CALLBACK (popup_key_press), cmpl); g_signal_connect_swapped (cmpl->priv->treeview, "button-press-event", G_CALLBACK (list_button_press), cmpl); g_signal_connect_swapped (cmpl->priv->buffer, "delete-range", G_CALLBACK (buffer_delete_range), cmpl); g_signal_connect_swapped (cmpl->priv->buffer, "insert-text", G_CALLBACK (buffer_insert_text), cmpl); g_signal_connect_data (cmpl->priv->buffer, "delete-range", G_CALLBACK (moo_text_completion_update), cmpl, NULL, G_CONNECT_AFTER | G_CONNECT_SWAPPED); g_signal_connect_data (cmpl->priv->buffer, "insert-text", G_CALLBACK (moo_text_completion_update), cmpl, NULL, G_CONNECT_AFTER | G_CONNECT_SWAPPED); } static void moo_text_completion_disconnect_popup (MooTextCompletion *cmpl) { g_signal_handlers_disconnect_by_func (cmpl->priv->doc, (gpointer) doc_focus_out, cmpl); g_signal_handlers_disconnect_by_func (cmpl->priv->popup, (gpointer) popup_button_press, cmpl); g_signal_handlers_disconnect_by_func (cmpl->priv->popup, (gpointer) popup_key_press, cmpl); g_signal_handlers_disconnect_by_func (cmpl->priv->treeview, (gpointer) list_button_press, cmpl); g_signal_handlers_disconnect_by_func (cmpl->priv->buffer, (gpointer) buffer_delete_range, cmpl); g_signal_handlers_disconnect_by_func (cmpl->priv->buffer, (gpointer) buffer_insert_text, cmpl); g_signal_handlers_disconnect_by_func (cmpl->priv->buffer, (gpointer) moo_text_completion_update, cmpl); } static gboolean moo_text_completion_complete_real (MooTextCompletion *cmpl, GtkTreeModel *model, GtkTreeIter *iter) { char *text = NULL; GtkTextIter start, end; gtk_tree_model_get (model, iter, cmpl->priv->text_column, &text, -1); g_return_val_if_fail (text != NULL, FALSE); moo_text_completion_get_region (cmpl, &start, &end); gtk_text_buffer_delete (cmpl->priv->buffer, &start, &end); gtk_text_buffer_insert (cmpl->priv->buffer, &start, text, -1); g_free (text); return TRUE; } MooTextCompletion * moo_text_completion_new (void) { return g_object_new (MOO_TYPE_TEXT_COMPLETION, NULL); }