From 54105acd4e5283899c1659e64e41341eec8d667c Mon Sep 17 00:00:00 2001 From: Jon Trowbridge Date: Mon, 19 Feb 2001 22:48:03 +0000 Subject: Forgot to add. Doh! svn path=/trunk/; revision=8281 --- widgets/text/e-completion-view.c | 687 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 687 insertions(+) create mode 100644 widgets/text/e-completion-view.c (limited to 'widgets/text/e-completion-view.c') diff --git a/widgets/text/e-completion-view.c b/widgets/text/e-completion-view.c new file mode 100644 index 0000000000..026cb151dd --- /dev/null +++ b/widgets/text/e-completion-view.c @@ -0,0 +1,687 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ + +/* + * ECompletionView - A text completion selection widget + * Copyright (C) 2000, 2001 Ximian, Inc. + * + * Author: Jon Trowbridge + * Adapted from code by Miguel de Icaza + */ + +/* + * 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. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + + +#include +#include +#include +#include +#include +#include "e-completion-view.h" + +enum { + E_COMPLETION_VIEW_NONEMPTY, + E_COMPLETION_VIEW_ADDED, + E_COMPLETION_VIEW_FULL, + E_COMPLETION_VIEW_BROWSE, + E_COMPLETION_VIEW_UNBROWSE, + E_COMPLETION_VIEW_ACTIVATE, + E_COMPLETION_VIEW_LAST_SIGNAL +}; + +static guint e_completion_view_signals[E_COMPLETION_VIEW_LAST_SIGNAL] = { 0 }; + +static void e_completion_view_disconnect (ECompletionView *cv); +static ETable *e_completion_view_table (ECompletionView *cv); +static void e_completion_view_clear_choices (ECompletionView *cv); +static void e_completion_view_set_cursor_row (ECompletionView *cv, gint r); +static void e_completion_view_select (ECompletionView *cv, gint r); + +static gint e_completion_view_key_press_handler (GtkWidget *w, GdkEventKey *key_event, gpointer user_data); + +static void e_completion_view_class_init (ECompletionViewClass *klass); +static void e_completion_view_init (ECompletionView *completion); +static void e_completion_view_destroy (GtkObject *object); + +static GtkObjectClass *parent_class; + + + +static gint +e_completion_view_local_key_press_handler (GtkWidget *w, GdkEventKey *ev) +{ + return e_completion_view_key_press_handler (w, ev, w); +} + +GtkType +e_completion_view_get_type (void) +{ + static GtkType completion_view_type = 0; + + if (!completion_view_type) { + GtkTypeInfo completion_view_info = { + "ECompletionView", + sizeof (ECompletionView), + sizeof (ECompletionViewClass), + (GtkClassInitFunc) e_completion_view_class_init, + (GtkObjectInitFunc) e_completion_view_init, + NULL, NULL, /* reserved */ + (GtkClassInitFunc) NULL + }; + + completion_view_type = gtk_type_unique (gtk_vbox_get_type (), &completion_view_info); + } + + return completion_view_type; +} + +static void +e_completion_view_class_init (ECompletionViewClass *klass) +{ + GtkObjectClass *object_class = (GtkObjectClass *) klass; + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + parent_class = GTK_OBJECT_CLASS (gtk_type_class (gtk_vbox_get_type ())); + + e_completion_view_signals[E_COMPLETION_VIEW_NONEMPTY] = + gtk_signal_new ("nonempty", + GTK_RUN_LAST, + object_class->type, + GTK_SIGNAL_OFFSET (ECompletionViewClass, nonempty), + gtk_marshal_NONE__NONE, + GTK_TYPE_NONE, 0); + + e_completion_view_signals[E_COMPLETION_VIEW_ADDED] = + gtk_signal_new ("added", + GTK_RUN_LAST, + object_class->type, + GTK_SIGNAL_OFFSET (ECompletionViewClass, added), + gtk_marshal_NONE__NONE, + GTK_TYPE_NONE, 0); + + e_completion_view_signals[E_COMPLETION_VIEW_FULL] = + gtk_signal_new ("full", + GTK_RUN_LAST, + object_class->type, + GTK_SIGNAL_OFFSET (ECompletionViewClass, full), + gtk_marshal_NONE__NONE, + GTK_TYPE_NONE, 0); + + e_completion_view_signals[E_COMPLETION_VIEW_BROWSE] = + gtk_signal_new ("browse", + GTK_RUN_LAST, + object_class->type, + GTK_SIGNAL_OFFSET (ECompletionViewClass, browse), + gtk_marshal_NONE__POINTER, + GTK_TYPE_NONE, 1, + GTK_TYPE_POINTER); + + e_completion_view_signals[E_COMPLETION_VIEW_UNBROWSE] = + gtk_signal_new ("unbrowse", + GTK_RUN_LAST, + object_class->type, + GTK_SIGNAL_OFFSET (ECompletionViewClass, unbrowse), + gtk_marshal_NONE__NONE, + GTK_TYPE_NONE, 0); + + e_completion_view_signals[E_COMPLETION_VIEW_ACTIVATE] = + gtk_signal_new ("activate", + GTK_RUN_LAST, + object_class->type, + GTK_SIGNAL_OFFSET (ECompletionViewClass, activate), + gtk_marshal_NONE__POINTER_POINTER, + GTK_TYPE_NONE, 2, + GTK_TYPE_POINTER, GTK_TYPE_POINTER); + + gtk_object_class_add_signals (object_class, e_completion_view_signals, E_COMPLETION_VIEW_LAST_SIGNAL); + + object_class->destroy = e_completion_view_destroy; + widget_class->key_press_event = e_completion_view_local_key_press_handler; +} + +static void +e_completion_view_init (ECompletionView *completion) +{ +} + +static void +e_completion_view_destroy (GtkObject *object) +{ + ECompletionView *cv = E_COMPLETION_VIEW (object); + + e_completion_view_disconnect (cv); + e_completion_view_clear_choices (cv); + + if (cv->key_widget) { + gtk_signal_disconnect (GTK_OBJECT (cv->key_widget), cv->key_signal_id); + gtk_object_unref (GTK_OBJECT (cv->key_widget)); + } + + if (cv->completion) + gtk_object_unref (GTK_OBJECT (cv->completion)); + + + if (parent_class->destroy) + (parent_class->destroy) (object); +} + +static void +e_completion_view_disconnect (ECompletionView *cv) +{ + g_return_if_fail (cv != NULL); + g_return_if_fail (E_IS_COMPLETION_VIEW (cv)); + + if (cv->begin_signal_id) + gtk_signal_disconnect (GTK_OBJECT (cv->completion), cv->begin_signal_id); + if (cv->comp_signal_id) + gtk_signal_disconnect (GTK_OBJECT (cv->completion), cv->comp_signal_id); + if (cv->restart_signal_id) + gtk_signal_disconnect (GTK_OBJECT (cv->completion), cv->restart_signal_id); + if (cv->cancel_signal_id) + gtk_signal_disconnect (GTK_OBJECT (cv->completion), cv->cancel_signal_id); + if (cv->end_signal_id) + gtk_signal_disconnect (GTK_OBJECT (cv->completion), cv->end_signal_id); + + cv->begin_signal_id = 0; + cv->comp_signal_id = 0; + cv->restart_signal_id = 0; + cv->end_signal_id = 0; +} + +static ETable * +e_completion_view_table (ECompletionView *cv) +{ + return e_table_scrolled_get_table (E_TABLE_SCROLLED (cv->table)); +} + +static void +e_completion_view_clear_choices (ECompletionView *cv) +{ + GList *i; + + g_return_if_fail (cv != NULL); + g_return_if_fail (E_IS_COMPLETION_VIEW (cv)); + + for (i = cv->choices; i != NULL; i = g_list_next (i)) + g_free (i->data); + + g_list_free (cv->choices); + cv->choices = NULL; + + cv->choice_count = 0; +} + +static void +e_completion_view_set_cursor_row (ECompletionView *cv, gint r) +{ + ETable *table; + GtkAdjustment *adj; + gint x, y1, y2, r1, r2, c; + double fracline; + gint iteration_count=0; + + g_return_if_fail (cv != NULL); + g_return_if_fail (E_IS_COMPLETION_VIEW (cv)); + g_return_if_fail (r < cv->choice_count); + + adj = e_scroll_frame_get_vadjustment (E_SCROLL_FRAME (cv->table)); + + table = e_completion_view_table (cv); + + if (r < 0) { + e_table_selection_model_clear (table->selection); + + /* Move back to the top when we clear the selection */ + gtk_adjustment_set_value (adj, adj->lower); + return; + } + + e_table_set_cursor_row (table, r); + + /* OK, now the tricky bit. We try to insure that this row is + visible. */ + + /* If we are selecting the first or last row, then it is easy. We just + cram the vadjustment all the way up/down. */ + if (r == 0) { + gtk_adjustment_set_value (adj, adj->lower); + return; + } else if (r == cv->choice_count - 1) { + gtk_adjustment_set_value (adj, adj->upper - adj->page_size); + return; + } + + fracline = ((adj->upper - adj->lower - adj->page_size) / cv->choice_count) / 4; + + while (iteration_count < 100) { + x = GTK_LAYOUT(table->table_canvas)->hadjustment->value; + y1 = GTK_LAYOUT(table->table_canvas)->vadjustment->value; + + y2 = y1 + cv->table->allocation.height; + + e_table_group_compute_location (e_completion_view_table (cv)->group, &x, &y1, &r1, &c); + e_table_group_compute_location (e_completion_view_table (cv)->group, &x, &y2, &r2, &c); + + if (r <= r1) { + gtk_adjustment_set_value (adj, adj->value - fracline); + } else if (r >= r2) { + gtk_adjustment_set_value (adj, adj->value + fracline); + } else + return; + + ++iteration_count; + } + + g_assert_not_reached (); +} + +static void +e_completion_view_select (ECompletionView *cv, gint r) +{ + const gchar *sel = (const gchar *) g_list_nth_data (cv->choices, r); + gpointer extra = e_completion_find_extra_data (cv->completion, sel); + + cv->selection = r; + e_completion_view_set_cursor_row (cv, r); + gtk_signal_emit (GTK_OBJECT (cv), e_completion_view_signals[E_COMPLETION_VIEW_ACTIVATE], sel, extra); +} + +static gint +e_completion_view_key_press_handler (GtkWidget *w, GdkEventKey *key_event, gpointer user_data) +{ + ECompletionView *cv = E_COMPLETION_VIEW (user_data); + gint dir = 0; + + /* Start up a completion.*/ + if (cv->complete_key && key_event->keyval == cv->complete_key && !cv->editable) { + gtk_signal_emit (GTK_OBJECT (cv), e_completion_view_signals[E_COMPLETION_VIEW_BROWSE], NULL); + goto stop_emission; + } + + /* Stop our completion. */ + if (cv->uncomplete_key && key_event->keyval == cv->uncomplete_key && cv->editable && cv->selection < 0) { + e_completion_view_set_cursor_row (cv, -1); + gtk_signal_emit (GTK_OBJECT (cv), e_completion_view_signals[E_COMPLETION_VIEW_UNBROWSE]); + goto stop_emission; + } + + if (!cv->editable) + return FALSE; + + switch (key_event->keyval) { + case GDK_Down: + case GDK_KP_Down: + dir = 1; + break; + + case GDK_Up: + case GDK_KP_Up: + dir = -1; + break; + + case GDK_Return: + case GDK_KP_Enter: + case GDK_space: + case GDK_KP_Space: + case GDK_Right: /* Lynx-style "forward" */ + case GDK_KP_Right: + /* Only handle these key presses if we have an active selection; + otherwise, pass them on. */ + if (cv->selection >= 0) { + e_completion_view_select (cv, cv->selection); + goto stop_emission; + } + return FALSE; + + case GDK_Escape: + case GDK_Left: /* Lynx-style "back" */ + case GDK_KP_Left: + if (cv->selection >= 0) { + /* A hack to "unbrowse" us on these keys if we are browsing. */ + cv->selection = -1; + dir = 0; + break; + } + + return FALSE; + + default: + return FALSE; + } + + cv->selection += dir; + + if (cv->selection >= cv->choice_count) { + cv->selection = cv->choice_count - 1; + /* Don't re-emit the browse signal */ + goto stop_emission; + } + + e_completion_view_set_cursor_row (cv, cv->selection); + + if (cv->selection >= 0) + gtk_signal_emit (GTK_OBJECT (cv), e_completion_view_signals[E_COMPLETION_VIEW_BROWSE], + g_list_nth_data (cv->choices, cv->selection)); + else + gtk_signal_emit (GTK_OBJECT (cv), e_completion_view_signals[E_COMPLETION_VIEW_UNBROWSE]); + + stop_emission: + gtk_signal_emit_stop_by_name (GTK_OBJECT (w), "key_press_event"); + + return TRUE; +} + +static void +begin_completion_cb (ECompletion *completion, gpointer user_data) +{ + ECompletionView *cv = E_COMPLETION_VIEW (user_data); + + e_completion_view_clear_choices (cv); + cv->have_all_choices = FALSE; + + e_table_model_changed (cv->model); +} + +static void +restart_completion_cb (ECompletion *completion, gpointer user_data) +{ + /* For now, handle restarts like the beginning of a new completion. */ + begin_completion_cb (completion, user_data); +} + +static void +cancel_completion_cb (ECompletion *completion, gpointer user_data) +{ + ECompletionView *cv = E_COMPLETION_VIEW (user_data); + + /* On a cancel, clear our choices and issue an "unbrowse" signal. */ + e_completion_view_clear_choices (cv); + cv->have_all_choices = TRUE; + e_completion_view_set_cursor_row (cv, -1); + e_table_model_changed (cv->model); + + gtk_signal_emit (GTK_OBJECT (cv), e_completion_view_signals[E_COMPLETION_VIEW_UNBROWSE]); +} + +static void +completion_cb (ECompletion *completion, const gchar *text, gpointer extra_data, gpointer user_data) +{ + ECompletionView *cv = E_COMPLETION_VIEW (user_data); + gint r = cv->choice_count; + gboolean first = (cv->choices == NULL); + + cv->choices = g_list_append (cv->choices, g_strdup (text)); + ++cv->choice_count; + + e_table_model_row_inserted (cv->model, r); + + if (first) + gtk_signal_emit (GTK_OBJECT (cv), e_completion_view_signals[E_COMPLETION_VIEW_NONEMPTY]); + + gtk_signal_emit (GTK_OBJECT (cv), e_completion_view_signals[E_COMPLETION_VIEW_ADDED]); +} + +static void +end_completion_cb (ECompletion *completion, gpointer user_data) +{ + ECompletionView *cv = E_COMPLETION_VIEW (user_data); + + /* Do a final refresh of the table. */ + e_table_model_changed (cv->model); + + cv->have_all_choices = TRUE; + gtk_signal_emit (GTK_OBJECT (cv), e_completion_view_signals[E_COMPLETION_VIEW_FULL]); +} + +/*** Table Callbacks ***/ + +static char *simple_spec = +" " +" " +" " +" " +" " +" " +""; + +static gint +table_col_count (ETableModel *etm, gpointer data) +{ + return 1; +} + +static gint +table_row_count (ETableModel *etm, gpointer data) +{ + ECompletionView *cv = E_COMPLETION_VIEW (data); + return cv->choice_count; +} + +static gboolean +table_is_cell_editable (ETableModel *etm, gint c, gint r, gpointer data) +{ + return FALSE; +} + +static gpointer +table_value_at (ETableModel *etm, gint c, gint r, gpointer data) +{ + ECompletionView *cv = E_COMPLETION_VIEW (data); + gpointer p; + + p = g_list_nth_data (cv->choices, r); + + return p; +} + +static gchar * +table_value_to_string (ETableModel *em, gint col, gconstpointer val, gpointer data) +{ + return (gchar *) val; +} + +static void +table_click_cb (ETable *et, gint r, gint c, GdkEvent *ev, gpointer data) +{ + ECompletionView *cv = E_COMPLETION_VIEW (data); + + e_completion_view_select (cv, r); +} + +void +e_completion_view_construct (ECompletionView *cv, ECompletion *completion) +{ + GtkWidget *frame; + + g_return_if_fail (cv != NULL); + g_return_if_fail (E_IS_COMPLETION_VIEW (cv)); + g_return_if_fail (completion != NULL); + g_return_if_fail (E_IS_COMPLETION (completion)); + + /* Make sure we don't call construct twice. */ + g_return_if_fail (cv->completion == NULL); + + cv->completion = completion; + gtk_object_ref (GTK_OBJECT (completion)); + + cv->begin_signal_id = gtk_signal_connect (GTK_OBJECT (completion), + "begin_completion", + GTK_SIGNAL_FUNC (begin_completion_cb), + cv); + cv->comp_signal_id = gtk_signal_connect (GTK_OBJECT (completion), + "completion", + GTK_SIGNAL_FUNC (completion_cb), + cv); + cv->restart_signal_id = gtk_signal_connect (GTK_OBJECT (completion), + "restart_completion", + GTK_SIGNAL_FUNC (restart_completion_cb), + cv); + cv->cancel_signal_id = gtk_signal_connect (GTK_OBJECT (completion), + "cancel_completion", + GTK_SIGNAL_FUNC (cancel_completion_cb), + cv); + cv->end_signal_id = gtk_signal_connect (GTK_OBJECT (completion), + "end_completion", + GTK_SIGNAL_FUNC (end_completion_cb), + cv); + + cv->model = e_table_simple_new (table_col_count, + table_row_count, + table_value_at, + NULL, + table_is_cell_editable, + NULL, NULL, NULL, NULL, + table_value_to_string, + cv); + + cv->table = e_table_scrolled_new (cv->model, NULL, simple_spec, NULL); + + frame = gtk_frame_new (NULL); + + gtk_container_add (GTK_CONTAINER (cv), frame); + gtk_container_add (GTK_CONTAINER (frame), cv->table); + gtk_widget_show_all (frame); + + gtk_signal_connect (GTK_OBJECT (e_completion_view_table (cv)), + "click", + GTK_SIGNAL_FUNC (table_click_cb), + cv); + + cv->selection = -1; +} + +GtkWidget * +e_completion_view_new (ECompletion *completion) +{ + gpointer p; + + g_return_val_if_fail (completion != NULL, NULL); + g_return_val_if_fail (E_IS_COMPLETION (completion), NULL); + + p = gtk_type_new (e_completion_view_get_type ()); + + e_completion_view_construct (E_COMPLETION_VIEW (p), completion); + + return GTK_WIDGET (p); +} + +void +e_completion_view_connect_keys (ECompletionView *cv, GtkWidget *w) +{ + g_return_if_fail (cv != NULL); + g_return_if_fail (E_IS_COMPLETION_VIEW (cv)); + g_return_if_fail (w == NULL || GTK_IS_WIDGET (w)); + + if (cv->key_widget) { + gtk_signal_disconnect (GTK_OBJECT (cv->key_widget), cv->key_signal_id); + gtk_object_unref (GTK_OBJECT (cv->key_widget)); + } + + if (w) { + cv->key_widget = w; + gtk_object_ref (GTK_OBJECT (w)); + + cv->key_signal_id = gtk_signal_connect (GTK_OBJECT (w), + "key_press_event", + GTK_SIGNAL_FUNC (e_completion_view_key_press_handler), + cv); + } else { + cv->key_widget = NULL; + cv->key_signal_id = 0; + } +} + +void +e_completion_view_set_complete_key (ECompletionView *cv, gint keyval) +{ + g_return_if_fail (cv != NULL); + g_return_if_fail (E_IS_COMPLETION_VIEW (cv)); + + cv->complete_key = keyval; +} + +void +e_completion_view_set_uncomplete_key (ECompletionView *cv, gint keyval) +{ + g_return_if_fail (cv != NULL); + g_return_if_fail (E_IS_COMPLETION_VIEW (cv)); + + cv->uncomplete_key = keyval; +} + +void +e_completion_view_set_width (ECompletionView *cv, gint width) +{ + GtkWidget *w; + gint y, r, dummy, line_height; + double drop_room, lines; + + g_return_if_fail (cv != NULL); + g_return_if_fail (E_IS_COMPLETION_VIEW (cv)); + g_return_if_fail (width > 0); + + w = GTK_WIDGET (cv); + + if (! GTK_WIDGET_REALIZED (w)) { + gtk_widget_set_usize (w, width, -1); + return; + } + + /* A Horrible Hack(tm) to figure out the height of a single table row */ + + for (line_height=5, r=0; r == 0 && line_height < 1000; line_height += 2) { + dummy = 0; + e_table_group_compute_location (e_completion_view_table (cv)->group, + &dummy, &line_height, &r, &dummy); + } + if (line_height >= 1000) { + /* Something went wrong, so we make a (possibly very lame) guess */ + line_height = 30; + } + + + gdk_window_get_origin (w->window, NULL, &y); + y += w->allocation.y; + + lines = 5; /* default maximum */ + lines = MIN (lines, cv->choice_count); + + drop_room = (gdk_screen_height () - y) / (double)line_height; + drop_room = MAX (drop_room, 1); + + lines = MIN (lines, drop_room); + + /* We reduce the total height by a bit; in practice, this seems to work out well. */ + gtk_widget_set_usize (w, width, (gint) floor (line_height * lines * 0.97)); +} + +void +e_completion_view_set_editable (ECompletionView *cv, gboolean x) +{ + g_return_if_fail (cv != NULL); + g_return_if_fail (E_IS_COMPLETION_VIEW (cv)); + + if (x == cv->editable) + return; + + cv->editable = x; + cv->selection = -1; + e_completion_view_set_cursor_row (cv, -1); +} + -- cgit v1.2.3