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
478 lines
12 KiB
C
478 lines
12 KiB
C
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
|
||
|
||
/* ECompletion - A base class for text completion.
|
||
* Copyright (C) 2000, 2001 Ximian, Inc.
|
||
*
|
||
* Author: Miguel de Icaza <miguel@ximian.com>
|
||
* Adapted by Jon Trowbridge <trow@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 <gtk/gtk.h>
|
||
#include "e-completion.h"
|
||
|
||
enum {
|
||
E_COMPLETION_BEGIN_COMPLETION,
|
||
E_COMPLETION_COMPLETION,
|
||
E_COMPLETION_RESTART_COMPLETION,
|
||
E_COMPLETION_CANCEL_COMPLETION,
|
||
E_COMPLETION_END_COMPLETION,
|
||
E_COMPLETION_LAST_SIGNAL
|
||
};
|
||
|
||
static guint e_completion_signals[E_COMPLETION_LAST_SIGNAL] = { 0 };
|
||
|
||
typedef struct _Match Match;
|
||
struct _Match {
|
||
gchar *text;
|
||
double score;
|
||
gpointer extra_data;
|
||
GtkDestroyNotify extra_destroy;
|
||
};
|
||
|
||
struct _ECompletionPrivate {
|
||
gboolean searching;
|
||
gchar *search_text;
|
||
gint pos;
|
||
gint limit;
|
||
gint match_count;
|
||
GList *matches;
|
||
double min_score, max_score;
|
||
};
|
||
|
||
static void e_completion_class_init (ECompletionClass *klass);
|
||
static void e_completion_init (ECompletion *complete);
|
||
static void e_completion_destroy (GtkObject *object);
|
||
|
||
static Match *match_new (const gchar *txt, double score, gpointer extra_data, GtkDestroyNotify extra_destroy);
|
||
static void match_free (Match *);
|
||
static void match_list_free (GList *);
|
||
|
||
static void e_completion_add_match (ECompletion *complete, const gchar *txt, double score, gpointer extra_data, GtkDestroyNotify);
|
||
static void e_completion_clear_matches (ECompletion *complete);
|
||
static gboolean e_completion_sort_by_score (ECompletion *complete);
|
||
static void e_completion_restart (ECompletion *complete);
|
||
|
||
static GtkObjectClass *parent_class;
|
||
|
||
|
||
|
||
GtkType
|
||
e_completion_get_type (void)
|
||
{
|
||
static GtkType complete_type = 0;
|
||
|
||
if (!complete_type) {
|
||
GtkTypeInfo complete_info = {
|
||
"ECompletion",
|
||
sizeof (ECompletion),
|
||
sizeof (ECompletionClass),
|
||
(GtkClassInitFunc) e_completion_class_init,
|
||
(GtkObjectInitFunc) e_completion_init,
|
||
NULL, NULL, /* reserved */
|
||
(GtkClassInitFunc) NULL
|
||
};
|
||
|
||
complete_type = gtk_type_unique (gtk_object_get_type (), &complete_info);
|
||
}
|
||
|
||
return complete_type;
|
||
}
|
||
|
||
static void
|
||
e_completion_class_init (ECompletionClass *klass)
|
||
{
|
||
GtkObjectClass *object_class = (GtkObjectClass *) klass;
|
||
|
||
parent_class = GTK_OBJECT_CLASS (gtk_type_class (gtk_object_get_type ()));
|
||
|
||
e_completion_signals[E_COMPLETION_BEGIN_COMPLETION] =
|
||
gtk_signal_new ("begin_completion",
|
||
GTK_RUN_LAST,
|
||
object_class->type,
|
||
GTK_SIGNAL_OFFSET (ECompletionClass, begin_completion),
|
||
gtk_marshal_NONE__POINTER_INT_INT,
|
||
GTK_TYPE_NONE, 3,
|
||
GTK_TYPE_POINTER, GTK_TYPE_INT, GTK_TYPE_INT);
|
||
|
||
e_completion_signals[E_COMPLETION_COMPLETION] =
|
||
gtk_signal_new ("completion",
|
||
GTK_RUN_LAST,
|
||
object_class->type,
|
||
GTK_SIGNAL_OFFSET (ECompletionClass, completion),
|
||
gtk_marshal_NONE__POINTER_POINTER,
|
||
GTK_TYPE_NONE, 2,
|
||
GTK_TYPE_POINTER, GTK_TYPE_POINTER);
|
||
|
||
e_completion_signals[E_COMPLETION_RESTART_COMPLETION] =
|
||
gtk_signal_new ("restart_completion",
|
||
GTK_RUN_LAST,
|
||
object_class->type,
|
||
GTK_SIGNAL_OFFSET (ECompletionClass, restart_completion),
|
||
gtk_marshal_NONE__NONE,
|
||
GTK_TYPE_NONE, 0);
|
||
|
||
e_completion_signals[E_COMPLETION_CANCEL_COMPLETION] =
|
||
gtk_signal_new ("cancel_completion",
|
||
GTK_RUN_LAST,
|
||
object_class->type,
|
||
GTK_SIGNAL_OFFSET (ECompletionClass, cancel_completion),
|
||
gtk_marshal_NONE__NONE,
|
||
GTK_TYPE_NONE, 0);
|
||
|
||
e_completion_signals[E_COMPLETION_END_COMPLETION] =
|
||
gtk_signal_new ("end_completion",
|
||
GTK_RUN_LAST,
|
||
object_class->type,
|
||
GTK_SIGNAL_OFFSET (ECompletionClass, end_completion),
|
||
gtk_marshal_NONE__NONE,
|
||
GTK_TYPE_NONE, 0);
|
||
|
||
gtk_object_class_add_signals (object_class, e_completion_signals, E_COMPLETION_LAST_SIGNAL);
|
||
|
||
object_class->destroy = e_completion_destroy;
|
||
}
|
||
|
||
static void
|
||
e_completion_init (ECompletion *complete)
|
||
{
|
||
complete->priv = g_new0 (struct _ECompletionPrivate, 1);
|
||
}
|
||
|
||
static void
|
||
e_completion_destroy (GtkObject *object)
|
||
{
|
||
ECompletion *complete = E_COMPLETION (object);
|
||
|
||
g_free (complete->priv->search_text);
|
||
complete->priv->search_text = NULL;
|
||
|
||
e_completion_clear_matches (complete);
|
||
|
||
g_free (complete->priv);
|
||
complete->priv = NULL;
|
||
|
||
if (parent_class->destroy)
|
||
(parent_class->destroy) (object);
|
||
}
|
||
|
||
static Match *
|
||
match_new (const gchar *text, double score, gpointer extra_data, GtkDestroyNotify extra_destroy)
|
||
{
|
||
Match *m;
|
||
|
||
if (text == NULL)
|
||
return NULL;
|
||
|
||
m = g_new (Match, 1);
|
||
m->text = g_strdup (text);
|
||
m->score = score;
|
||
m->extra_data = extra_data;
|
||
m->extra_destroy = extra_destroy;
|
||
|
||
return m;
|
||
}
|
||
|
||
static void
|
||
match_free (Match *m)
|
||
{
|
||
if (m) {
|
||
g_free (m->text);
|
||
if (m->extra_destroy)
|
||
m->extra_destroy (m->extra_data);
|
||
g_free (m);
|
||
}
|
||
}
|
||
|
||
static void
|
||
match_list_free (GList *i)
|
||
{
|
||
while (i) {
|
||
match_free ( (Match *) i->data );
|
||
i = g_list_next (i);
|
||
}
|
||
}
|
||
|
||
static void
|
||
e_completion_add_match (ECompletion *complete, const gchar *txt, double score, gpointer extra_data, GtkDestroyNotify extra_destroy)
|
||
{
|
||
complete->priv->matches = g_list_append (complete->priv->matches, match_new (txt, score, extra_data, extra_destroy));
|
||
|
||
if (complete->priv->match_count == 0) {
|
||
|
||
complete->priv->min_score = complete->priv->max_score = score;
|
||
|
||
} else {
|
||
|
||
complete->priv->min_score = MIN (complete->priv->min_score, score);
|
||
complete->priv->max_score = MAX (complete->priv->max_score, score);
|
||
|
||
}
|
||
|
||
++complete->priv->match_count;
|
||
}
|
||
|
||
static void
|
||
e_completion_clear_matches (ECompletion *complete)
|
||
{
|
||
match_list_free (complete->priv->matches);
|
||
g_list_free (complete->priv->matches);
|
||
complete->priv->matches = NULL;
|
||
|
||
complete->priv->match_count = 0;
|
||
|
||
complete->priv->min_score = 0;
|
||
complete->priv->max_score = 0;
|
||
}
|
||
|
||
void
|
||
e_completion_begin_search (ECompletion *complete, const gchar *text, gint pos, gint limit)
|
||
{
|
||
g_return_if_fail (complete != NULL);
|
||
g_return_if_fail (E_IS_COMPLETION (complete));
|
||
g_return_if_fail (text != NULL);
|
||
|
||
/* Stop any prior search. */
|
||
if (complete->priv->searching)
|
||
e_completion_cancel_search (complete);
|
||
|
||
g_free (complete->priv->search_text);
|
||
complete->priv->search_text = g_strdup (text);
|
||
|
||
complete->priv->pos = pos;
|
||
complete->priv->searching = TRUE;
|
||
|
||
e_completion_clear_matches (complete);
|
||
|
||
complete->priv->limit = limit > 0 ? limit : G_MAXINT;
|
||
|
||
gtk_signal_emit (GTK_OBJECT (complete), e_completion_signals[E_COMPLETION_BEGIN_COMPLETION], text, pos, limit);
|
||
}
|
||
|
||
void
|
||
e_completion_cancel_search (ECompletion *complete)
|
||
{
|
||
g_return_if_fail (complete != NULL);
|
||
g_return_if_fail (E_IS_COMPLETION (complete));
|
||
|
||
/* If there is no search to cancel, just silently return. */
|
||
if (!complete->priv->searching)
|
||
return;
|
||
|
||
gtk_signal_emit (GTK_OBJECT (complete), e_completion_signals[E_COMPLETION_CANCEL_COMPLETION]);
|
||
|
||
complete->priv->searching = FALSE;
|
||
}
|
||
|
||
gboolean
|
||
e_completion_searching (ECompletion *complete)
|
||
{
|
||
g_return_val_if_fail (complete != NULL, FALSE);
|
||
g_return_val_if_fail (E_IS_COMPLETION (complete), FALSE);
|
||
|
||
return complete->priv->searching;
|
||
}
|
||
|
||
const gchar *
|
||
e_completion_search_text (ECompletion *complete)
|
||
{
|
||
g_return_val_if_fail (complete != NULL, NULL);
|
||
g_return_val_if_fail (E_IS_COMPLETION (complete), NULL);
|
||
|
||
return complete->priv->search_text;
|
||
}
|
||
|
||
gint
|
||
e_completion_search_text_pos (ECompletion *complete)
|
||
{
|
||
g_return_val_if_fail (complete != NULL, -1);
|
||
g_return_val_if_fail (E_IS_COMPLETION (complete), -1);
|
||
|
||
return complete->priv->pos;
|
||
}
|
||
|
||
gint
|
||
e_completion_match_count (ECompletion *complete)
|
||
{
|
||
g_return_val_if_fail (complete != NULL, 0);
|
||
g_return_val_if_fail (E_IS_COMPLETION (complete), 0);
|
||
|
||
return complete->priv->match_count;
|
||
}
|
||
|
||
void
|
||
e_completion_foreach_match (ECompletion *complete, ECompletionMatchFn fn, gpointer user_data)
|
||
{
|
||
GList *i;
|
||
|
||
g_return_if_fail (complete != NULL);
|
||
g_return_if_fail (E_IS_COMPLETION (complete));
|
||
|
||
if (fn == NULL)
|
||
return;
|
||
|
||
for (i = complete->priv->matches; i != NULL; i = g_list_next (i)) {
|
||
Match *m = (Match *) i->data;
|
||
fn (m->text, m->score, m->extra_data, user_data);
|
||
}
|
||
}
|
||
|
||
gpointer
|
||
e_completion_find_extra_data (ECompletion *complete, const gchar *text)
|
||
{
|
||
GList *i;
|
||
|
||
g_return_val_if_fail (complete != NULL, NULL);
|
||
g_return_val_if_fail (E_IS_COMPLETION (complete), NULL);
|
||
|
||
for (i = complete->priv->matches; i != NULL; i = g_list_next (i)) {
|
||
Match *m = (Match *) i->data;
|
||
if (strcmp (m->text, text) == 0)
|
||
return m->extra_data;
|
||
}
|
||
|
||
return NULL;
|
||
}
|
||
|
||
ECompletion *
|
||
e_completion_new (void)
|
||
{
|
||
return E_COMPLETION (gtk_type_new (e_completion_get_type ()));
|
||
}
|
||
|
||
static gint
|
||
score_cmp_fn (gconstpointer a, gconstpointer b)
|
||
{
|
||
double sa = ((const Match *) a)->score;
|
||
double sb = ((const Match *) b)->score;
|
||
gint cmp = (sa < sb) - (sb < sa);
|
||
if (cmp == 0)
|
||
cmp = g_strcasecmp (((const Match *) a)->text, ((const Match *) b)->text);
|
||
return cmp;
|
||
}
|
||
|
||
static gboolean
|
||
e_completion_sort_by_score (ECompletion *complete)
|
||
{
|
||
GList *sort_list = NULL, *i, *j;
|
||
gboolean diff;
|
||
gint count;
|
||
|
||
/* If all scores are equal, there is nothing to do. */
|
||
if (complete->priv->min_score == complete->priv->max_score)
|
||
return FALSE;
|
||
|
||
for (i = complete->priv->matches; i != NULL; i = g_list_next (i)) {
|
||
sort_list = g_list_append (sort_list, i->data);
|
||
}
|
||
|
||
sort_list = g_list_sort (sort_list, score_cmp_fn);
|
||
|
||
|
||
diff = FALSE;
|
||
count = 0;
|
||
i = complete->priv->matches;
|
||
j = sort_list;
|
||
while (i && j && !diff && count < complete->priv->limit) {
|
||
|
||
if (i->data != j->data)
|
||
diff = TRUE;
|
||
|
||
i = g_list_next (i);
|
||
j = g_list_next (j);
|
||
++count;
|
||
}
|
||
|
||
g_list_free (complete->priv->matches);
|
||
complete->priv->matches = sort_list;
|
||
|
||
return diff;
|
||
}
|
||
|
||
/* Emit a restart signal and re-declare our matches, up to the limit. */
|
||
static void
|
||
e_completion_restart (ECompletion *complete)
|
||
{
|
||
GList *i;
|
||
gint count = 0;
|
||
|
||
gtk_signal_emit (GTK_OBJECT (complete), e_completion_signals[E_COMPLETION_RESTART_COMPLETION]);
|
||
|
||
i = complete->priv->matches;
|
||
while (i != NULL && count < complete->priv->limit) {
|
||
Match *m = (Match *) i->data;
|
||
gtk_signal_emit (GTK_OBJECT (complete), e_completion_signals[E_COMPLETION_COMPLETION], m->text, m->extra_data);
|
||
|
||
i = g_list_next (i);
|
||
++count;
|
||
}
|
||
}
|
||
|
||
void
|
||
e_completion_found_match (ECompletion *complete, const gchar *text)
|
||
{
|
||
g_return_if_fail (complete);
|
||
g_return_if_fail (E_IS_COMPLETION (complete));
|
||
g_return_if_fail (text != NULL);
|
||
|
||
e_completion_found_match_full (complete, text, 0, NULL, NULL);
|
||
}
|
||
|
||
void
|
||
e_completion_found_match_full (ECompletion *complete, const gchar *text, double score, gpointer extra_data, GtkDestroyNotify extra_destroy)
|
||
{
|
||
g_return_if_fail (complete);
|
||
g_return_if_fail (E_IS_COMPLETION (complete));
|
||
g_return_if_fail (text != NULL);
|
||
|
||
if (! complete->priv->searching) {
|
||
g_warning ("e_completion_found_match(...,\"%s\",...) called outside of a search", text);
|
||
return;
|
||
}
|
||
|
||
e_completion_add_match (complete, text, score, extra_data, extra_destroy);
|
||
|
||
/* For now, do nothing when we hit the limit --- just don't announce the incoming matches. */
|
||
if (complete->priv->match_count >= complete->priv->limit) {
|
||
return;
|
||
}
|
||
|
||
gtk_signal_emit (GTK_OBJECT (complete), e_completion_signals[E_COMPLETION_COMPLETION], text, extra_data);
|
||
}
|
||
|
||
void
|
||
e_completion_end_search (ECompletion *complete)
|
||
{
|
||
g_return_if_fail (complete != NULL);
|
||
g_return_if_fail (E_IS_COMPLETION (complete));
|
||
g_return_if_fail (complete->priv->searching);
|
||
|
||
/* If sorting by score accomplishes anything, issue a restart right before we end. */
|
||
if (e_completion_sort_by_score (complete))
|
||
e_completion_restart (complete);
|
||
|
||
gtk_signal_emit (GTK_OBJECT (complete), e_completion_signals[E_COMPLETION_END_COMPLETION]);
|
||
|
||
complete->priv->searching = FALSE;
|
||
}
|
||
|