2006-04-14 01:26:10 -07:00
|
|
|
/*
|
|
|
|
* mootextcompletion.c
|
|
|
|
*
|
|
|
|
* Copyright (C) 2004-2006 by Yevgen Muntyan <muntyan@math.tamu.edu>
|
|
|
|
*
|
|
|
|
* 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 <gdk/gdkkeysyms.h>
|
|
|
|
|
|
|
|
|
|
|
|
#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;
|
2006-04-14 02:52:01 -07:00
|
|
|
GtkScrolledWindow *scrolled_window;
|
2006-04-14 01:26:10 -07:00
|
|
|
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);
|
2006-04-14 02:52:01 -07:00
|
|
|
cmpl->priv->scrolled_window = GTK_SCROLLED_WINDOW (scrolled_window);
|
2006-04-14 01:26:10 -07:00
|
|
|
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;
|
2006-04-14 02:52:01 -07:00
|
|
|
int vert_separator, horiz_separator;
|
2006-04-14 01:26:10 -07:00
|
|
|
GtkTextIter iter;
|
2006-04-14 02:52:01 -07:00
|
|
|
int width;
|
2006-04-14 01:26:10 -07:00
|
|
|
|
|
|
|
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,
|
2006-04-14 02:52:01 -07:00
|
|
|
NULL, NULL, &width, &height);
|
2006-04-14 01:26:10 -07:00
|
|
|
|
|
|
|
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);
|
|
|
|
|
2006-04-14 02:52:01 -07:00
|
|
|
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);
|
|
|
|
|
2006-04-14 01:26:10 -07:00
|
|
|
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;
|
2006-04-14 02:52:01 -07:00
|
|
|
GtkTextIter start, end;
|
2006-04-14 01:26:10 -07:00
|
|
|
|
|
|
|
gtk_tree_model_get (model, iter, cmpl->priv->text_column, &text, -1);
|
|
|
|
g_return_val_if_fail (text != NULL, FALSE);
|
|
|
|
|
2006-04-14 02:52:01 -07:00
|
|
|
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);
|
2006-04-14 01:26:10 -07:00
|
|
|
|
2006-04-14 02:52:01 -07:00
|
|
|
g_free (text);
|
2006-04-14 01:26:10 -07:00
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MooTextCompletion *
|
|
|
|
moo_text_completion_new (void)
|
|
|
|
{
|
|
|
|
return g_object_new (MOO_TYPE_TEXT_COMPLETION, NULL);
|
|
|
|
}
|