Files
evolution/widgets/text/e-completion-view.c
Jon Trowbridge fae87e8d3d Boost version number to 0.5.99.3.
2001-03-01  Jon Trowbridge  <trow@ximian.com>

        * configure.in: Boost version number to 0.5.99.3.

        * gal/e-text/e-entry.c (e_entry_show_popup): Grab pointer when the
        popup is visible, and then hide the popup if any button press
        events occur outside of the popup.  This lets up avoid most of the
        worst "floating popup" cases that would occur if windows are
        moved, desktops changed, etc. with the mouse.  (Doing things like
        changing desktop w/ keybindings can still cause a "floating
        popup", but that is also true of Gtk's own combo box.)  Change
        popup positioning to slightly offset it from the entry, rather
        than just plopping it down directly below.
        (button_press_cb): Determine if a button press occured outside of
        the popup when the pointer was grabbed, and unbrowse accordingly.

        * gal/e-text/e-completion-view.c
        (e_completion_view_key_press_handler): Improve keystroke handling.
        Allow Tabs to pass through (after hiding the pop-up) in order to
        allow focus change requests to work properly.
        (e_completion_view_construct): Disable horizontal scrollbars.

        * gal/e-text/e-completion-test.c (main): Reworked to use signals
        instead of explicit callbacks.

        * gal/e-text/e-completion.h:
        * gal/e-text/e-completion.c: Fix the awkward mix of signals and
        explicitly-specified callbacks by taking out the explicit
        callbacks.  This approach is more gtk-ish, after all.

svn path=/trunk/; revision=8458
2001-03-01 20:59:42 +00:00

692 lines
18 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* -*- 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 <trow@ximian.com>
* Adapted from code by Miguel de Icaza <miguel@ximian.com>
*/
/*
* 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 <config.h>
#include <math.h>
#include <gdk/gdkkeysyms.h>
#include <gal/e-table/e-table-simple.h>
#include <gal/e-table/e-table-scrolled.h>
#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_event_box_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_event_box_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_selection_model_clear (E_SELECTION_MODEL(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;
gboolean key_handled = TRUE;
/* 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_Tab:
/* Unbrowse, unhandled. */
cv->selection = -1;
dir = 0;
key_handled = FALSE;
break;
case GDK_Return:
case GDK_KP_Enter:
case GDK_space:
case GDK_KP_Space:
/* 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:
/* Unbrowse hack */
cv->selection = -1;
dir = 0;
break;
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:
if (key_handled)
gtk_signal_emit_stop_by_name (GTK_OBJECT (w), "key_press_event");
return key_handled;
}
static void
begin_completion_cb (ECompletion *completion, const gchar *txt, gint pos, gint limit, 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, NULL, 0, 0, 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 =
"<ETableSpecification no-headers=\"true\" draw-grid=\"false\" cursor-mode=\"line\"> "
" <ETableColumn model_col=\"0\" _title=\"Node\" expansion=\"1.0\" "
" minimum_width=\"16\" resizable=\"true\" cell=\"string\" "
" compare=\"string\"/> "
" <ETableState> "
" <column source=\"0\"/> "
" <grouping></grouping> "
" </ETableState> "
"</ETableSpecification>";
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);
e_scroll_frame_set_policy (E_SCROLL_FRAME (cv->table), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
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);
}