diff options
-rw-r--r-- | widgets/text/e-completion-test.c | 169 | ||||
-rw-r--r-- | widgets/text/e-completion.c | 511 | ||||
-rw-r--r-- | widgets/text/e-completion.h | 95 | ||||
-rw-r--r-- | widgets/text/e-entry.c | 621 | ||||
-rw-r--r-- | widgets/text/e-entry.h | 73 | ||||
-rw-r--r-- | widgets/text/e-text-model-repos.c | 73 | ||||
-rw-r--r-- | widgets/text/e-text-model-repos.h | 55 | ||||
-rw-r--r-- | widgets/text/e-text-model-test.c | 58 | ||||
-rw-r--r-- | widgets/text/e-text-model-uri.c | 273 | ||||
-rw-r--r-- | widgets/text/e-text-model-uri.h | 6 | ||||
-rw-r--r-- | widgets/text/e-text-model.c | 520 | ||||
-rw-r--r-- | widgets/text/e-text-model.h | 79 | ||||
-rw-r--r-- | widgets/text/e-text.c | 570 | ||||
-rw-r--r-- | widgets/text/e-text.h | 3 |
14 files changed, 2483 insertions, 623 deletions
diff --git a/widgets/text/e-completion-test.c b/widgets/text/e-completion-test.c new file mode 100644 index 0000000000..0d846ec9ff --- /dev/null +++ b/widgets/text/e-completion-test.c @@ -0,0 +1,169 @@ +/* + ECompleteTest +*/ + +#include <gnome.h> +#include "e-completion.h" +#include "e-entry.h" + +#define TIMEOUT 50 + +/* Dictionary Lookup test */ + +static gint word_count = 0; +static gchar **word_array = NULL; + +static void +read_dict (void) +{ + FILE *in = fopen ("/usr/share/dict/words", "r"); + gchar buffer[128]; + GList *word_list = NULL, *iter; + gint i; + + while (fgets (buffer, 128, in)) { + gint len = strlen (buffer); + if (len > 0 && buffer[len-1] == '\n') + buffer[len-1] = '\0'; + word_list = g_list_prepend (word_list, g_strdup (buffer)); + ++word_count; + } + fclose (in); + + word_array = g_new (gchar *, word_count); + i = word_count-1; + for (iter = word_list; iter != NULL; iter = g_list_next (iter)) { + word_array[i] = (gchar *)iter->data; + --i; + } +} + +static gint +find_word (const gchar *str) +{ + gint a, b; + + if (word_array == NULL) + read_dict (); + + a = 0; + b = word_count-1; + + while (b-a > 1) { + gint m = (a+b)/2; + gint cmp = g_strcasecmp (str, word_array[m]); + + if (cmp < 0) + b = m; + else if (cmp > 0) + a = m; + else + return m; + }; + + return b; +} + +struct { + ECompletion *complete; + const gchar *txt; + gint start; + gint current; + gint len; + gint limit; +} dict_info; +static guint dict_tag = 0; + +static gboolean +dict_check (gpointer ptr) +{ + gint limit = dict_info.limit; + gint i; + + /* If this is the first iteration, do the binary search in our word list to figure out + where to start. We do less work on the first iteration, to give more of a sense of + immediate feedback. */ + if (dict_info.start < 0) { + dict_info.start = dict_info.current = find_word (dict_info.txt); + } + + i = dict_info.current; + while (limit > 0 && i < word_count && g_strncasecmp (dict_info.txt, word_array[i], dict_info.len) == 0) { + e_completion_found_match_full (dict_info.complete, word_array[i], + dict_info.len / (double)strlen (word_array[i]), + NULL, NULL); + ++i; + --limit; + } + dict_info.current = i; + dict_info.limit = MIN (dict_info.limit*2, 200); + + if (limit != 0) { + dict_tag = 0; + e_completion_end_search (dict_info.complete); + return FALSE; + } + + + + return TRUE; +} + +static void +begin_dict_search (ECompletion *complete, const gchar *txt, gint pos, gint limit, gpointer user_data) +{ + gint len = strlen (txt); + + if (dict_tag != 0) { + gtk_timeout_remove (dict_tag); + dict_tag = 0; + } + + if (len > 0) { + dict_info.complete = complete; + dict_info.txt = txt; + dict_info.start = -1; + dict_info.current = -1; + dict_info.len = len; + dict_info.limit = 20; + dict_tag = gtk_timeout_add (TIMEOUT, dict_check, NULL); + } else { + g_message ("halting"); + e_completion_end_search (complete); + } +} + +static void +end_dict_search (ECompletion *complete, gboolean finished, gpointer foo) +{ + if (dict_tag != 0) { + gtk_timeout_remove (dict_tag); + dict_tag = 0; + } +} + +int +main (int argc, gchar **argv) +{ + ECompletion* complete; + GtkWidget *entry; + GtkWidget *win; + + gnome_init ("ETextModelTest", "0.0", argc, argv); + + read_dict (); + + complete = e_completion_new (begin_dict_search, end_dict_search, NULL); + + win = gtk_window_new (GTK_WINDOW_TOPLEVEL); + entry = e_entry_new (); + e_entry_enable_completion_full (E_ENTRY (entry), complete, 100, NULL); + e_entry_set_editable (E_ENTRY (entry), TRUE); + + gtk_container_add (GTK_CONTAINER (win), entry); + gtk_widget_show_all (win); + + gtk_main (); + + return 0; +} diff --git a/widgets/text/e-completion.c b/widgets/text/e-completion.c new file mode 100644 index 0000000000..8b875c7af6 --- /dev/null +++ b/widgets/text/e-completion.c @@ -0,0 +1,511 @@ +/* -*- 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 { + + ECompletionBeginFn begin_search; + ECompletionEndFn end_search; + gpointer user_data; + + 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__NONE, + GTK_TYPE_NONE, 0); + + 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); + + /* Without one of these, we can't search! */ + if (complete->priv->begin_search) { + + 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]); + complete->priv->begin_search (complete, text, pos, limit, complete->priv->user_data); + return; + } + + g_warning ("Unable to search for \"%s\" - no virtual method specified.", text); +} + +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; + + if (complete->priv->end_search) + complete->priv->end_search (complete, FALSE, complete->priv->user_data); + + complete->priv->searching = FALSE; + + gtk_signal_emit (GTK_OBJECT (complete), e_completion_signals[E_COMPLETION_CANCEL_COMPLETION]); +} + +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; +} + +void +e_completion_construct (ECompletion *complete, ECompletionBeginFn begin_fn, ECompletionEndFn end_fn, gpointer user_data) +{ + g_return_if_fail (complete != NULL); + g_return_if_fail (E_IS_COMPLETION (complete)); + + complete->priv->begin_search = begin_fn; + complete->priv->end_search = end_fn; + complete->priv->user_data = user_data; +} + +ECompletion * +e_completion_new (ECompletionBeginFn begin_fn, ECompletionEndFn end_fn, gpointer user_data) +{ + ECompletion *complete = E_COMPLETION (gtk_type_new (e_completion_get_type ())); + + e_completion_construct (complete, begin_fn, end_fn, user_data); + + return complete; +} + +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); + + if (complete->priv->end_search) + complete->priv->end_search (complete, TRUE, complete->priv->user_data); + + complete->priv->searching = FALSE; + + gtk_signal_emit (GTK_OBJECT (complete), e_completion_signals[E_COMPLETION_END_COMPLETION]); +} + diff --git a/widgets/text/e-completion.h b/widgets/text/e-completion.h new file mode 100644 index 0000000000..41be79a927 --- /dev/null +++ b/widgets/text/e-completion.h @@ -0,0 +1,95 @@ +/* -*- 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 + */ + +#ifndef E_COMPLETION_H +#define E_COMPLETION_H + +#include <libgnome/gnome-defs.h> +#include <gtk/gtkobject.h> + +BEGIN_GNOME_DECLS + +#define E_COMPLETION_TYPE (e_completion_get_type ()) +#define E_COMPLETION(o) (GTK_CHECK_CAST ((o), E_COMPLETION_TYPE, ECompletion)) +#define E_COMPLETION_CLASS(k) (GTK_CHECK_CLASS_CAST ((k), E_COMPLETION_TYPE, ECompletionClass)) +#define E_IS_COMPLETION(o) (GTK_CHECK_TYPE ((o), E_COMPLETION_TYPE)) +#define E_IS_COMPLETION_CLASS(k) (GTK_CHECK_CLASS_TYPE ((k), E_COMPLETION_TYPE)) + +typedef struct _ECompletion ECompletion; +typedef struct _ECompletionClass ECompletionClass; +struct _ECompletionPrivate; + +typedef void (*ECompletionBeginFn) (ECompletion *, const gchar *text, gint pos, gint limit, gpointer user_data); +typedef void (*ECompletionEndFn) (ECompletion *, gboolean finished, gpointer user_data); +typedef void (*ECompletionMatchFn) (const gchar *text, double score, gpointer extra_data, gpointer user_data); + +struct _ECompletion { + GtkObject parent; + + struct _ECompletionPrivate *priv; +}; + +struct _ECompletionClass { + GtkObjectClass parent_class; + + /* Signals */ + void (*begin_completion) (ECompletion *comp); + void (*completion) (ECompletion *comp, const gchar *text, gpointer extra_data); + void (*restart_completion) (ECompletion *comp); + void (*cancel_completion) (ECompletion *comp); + void (*end_completion) (ECompletion *comp); +}; + +GtkType e_completion_get_type (void); + +void e_completion_begin_search (ECompletion *comp, const gchar *text, gint pos, gint limit); +void e_completion_cancel_search (ECompletion *comp); + +gboolean e_completion_searching (ECompletion *comp); +const gchar *e_completion_search_text (ECompletion *comp); +gint e_completion_search_text_pos (ECompletion *comp); +gint e_completion_match_count (ECompletion *comp); +void e_completion_foreach_match (ECompletion *comp, ECompletionMatchFn fn, gpointer user_data); +gpointer e_completion_find_extra_data (ECompletion *comp, const gchar *text); + +void e_completion_construct (ECompletion *comp, ECompletionBeginFn, ECompletionEndFn, gpointer user_data); +ECompletion *e_completion_new (ECompletionBeginFn, ECompletionEndFn, gpointer user_data); + + + +/* These functions should only be called by derived classes or search callbacks, + or very bad things might happen. */ + +void e_completion_found_match (ECompletion *comp, const gchar *completion_text); +void e_completion_found_match_full (ECompletion *comp, const gchar *completion_text, double score, + gpointer extra_data, GtkDestroyNotify extra_data_destructor); +void e_completion_end_search (ECompletion *comp); + +END_GNOME_DECLS + + +#endif /* E_COMPLETION_H */ diff --git a/widgets/text/e-entry.c b/widgets/text/e-entry.c index cac7759f58..e9ff4bedf5 100644 --- a/widgets/text/e-entry.c +++ b/widgets/text/e-entry.c @@ -1,17 +1,39 @@ /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ + +/* + * EEntry: An EText-based entry widget + * + * Authors: + * Miguel de Icaza <miguel@helixcode.com> + * Chris Lahey <clahey@helixcode.com> + * Jon Trowbridge <trow@ximian.com> + * + * Copyright (C) 1999, 2000, 2001 Ximian Inc. + */ + /* - * E-table.c: A graphical view of a Table. + * 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. * - * Author: - * Miguel de Icaza (miguel@helixcode.com) - * Chris Lahey (clahey@helixcode.com) + * 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. * - * Copyright 1999, Helix Code, Inc + * 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 <stdlib.h> #include <stdio.h> #include <string.h> +#include <ctype.h> #ifdef HAVE_ALLOCA_H #include <alloca.h> #endif @@ -22,6 +44,8 @@ #include "gal/util/e-util.h" #include "gal/widgets/e-canvas.h" #include "gal/widgets/e-canvas-utils.h" +#include "e-completion-view.h" +#include "e-text.h" #include "e-entry.h" #define MIN_ENTRY_WIDTH 150 @@ -68,21 +92,54 @@ enum { ARG_CURSOR_POS }; +typedef struct _EEntryPrivate EEntryPrivate; +struct _EEntryPrivate { + GnomeCanvas *canvas; + EText *item; + GtkJustification justification; + + guint changed_proxy_tag; + guint activate_proxy_tag; + + /* Data related to completions */ + ECompletion *completion; + EEntryCompletionHandler handler; + GtkWidget *completion_view; + guint nonempty_signal_id; + guint added_signal_id; + guint full_signal_id; + guint browse_signal_id; + guint unbrowse_signal_id; + guint activate_signal_id; + GtkWidget *completion_view_popup; + gboolean popup_is_visible; + gchar *pre_browse_text; + gint completion_delay; + guint completion_delay_tag; + + guint draw_borders : 1; +}; + +static gboolean e_entry_is_empty (EEntry *entry); +static void e_entry_show_popup (EEntry *entry, gboolean x); +static void e_entry_start_completion (EEntry *entry); +static void e_entry_start_delayed_completion (EEntry *entry, gint delay); +static void e_entry_cancel_delayed_completion (EEntry *entry); + static void canvas_size_allocate (GtkWidget *widget, GtkAllocation *alloc, - EEntry *e_entry) + EEntry *entry) { gint xthick; gint ythick; - gnome_canvas_set_scroll_region ( - e_entry->canvas, - 0, 0, alloc->width, alloc->height); - gtk_object_set (GTK_OBJECT (e_entry->item), + gnome_canvas_set_scroll_region (entry->priv->canvas, + 0, 0, alloc->width, alloc->height); + gtk_object_set (GTK_OBJECT (entry->priv->item), "clip_width", (double) (alloc->width), "clip_height", (double) (alloc->height), NULL); - if (e_entry->draw_borders) { + if (entry->priv->draw_borders) { xthick = 0; ythick = 0; } else { @@ -90,22 +147,25 @@ canvas_size_allocate (GtkWidget *widget, GtkAllocation *alloc, ythick = widget->style->klass->ythickness; } - switch (e_entry->justification) { + switch (entry->priv->justification) { case GTK_JUSTIFY_RIGHT: - e_canvas_item_move_absolute(GNOME_CANVAS_ITEM(e_entry->item), alloc->width - xthick, ythick); + e_canvas_item_move_absolute(GNOME_CANVAS_ITEM(entry->priv->item), + alloc->width - xthick, ythick); break; case GTK_JUSTIFY_CENTER: - e_canvas_item_move_absolute(GNOME_CANVAS_ITEM(e_entry->item), alloc->width / 2, ythick); + e_canvas_item_move_absolute(GNOME_CANVAS_ITEM(entry->priv->item), + alloc->width / 2, ythick); break; default: - e_canvas_item_move_absolute(GNOME_CANVAS_ITEM(e_entry->item), xthick, ythick); + e_canvas_item_move_absolute(GNOME_CANVAS_ITEM(entry->priv->item), + xthick, ythick); break; } } static void canvas_size_request (GtkWidget *widget, GtkRequisition *requisition, - EEntry *ee) + EEntry *entry) { int border; @@ -113,7 +173,7 @@ canvas_size_request (GtkWidget *widget, GtkRequisition *requisition, g_return_if_fail (GNOME_IS_CANVAS (widget)); g_return_if_fail (requisition != NULL); - if (ee->draw_borders) + if (entry->priv->draw_borders) border = INNER_BORDER; else border = 0; @@ -125,65 +185,94 @@ canvas_size_request (GtkWidget *widget, GtkRequisition *requisition, } static gint -canvas_focus_in_event (GtkWidget *widget, GdkEventFocus *focus, EEntry *e_entry) +canvas_focus_in_event (GtkWidget *widget, GdkEventFocus *focus, EEntry *entry) { - if (e_entry->canvas->focused_item != GNOME_CANVAS_ITEM(e_entry->item)) - gnome_canvas_item_grab_focus(GNOME_CANVAS_ITEM(e_entry->item)); + if (entry->priv->canvas->focused_item != GNOME_CANVAS_ITEM(entry->priv->item)) + gnome_canvas_item_grab_focus(GNOME_CANVAS_ITEM(entry->priv->item)); return 0; } static void -e_entry_proxy_changed (EText *text, EEntry *ee) +e_entry_proxy_changed (EText *text, EEntry *entry) { - gtk_signal_emit (GTK_OBJECT (ee), e_entry_signals [E_ENTRY_CHANGED]); + if (e_entry_is_empty (entry)) { + e_entry_cancel_delayed_completion (entry); + e_entry_show_popup (entry, FALSE); + } else if (entry->priv->popup_is_visible) + e_entry_start_completion (entry); + else if (entry->priv->completion && entry->priv->completion_delay >= 0) + e_entry_start_delayed_completion (entry, entry->priv->completion_delay); + + gtk_signal_emit (GTK_OBJECT (entry), e_entry_signals [E_ENTRY_CHANGED]); } static void -e_entry_proxy_activate (EText *text, EEntry *ee) +e_entry_proxy_activate (EText *text, EEntry *entry) { - gtk_signal_emit (GTK_OBJECT (ee), e_entry_signals [E_ENTRY_ACTIVATE]); + gtk_signal_emit (GTK_OBJECT (entry), e_entry_signals [E_ENTRY_ACTIVATE]); } static void e_entry_init (GtkObject *object) { - EEntry *e_entry = E_ENTRY (object); + EEntry *entry = E_ENTRY (object); GtkTable *gtk_table = GTK_TABLE (object); + + entry->priv = g_new0 (EEntryPrivate, 1); - e_entry->canvas = GNOME_CANVAS(e_canvas_new()); - gtk_signal_connect(GTK_OBJECT(e_entry->canvas), "size_allocate", - GTK_SIGNAL_FUNC(canvas_size_allocate), e_entry); - gtk_signal_connect(GTK_OBJECT(e_entry->canvas), "size_request", - GTK_SIGNAL_FUNC(canvas_size_request), e_entry); - gtk_signal_connect(GTK_OBJECT(e_entry->canvas), "focus_in_event", - GTK_SIGNAL_FUNC(canvas_focus_in_event), e_entry); - e_entry->draw_borders = TRUE; - e_entry->item = E_TEXT(gnome_canvas_item_new(gnome_canvas_root(e_entry->canvas), - e_text_get_type(), - "clip", TRUE, - "fill_clip_rectangle", TRUE, - "anchor", GTK_ANCHOR_NW, - "draw_borders", TRUE, - "draw_background", TRUE, - NULL)); - e_entry->justification = GTK_JUSTIFY_LEFT; - gtk_table_attach(gtk_table, GTK_WIDGET(e_entry->canvas), - 0, 1, 0, 1, - GTK_EXPAND | GTK_FILL | GTK_SHRINK, - GTK_EXPAND | GTK_FILL | GTK_SHRINK, - 0, 0); - gtk_widget_show(GTK_WIDGET(e_entry->canvas)); + entry->priv->canvas = GNOME_CANVAS (e_canvas_new ()); + + gtk_signal_connect (GTK_OBJECT (entry->priv->canvas), + "size_allocate", + GTK_SIGNAL_FUNC (canvas_size_allocate), + entry); + + gtk_signal_connect (GTK_OBJECT (entry->priv->canvas), + "size_request", + GTK_SIGNAL_FUNC (canvas_size_request), + entry); + + gtk_signal_connect(GTK_OBJECT (entry->priv->canvas), + "focus_in_event", + GTK_SIGNAL_FUNC(canvas_focus_in_event), + entry); + + entry->priv->draw_borders = TRUE; + + entry->priv->item = E_TEXT(gnome_canvas_item_new(gnome_canvas_root (entry->priv->canvas), + e_text_get_type(), + "clip", TRUE, + "fill_clip_rectangle", TRUE, + "anchor", GTK_ANCHOR_NW, + "draw_borders", TRUE, + "draw_background", TRUE, + "max_lines", 1, + "editable", TRUE, + NULL)); + + entry->priv->justification = GTK_JUSTIFY_LEFT; + gtk_table_attach (gtk_table, GTK_WIDGET (entry->priv->canvas), + 0, 1, 0, 1, + GTK_EXPAND | GTK_FILL | GTK_SHRINK, + GTK_EXPAND | GTK_FILL | GTK_SHRINK, + 0, 0); + gtk_widget_show (GTK_WIDGET (entry->priv->canvas)); /* * Proxy functions: we proxy the changed and activate signals * from the item to outselves */ - gtk_signal_connect (GTK_OBJECT (e_entry->item), "changed", - GTK_SIGNAL_FUNC (e_entry_proxy_changed), e_entry); - gtk_signal_connect (GTK_OBJECT (e_entry->item), "activate", - GTK_SIGNAL_FUNC (e_entry_proxy_activate), e_entry); - + entry->priv->changed_proxy_tag = gtk_signal_connect (GTK_OBJECT (entry->priv->item), + "changed", + GTK_SIGNAL_FUNC (e_entry_proxy_changed), + entry); + entry->priv->activate_proxy_tag = gtk_signal_connect (GTK_OBJECT (entry->priv->item), + "activate", + GTK_SIGNAL_FUNC (e_entry_proxy_activate), + entry); + + entry->priv->completion_delay = -1; } /** @@ -191,12 +280,11 @@ e_entry_init (GtkObject *object) * * Constructs the given EEntry. * - * Returns: The EEntry **/ -EEntry * -e_entry_construct (EEntry *e_entry) +void +e_entry_construct (EEntry *entry) { - return e_entry; + /* Do nothing */ } @@ -210,18 +298,349 @@ e_entry_construct (EEntry *e_entry) GtkWidget * e_entry_new (void) { - EEntry *e_entry; - e_entry = gtk_type_new (e_entry_get_type ()); - e_entry = e_entry_construct (e_entry); + EEntry *entry; + entry = gtk_type_new (e_entry_get_type ()); + e_entry_construct (entry); + + return GTK_WIDGET (entry); +} + +const gchar * +e_entry_get_text (EEntry *entry) +{ + g_return_val_if_fail (entry != NULL && E_IS_ENTRY (entry), NULL); + + return e_text_model_get_text (entry->priv->item->model); +} + +void +e_entry_set_text (EEntry *entry, const gchar *txt) +{ + g_return_if_fail (entry != NULL && E_IS_ENTRY (entry)); + + e_text_model_set_text (entry->priv->item->model, txt); +} + +static void +e_entry_set_text_quiet (EEntry *entry, const gchar *txt) +{ + g_return_if_fail (entry != NULL && E_IS_ENTRY (entry)); + + gtk_signal_handler_block (GTK_OBJECT (entry->priv->item), entry->priv->changed_proxy_tag); + e_entry_set_text (entry, txt); + gtk_signal_handler_unblock (GTK_OBJECT (entry->priv->item), entry->priv->changed_proxy_tag); +} + + +void +e_entry_set_editable (EEntry *entry, gboolean am_i_editable) +{ + g_return_if_fail (entry != NULL && E_IS_ENTRY (entry)); + + gtk_object_set (GTK_OBJECT (entry->priv->item), "editable", am_i_editable, NULL); +} + +gint +e_entry_get_position (EEntry *entry) +{ + g_return_val_if_fail (entry != NULL && E_IS_ENTRY (entry), -1); + + return entry->priv->item->selection_start; +} + +void +e_entry_set_position (EEntry *entry, gint pos) +{ + g_return_if_fail (entry != NULL && E_IS_ENTRY (entry)); + if (pos < 0) + pos = 0; + else if (pos > e_text_model_get_text_length (entry->priv->item->model)) + pos = e_text_model_get_text_length (entry->priv->item->model); + + entry->priv->item->selection_start = pos; +} + +void +e_entry_select_region (EEntry *entry, gint pos1, gint pos2) +{ + g_return_if_fail (entry != NULL && E_IS_ENTRY (entry)); + + e_entry_set_position (entry, MAX (pos1, pos2)); + entry->priv->item->selection_end = entry->priv->item->selection_start; + e_entry_set_position (entry, MIN (pos1, pos2)); +} + +/** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **/ + +/*** Completion-related code ***/ + +static gboolean +e_entry_is_empty (EEntry *entry) +{ + const gchar *txt = e_entry_get_text (entry); + + if (txt == NULL) + return TRUE; + + while (*txt) { + if (!isspace ((gint) *txt)) + return FALSE; + ++txt; + } + + return TRUE; +} + +static void +e_entry_show_popup (EEntry *entry, gboolean visible) +{ + GtkWidget *pop = entry->priv->completion_view_popup; + + if (pop == NULL) + return; + + if (visible) { + GtkAllocation *dim = &(GTK_WIDGET (entry)->allocation); + gint x, y; + + /* Figure out where to put our popup. */ + gdk_window_get_origin (GTK_WIDGET (entry)->window, &x, &y); + x += dim->x; + y += dim->height + dim->y; + + gtk_widget_set_uposition (pop, x, y); + e_completion_view_set_width (E_COMPLETION_VIEW (entry->priv->completion_view), dim->width); + + gtk_widget_show (pop); + + } else { + + gtk_widget_hide (pop); + + } + + e_completion_view_set_editable (E_COMPLETION_VIEW (entry->priv->completion_view), visible); + entry->priv->popup_is_visible = visible; +} + +static void +e_entry_refresh_popup (EEntry *entry) +{ + if (entry->priv->popup_is_visible) + e_entry_show_popup (entry, TRUE); +} + +static void +e_entry_start_completion (EEntry *entry) +{ + if (entry->priv->completion == NULL) + return; + + e_entry_cancel_delayed_completion (entry); + + if (e_entry_is_empty (entry)) + return; + + if (entry->priv->completion) + e_completion_begin_search (entry->priv->completion, + e_entry_get_text (entry), + e_entry_get_position (entry), + 0); /* No limit. Probably a bad idea. */ +} + +static gboolean +start_delayed_cb (gpointer user_data) +{ + EEntry *entry = E_ENTRY (user_data); + entry->priv->completion_delay_tag = 0; + e_entry_start_completion (entry); + return FALSE; +} + +static void +e_entry_start_delayed_completion (EEntry *entry, gint delay) +{ + if (delay < 0) + return; + + e_entry_cancel_delayed_completion (entry); + + if (delay == 0) + e_entry_start_completion (entry); + else + entry->priv->completion_delay_tag = gtk_timeout_add (delay, start_delayed_cb, entry); +} + +static void +e_entry_cancel_delayed_completion (EEntry *entry) +{ + if (entry->priv->completion == NULL) + return; + + e_completion_cancel_search (entry->priv->completion); /* just to be sure... */ + if (entry->priv->completion_delay_tag) { + gtk_timeout_remove (entry->priv->completion_delay_tag); + entry->priv->completion_delay_tag = 0; + } +} + +static void +nonempty_cb (ECompletionView *view, gpointer user_data) +{ + EEntry *entry = E_ENTRY (user_data); + + e_entry_show_popup (entry, TRUE); +} + +static void +added_cb (ECompletionView *view, gpointer user_data) +{ + EEntry *entry = E_ENTRY (user_data); + e_entry_refresh_popup (entry); +} + +static void +full_cb (ECompletionView *view, gpointer user_data) +{ + EEntry *entry = E_ENTRY (user_data); + + e_entry_show_popup (entry, view->choice_count > 0); +} + +static void +browse_cb (ECompletionView *view, const gchar *txt, gpointer user_data) +{ + EEntry *entry = E_ENTRY (user_data); + + if (txt == NULL) { + /* Requesting a completion. */ + e_entry_start_completion (entry); + return; + } + + if (entry->priv->pre_browse_text == NULL) + entry->priv->pre_browse_text = g_strdup (e_entry_get_text (entry)); + + /* If there is no other handler in place, echo the selected completion in + the entry. */ + if (entry->priv->handler == NULL) + e_entry_set_text_quiet (entry, txt); +} + +static void +unbrowse_cb (ECompletionView *view, gpointer user_data) +{ + EEntry *entry = E_ENTRY (user_data); + + if (entry->priv->pre_browse_text) { + + if (entry->priv->handler == NULL) + e_entry_set_text_quiet (entry, entry->priv->pre_browse_text); + + g_free (entry->priv->pre_browse_text); + entry->priv->pre_browse_text = NULL; + } + + e_entry_show_popup (entry, FALSE); +} + +static void +activate_cb (ECompletionView *view, const gchar *txt, gpointer extra_data, gpointer user_data) +{ + EEntry *entry = E_ENTRY (user_data); + + e_entry_cancel_delayed_completion (entry); + + g_free (entry->priv->pre_browse_text); + entry->priv->pre_browse_text = NULL; + e_entry_show_popup (entry, FALSE); + + if (entry->priv->handler) + entry->priv->handler (entry, txt, extra_data); + else + e_entry_set_text (entry, txt); + + e_entry_cancel_delayed_completion (entry); +} + +void +e_entry_enable_completion (EEntry *entry, ECompletion *completion) +{ + g_return_if_fail (entry != NULL && E_IS_ENTRY (entry)); + g_return_if_fail (completion != NULL && E_IS_COMPLETION (completion)); + + e_entry_enable_completion_full (entry, completion, -1, NULL); +} + +void +e_entry_enable_completion_full (EEntry *entry, ECompletion *completion, gint delay, EEntryCompletionHandler handler) +{ + g_return_if_fail (entry != NULL && E_IS_ENTRY (entry)); + g_return_if_fail (completion != NULL && E_IS_COMPLETION (completion)); + + /* For now, completion can't be changed mid-stream. */ + g_return_if_fail (entry->priv->completion == NULL); - return GTK_WIDGET (e_entry); + entry->priv->completion = completion; + gtk_object_ref (GTK_OBJECT (completion)); + gtk_object_sink (GTK_OBJECT (completion)); + + entry->priv->completion_delay = delay; + entry->priv->handler = handler; + + entry->priv->completion_view = e_completion_view_new (completion); + /* Make the up and down keys enable and disable completions. */ + e_completion_view_set_complete_key (E_COMPLETION_VIEW (entry->priv->completion_view), GDK_Down); + e_completion_view_set_uncomplete_key (E_COMPLETION_VIEW (entry->priv->completion_view), GDK_Up); + + entry->priv->nonempty_signal_id = gtk_signal_connect (GTK_OBJECT (entry->priv->completion_view), + "nonempty", + GTK_SIGNAL_FUNC (nonempty_cb), + entry); + + entry->priv->added_signal_id = gtk_signal_connect (GTK_OBJECT (entry->priv->completion_view), + "added", + GTK_SIGNAL_FUNC (added_cb), + entry); + + entry->priv->full_signal_id = gtk_signal_connect (GTK_OBJECT (entry->priv->completion_view), + "full", + GTK_SIGNAL_FUNC (full_cb), + entry); + + entry->priv->browse_signal_id = gtk_signal_connect (GTK_OBJECT (entry->priv->completion_view), + "browse", + GTK_SIGNAL_FUNC (browse_cb), + entry); + + entry->priv->unbrowse_signal_id = gtk_signal_connect (GTK_OBJECT (entry->priv->completion_view), + "unbrowse", + GTK_SIGNAL_FUNC (unbrowse_cb), + entry); + + entry->priv->activate_signal_id = gtk_signal_connect (GTK_OBJECT (entry->priv->completion_view), + "activate", + GTK_SIGNAL_FUNC (activate_cb), + entry); + + entry->priv->completion_view_popup = gtk_window_new (GTK_WINDOW_POPUP); + gtk_object_ref (GTK_OBJECT (entry->priv->completion_view_popup)); + gtk_object_sink (GTK_OBJECT (entry->priv->completion_view_popup)); + gtk_window_set_policy (GTK_WINDOW (entry->priv->completion_view_popup), FALSE, TRUE, FALSE); + gtk_container_add (GTK_CONTAINER (entry->priv->completion_view_popup), entry->priv->completion_view); + gtk_widget_show (entry->priv->completion_view); + + e_completion_view_connect_keys (E_COMPLETION_VIEW (entry->priv->completion_view), GTK_WIDGET (entry->priv->canvas)); } + +/** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **/ + static void et_get_arg (GtkObject *o, GtkArg *arg, guint arg_id) { - EEntry *ee = E_ENTRY (o); - GtkObject *item = GTK_OBJECT (ee->item); + EEntry *entry = E_ENTRY (o); + GtkObject *item = GTK_OBJECT (entry->priv->item); switch (arg_id){ case ARG_MODEL: @@ -314,7 +733,7 @@ et_get_arg (GtkObject *o, GtkArg *arg, guint arg_id) break; case ARG_DRAW_BORDERS: - GTK_VALUE_BOOL (*arg) = ee->draw_borders; + GTK_VALUE_BOOL (*arg) = entry->priv->draw_borders; break; case ARG_DRAW_BACKGROUND: @@ -337,13 +756,13 @@ et_get_arg (GtkObject *o, GtkArg *arg, guint arg_id) static void et_set_arg (GtkObject *o, GtkArg *arg, guint arg_id) { - EEntry *ee = E_ENTRY (o); - GtkObject *item = GTK_OBJECT (ee->item); + EEntry *entry = E_ENTRY (o); + GtkObject *item = GTK_OBJECT (entry->priv->item); GtkAnchorType anchor; double width, height; gint xthick; gint ythick; - GtkWidget *widget = GTK_WIDGET(ee->canvas); + GtkWidget *widget = GTK_WIDGET(entry->priv->canvas); switch (arg_id){ case ARG_MODEL: @@ -384,13 +803,13 @@ et_set_arg (GtkObject *o, GtkArg *arg, guint arg_id) break; case ARG_JUSTIFICATION: - ee->justification = GTK_VALUE_ENUM (*arg); + entry->priv->justification = GTK_VALUE_ENUM (*arg); gtk_object_get(item, "clip_width", &width, "clip_height", &height, NULL); - if (ee->draw_borders) { + if (entry->priv->draw_borders) { xthick = 0; ythick = 0; } else { @@ -398,22 +817,22 @@ et_set_arg (GtkObject *o, GtkArg *arg, guint arg_id) ythick = widget->style->klass->ythickness; } - switch (ee->justification) { + switch (entry->priv->justification) { case GTK_JUSTIFY_CENTER: anchor = GTK_ANCHOR_N; - e_canvas_item_move_absolute(GNOME_CANVAS_ITEM(ee->item), width / 2, ythick); + e_canvas_item_move_absolute(GNOME_CANVAS_ITEM(entry->priv->item), width / 2, ythick); break; case GTK_JUSTIFY_RIGHT: anchor = GTK_ANCHOR_NE; - e_canvas_item_move_absolute(GNOME_CANVAS_ITEM(ee->item), width - xthick, ythick); + e_canvas_item_move_absolute(GNOME_CANVAS_ITEM(entry->priv->item), width - xthick, ythick); break; default: anchor = GTK_ANCHOR_NW; - e_canvas_item_move_absolute(GNOME_CANVAS_ITEM(ee->item), xthick, ythick); + e_canvas_item_move_absolute(GNOME_CANVAS_ITEM(entry->priv->item), xthick, ythick); break; } gtk_object_set(item, - "justification", ee->justification, + "justification", entry->priv->justification, "anchor", anchor, NULL); break; @@ -487,11 +906,11 @@ et_set_arg (GtkObject *o, GtkArg *arg, guint arg_id) case ARG_DRAW_BORDERS: { gboolean need_queue; - need_queue = (ee->draw_borders ^ GTK_VALUE_BOOL (*arg)); + need_queue = (entry->priv->draw_borders ^ GTK_VALUE_BOOL (*arg)); gtk_object_set (item, "draw_borders", GTK_VALUE_BOOL (*arg), NULL); - ee->draw_borders = GTK_VALUE_BOOL (*arg); + entry->priv->draw_borders = GTK_VALUE_BOOL (*arg); if (need_queue) - gtk_widget_queue_resize (GTK_WIDGET (ee)); + gtk_widget_queue_resize (GTK_WIDGET (entry)); break; } @@ -508,32 +927,50 @@ et_set_arg (GtkObject *o, GtkArg *arg, guint arg_id) } static void +e_entry_destroy (GtkObject *object) +{ + EEntry *entry = E_ENTRY (object); + + if (entry->priv->completion_delay_tag) + gtk_timeout_remove (entry->priv->completion_delay_tag); + + if (entry->priv->completion) + gtk_object_unref (GTK_OBJECT (entry->priv->completion)); + if (entry->priv->completion_view_popup) + gtk_widget_destroy (entry->priv->completion_view_popup); + g_free (entry->priv->pre_browse_text); + + g_free (entry->priv); + entry->priv = NULL; +} + +static void e_entry_class_init (GtkObjectClass *object_class) { EEntryClass *klass = E_ENTRY_CLASS(object_class); + parent_class = gtk_type_class (PARENT_TYPE); object_class->set_arg = et_set_arg; object_class->get_arg = et_get_arg; + object_class->destroy = e_entry_destroy; klass->changed = NULL; klass->activate = NULL; - e_entry_signals[E_ENTRY_CHANGED] = - gtk_signal_new ("changed", - GTK_RUN_LAST, - object_class->type, - GTK_SIGNAL_OFFSET (EEntryClass, changed), - gtk_marshal_NONE__NONE, - GTK_TYPE_NONE, 0); - - e_entry_signals[E_ENTRY_ACTIVATE] = - gtk_signal_new ("activate", - GTK_RUN_LAST, - object_class->type, - GTK_SIGNAL_OFFSET (EEntryClass, activate), - gtk_marshal_NONE__NONE, - GTK_TYPE_NONE, 0); + e_entry_signals[E_ENTRY_CHANGED] = gtk_signal_new ("changed", + GTK_RUN_LAST, + object_class->type, + GTK_SIGNAL_OFFSET (EEntryClass, changed), + gtk_marshal_NONE__NONE, + GTK_TYPE_NONE, 0); + + e_entry_signals[E_ENTRY_ACTIVATE] = gtk_signal_new ("activate", + GTK_RUN_LAST, + object_class->type, + GTK_SIGNAL_OFFSET (EEntryClass, activate), + gtk_marshal_NONE__NONE, + GTK_TYPE_NONE, 0); gtk_object_class_add_signals (object_class, e_entry_signals, E_ENTRY_LAST_SIGNAL); diff --git a/widgets/text/e-entry.h b/widgets/text/e-entry.h index 5d67946ce2..8b62ddcc29 100644 --- a/widgets/text/e-entry.h +++ b/widgets/text/e-entry.h @@ -1,11 +1,40 @@ /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ + +/* + * EEntry: An EText-based entry widget + * + * Authors: + * Miguel de Icaza <miguel@helixcode.com> + * Chris Lahey <clahey@helixcode.com> + * Jon Trowbridge <trow@ximian.com> + * + * Copyright (C) 1999, 2000, 2001 Ximian Inc. + */ + +/* + * 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 + */ + + #ifndef _E_ENTRY_H_ #define _E_ENTRY_H_ -#include <libgnomeui/gnome-canvas.h> #include <gtk/gtktable.h> #include <gnome-xml/tree.h> -#include <gal/e-text/e-text.h> +#include "e-completion.h" BEGIN_GNOME_DECLS @@ -15,27 +44,41 @@ BEGIN_GNOME_DECLS #define E_IS_ENTRY(o) (GTK_CHECK_TYPE ((o), E_ENTRY_TYPE)) #define E_IS_ENTRY_CLASS(k) (GTK_CHECK_CLASS_TYPE ((k), E_ENTRY_TYPE)) -typedef struct { - GtkTable parent; - - GnomeCanvas *canvas; - EText *item; - GtkJustification justification; +typedef struct _EEntry EEntry; +typedef struct _EEntryClass EEntryClass; +struct _EEntryPrivate; - guint draw_borders : 1; -} EEntry; +typedef void (*EEntryCompletionHandler) (EEntry *entry, const gchar *text, gpointer extra_data); -typedef struct { +struct _EEntry { + GtkTable parent; + struct _EEntryPrivate *priv; +}; + +struct _EEntryClass { GtkTableClass parent_class; void (* changed) (EEntry *entry); void (* activate) (EEntry *entry); -} EEntryClass; +}; + +GtkType e_entry_get_type (void); + +void e_entry_construct (EEntry *entry); +GtkWidget *e_entry_new (void); + +const gchar *e_entry_get_text (EEntry *entry); +void e_entry_set_text (EEntry *entry, const gchar *text); + +gint e_entry_get_position (EEntry *entry); +void e_entry_set_position (EEntry *entry, gint); +void e_entry_select_region (EEntry *entry, gint start, gint end); -GtkType e_entry_get_type (void); +void e_entry_set_editable (EEntry *entry, gboolean editable); -EEntry *e_entry_construct (EEntry *e_entry); -GtkWidget *e_entry_new (void); +void e_entry_enable_completion (EEntry *entry, ECompletion *completion); +void e_entry_enable_completion_full (EEntry *entry, ECompletion *completion, gint autocomplete_delay, + EEntryCompletionHandler handler); END_GNOME_DECLS diff --git a/widgets/text/e-text-model-repos.c b/widgets/text/e-text-model-repos.c new file mode 100644 index 0000000000..407a661b05 --- /dev/null +++ b/widgets/text/e-text-model-repos.c @@ -0,0 +1,73 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ + +/* Standard ETextModelReposFn definitions + * + * Copyright (C) 2001 Ximian Inc. + * + * Author: Jon Trowbridge <trow@ximian.com> + */ + +#include "e-text-model-repos.h" + +#define MODEL_CLAMP(model, pos) (CLAMP((pos), 0, strlen((model)->text))) + +gint +e_repos_shift (gint pos, gpointer data) +{ + EReposShift *info = (EReposShift *) data; + g_return_val_if_fail (data, -1); + + return e_text_model_validate_position (info->model, pos + info->change); +} + +gint +e_repos_absolute (gint pos, gpointer data) +{ + EReposAbsolute *info = (EReposAbsolute *) data; + g_return_val_if_fail (data, -1); + + pos = info->pos; + if (pos < 0) { + gint len = e_text_model_get_text_length (info->model); + pos += len + 1; + } + + return e_text_model_validate_position (info->model, pos); +} + +gint +e_repos_insert_shift (gint pos, gpointer data) +{ + EReposInsertShift *info = (EReposInsertShift *) data; + g_return_val_if_fail (data, -1); + + if (pos >= info->pos) + pos += info->len; + + return e_text_model_validate_position (info->model, pos); +} + +gint +e_repos_delete_shift (gint pos, gpointer data) +{ + EReposDeleteShift *info = (EReposDeleteShift *) data; + g_return_val_if_fail (data, -1); + + if (pos > info->pos + info->len) + pos -= info->len; + else if (pos > info->pos) + pos = info->pos; + + return e_text_model_validate_position (info->model, pos); +} + +gint +e_repos_clamp (gint pos, gpointer data) +{ + ETextModel *model; + + g_return_val_if_fail (data != NULL && E_IS_TEXT_MODEL (data), -1); + model = E_TEXT_MODEL (data); + + return e_text_model_validate_position (model, pos); +} diff --git a/widgets/text/e-text-model-repos.h b/widgets/text/e-text-model-repos.h new file mode 100644 index 0000000000..620e41a415 --- /dev/null +++ b/widgets/text/e-text-model-repos.h @@ -0,0 +1,55 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ + +/* Standard ETextModelReposFn definitions + * + * Copyright (C) 2001 Ximian Inc. + * + * Author: Jon Trowbridge <trow@ximian.com> + */ + +#ifndef E_TEXT_MODEL_REPOS_H +#define E_TEXT_MODEL_REPOS_H + +#include "e-text-model.h" + +typedef struct { + ETextModel *model; + gint change; /* Relative change to position. */ +} EReposShift; + +gint e_repos_shift (gint pos, gpointer data); + + +typedef struct { + ETextModel *model; + gint pos; /* Position to move to. Negative values count from the end buffer. + (i.e. -1 puts cursor at the end, -2 one character from end, etc.) */ +} EReposAbsolute; + +gint e_repos_absolute (gint pos, gpointer data); + + +typedef struct { + ETextModel *model; + gint pos; /* Location of first inserted character. */ + gint len; /* Number of characters inserted. */ +} EReposInsertShift; + +gint e_repos_insert_shift (gint pos, gpointer data); + + +typedef struct { + ETextModel *model; + gint pos; /* Location of first deleted character. */ + gint len; /* Number of characters deleted. */ +} EReposDeleteShift; + +gint e_repos_delete_shift (gint pos, gpointer data); + + +/* For e_repos_clamp, data is a pointer to an ETextModel. The only repositioning + that occurs is to avoid buffer overruns. */ + +gint e_repos_clamp (gint pos, gpointer data); + +#endif diff --git a/widgets/text/e-text-model-test.c b/widgets/text/e-text-model-test.c index 0b88552a05..9bf73962b0 100644 --- a/widgets/text/e-text-model-test.c +++ b/widgets/text/e-text-model-test.c @@ -19,50 +19,54 @@ describe_model (ETextModel *model) N = e_text_model_object_count (model); g_print ("text: %s\n", e_text_model_get_text (model)); - if (N > 0) { - gchar *s = e_text_model_strdup_expanded_text (model); - g_print ("expd: %s\n", s); - g_free (s); - } g_print ("objs: %d\n", N); - for (i=0; i<N; ++i) - g_print ("obj%d: %s\n", i, e_text_model_get_nth_object (model, i)); + for (i=0; i<N; ++i) { + gchar *s = e_text_model_strdup_nth_object (model, i); + g_print ("obj%d: %s\n", i, s); + g_free (s); + } } #endif int main (int argc, gchar **argv) { - GtkWidget *win, *canvas; - GnomeCanvasItem *item; + GtkWidget *win[2], *canvas[2]; + GnomeCanvasItem *item[2]; ETextModel *model; + gint i; gnome_init ("ETextModelTest", "0.0", argc, argv); model = e_text_model_uri_new (); - e_text_model_set_text (model, "My favorite website is http://www.ximian.com. My next favorite is http://www.gnome.org."); - win = gtk_window_new (GTK_WINDOW_TOPLEVEL); + e_text_model_set_text (model, "My favorite website is http://www.ximian.com. My next favorite www.assbarn.com."); + + // describe_model (model); + + for (i=0; i<2; ++i) { + win[i] = gtk_window_new (GTK_WINDOW_TOPLEVEL); - gtk_widget_push_visual (gdk_rgb_get_visual ()); - gtk_widget_push_colormap (gdk_rgb_get_cmap ()); - canvas = e_canvas_new (); - gtk_widget_pop_visual (); - gtk_widget_pop_colormap (); + gtk_widget_push_visual (gdk_rgb_get_visual ()); + gtk_widget_push_colormap (gdk_rgb_get_cmap ()); + canvas[i] = e_canvas_new (); + gtk_widget_pop_visual (); + gtk_widget_pop_colormap (); - item = gnome_canvas_item_new (gnome_canvas_root (GNOME_CANVAS (canvas)), - e_text_get_type (), - "model", model, - "font", "-adobe-helvetica-medium-r-normal--12-120-75-75-p-67-iso8859-1", - "anchor", GTK_ANCHOR_SOUTH_WEST, - "line_wrap", TRUE, - "width", 150.0, - "editable", TRUE, - NULL); + item[i] = gnome_canvas_item_new (gnome_canvas_root (GNOME_CANVAS (canvas[i])), + e_text_get_type (), + "model", model, + "font", "-adobe-helvetica-medium-r-normal--12-120-75-75-p-67-iso8859-1", + "anchor", GTK_ANCHOR_NORTH, + "line_wrap", TRUE, + "width", 150.0, + "editable", TRUE, + NULL); - gtk_container_add (GTK_CONTAINER (win), canvas); - gtk_widget_show_all (win); + gtk_container_add (GTK_CONTAINER (win[i]), canvas[i]); + gtk_widget_show_all (win[i]); + } gtk_main (); diff --git a/widgets/text/e-text-model-uri.c b/widgets/text/e-text-model-uri.c index bd8dbb18e8..55b293016f 100644 --- a/widgets/text/e-text-model-uri.c +++ b/widgets/text/e-text-model-uri.c @@ -9,6 +9,8 @@ #include <config.h> #include <ctype.h> +#include <sys/types.h> +#include <regex.h> #include "e-text-model-uri.h" static void e_text_model_uri_class_init (ETextModelURIClass *class); @@ -17,10 +19,17 @@ static void e_text_model_uri_destroy (GtkObject *object); static void objectify_uris (ETextModelURI *model); -static void e_text_model_uri_set_text (ETextModel *model, gchar *text); -static const gchar *e_text_model_uri_get_nth_object (ETextModel *model, gint); +static void e_text_model_uri_objectify (ETextModel *model); +static gint e_text_model_uri_validate_pos (ETextModel *model, gint pos); +static gint e_text_model_uri_get_obj_count (ETextModel *model); +static const gchar *e_text_model_uri_get_nth_object (ETextModel *model, gint i, gint *len); static void e_text_model_uri_activate_nth_object (ETextModel *model, gint); +typedef struct _ObjInfo ObjInfo; +struct _ObjInfo { + gint offset, len; +}; + static GtkObject *parent_class; GtkType @@ -59,9 +68,13 @@ e_text_model_uri_class_init (ETextModelURIClass *klass) object_class->destroy = e_text_model_uri_destroy; - model_class->set_text = e_text_model_uri_set_text; + model_class->object_activated = e_text_model_uri_activate_nth_object; + + model_class->objectify = e_text_model_uri_objectify; + model_class->validate_pos = e_text_model_uri_validate_pos; + model_class->obj_count = e_text_model_uri_get_obj_count; model_class->get_nth_obj = e_text_model_uri_get_nth_object; - model_class->activate_nth_obj = e_text_model_uri_activate_nth_object; + } static void @@ -73,95 +86,249 @@ e_text_model_uri_init (ETextModelURI *model) static void e_text_model_uri_destroy (GtkObject *object) { + ETextModelURI *model_uri = E_TEXT_MODEL_URI (object); + GList *iter; + + if (model_uri->objectify_idle) { + gtk_idle_remove (model_uri->objectify_idle); + model_uri->objectify_idle = 0; + } + + for (iter = model_uri->uris; iter != NULL; iter = g_list_next (iter)) + g_free (iter->data); + g_list_free (model_uri->uris); + model_uri->uris = NULL; + + if (GTK_OBJECT_CLASS (parent_class)->destroy) (* GTK_OBJECT_CLASS (parent_class)->destroy) (object); } -static gchar * -extract_uri (gchar **in_str) -{ - gchar *s = *in_str; - if (strncmp (s, "http://", 7) == 0) { - gint periods=0; - gchar *uri; +static const gchar *uri_regex[] = { + "(((news|telnet|nttp|file|http|ftp|https)://)|(www|ftp))[-A-Za-z0-9\\.]+(:[0-9]*)?/[-A-Za-z0-9_\\$\\.\\+\\!\\*\\(\\),;:@&=\\?/~\\#\\%]*[^]'\\.}>\\) ,\\\"]", + "(((news|telnet|nttp|file|http|ftp|https)://)|(www|ftp))[-A-Za-z0-9\\.]+[-A-Za-z0-9](:[0-9]*)?", + "mailto:[A-Za-z0-9_]+@[-A-Za-z0-9_]+\\.[-A-Za-z0-9\\.]+[-A-Za-z0-9]", + NULL +}; +static gint regex_count = 0; +static regex_t *regex_compiled = NULL; - s += 7; +static void +regex_init (void) +{ + gint i; - while (*s && (isalnum((gint) *s) || (*s == '.' && periods < 2))) { - if (*s == '.') - ++periods; - ++s; - } + if (regex_count != 0) + return; - uri = g_strndup (*in_str, s - *in_str); - *in_str = s; - return uri; + while (uri_regex[regex_count]) ++regex_count; - } else { - *in_str = s+1; - return NULL; + regex_compiled = g_new0 (regex_t, regex_count); + + for (i=0; i<regex_count; ++i) { + if (regcomp (®ex_compiled[i], uri_regex[i], REG_EXTENDED)) + g_error ("Bad regex?: %s", uri_regex[i]); } } + static void objectify_uris (ETextModelURI *model_uri) { - ETextModel *model = E_TEXT_MODEL (model_uri); - gchar *new_text; - gchar *src, *dest; - GList *uris = NULL; + static gboolean objectifying = FALSE; - if (model->text == NULL) + ETextModel *model = E_TEXT_MODEL (model_uri); + const gchar *txt; + GList *iter, *old_uris; + gint offset, len; + gboolean found_match; + regmatch_t match; + gboolean changed; + + if (objectifying) return; - new_text = g_new0 (gchar, strlen (model->text)+1); + objectifying = TRUE; + + if (regex_count == 0) + regex_init (); + + txt = e_text_model_get_text (model); + len = e_text_model_get_text_length (model); + + old_uris = model_uri->uris; + model_uri->uris = NULL; + + if (txt) { + offset = 0; + found_match = TRUE; + + while (offset < len && found_match) { + + gint i, so=-1, eo=-1; + + found_match = FALSE; + + for (i=0; i<regex_count; ++i) { + + if (regexec (®ex_compiled[i], txt+offset, 1, &match, 0) == 0) { + + /* Take earliest match possible. In case of a tie, take the + largest possible match. */ + if (!found_match + || match.rm_so < so + || (match.rm_so == so && match.rm_eo > eo)) { + so = match.rm_so; + eo = match.rm_eo; + } + found_match = TRUE; + } + } + + if (found_match) { + + ObjInfo *info = g_new0 (ObjInfo, 1); + info->offset = offset + so; + info->len = eo - so; + + model_uri->uris = g_list_append (model_uri->uris, info); + + offset += eo; + } + } + } + + changed = (g_list_length (old_uris) != g_list_length (model_uri->uris)); - src = model->text; - dest = new_text; + if (!changed) { + /* Check that there is a 1-1 correspondence between object positions. */ + GList *jter; - while (*src) { - gchar *uri_str; - gchar *next = src; - if ( (uri_str = extract_uri (&next)) ) { - uris = g_list_append (uris, uri_str); - *dest = '\1'; - } else { - *dest = *src; + for (iter = model_uri->uris; iter != NULL && !changed; iter = g_list_next (iter)) { + ObjInfo *info = (ObjInfo *) iter->data; + found_match = FALSE; + for (jter = old_uris; jter != NULL && !found_match; jter = g_list_next (jter)) { + ObjInfo *jnfo = (ObjInfo *) jter->data; + + if (info->offset == jnfo->offset && info->len == jnfo->len) + found_match = TRUE; + } + changed = !found_match; } - ++dest; - src = next; } - g_free (model->text); - model->text = new_text; + if (changed) + e_text_model_changed (model); + + /* Free old uris */ + for (iter = old_uris; iter != NULL; iter = g_list_next (iter)) + g_free (iter->data); + g_list_free (old_uris); + + objectifying = FALSE; +} + +static gboolean +objectify_idle_cb (gpointer ptr) +{ + ETextModelURI *model_uri = E_TEXT_MODEL_URI (ptr); + + g_assert (model_uri->objectify_idle); + objectify_uris (model_uri); + model_uri->objectify_idle = 0; + + return FALSE; +} + +static void +e_text_model_uri_objectify (ETextModel *model) +{ + ETextModelURI *model_uri = E_TEXT_MODEL_URI (model); + + if (model_uri->objectify_idle == 0) + model_uri->objectify_idle = gtk_idle_add (objectify_idle_cb, model); - /* Leaking old list */ - model_uri->uris = uris; + if (E_TEXT_MODEL_CLASS(parent_class)->objectify) + E_TEXT_MODEL_CLASS(parent_class)->objectify (model); } static void -e_text_model_uri_set_text (ETextModel *model, gchar *text) +objectify_idle_flush (ETextModelURI *model_uri) +{ + if (model_uri->objectify_idle) { + gtk_idle_remove (model_uri->objectify_idle); + model_uri->objectify_idle = 0; + objectify_uris (model_uri); + } +} + +static gint +e_text_model_uri_validate_pos (ETextModel *model, gint pos) +{ + gint obj_num; + + /* Cause us to skip over objects */ + + obj_num = e_text_model_get_object_at_offset (model, pos); + if (obj_num != -1) { + gint pos0, pos1, mp; + e_text_model_get_nth_object_bounds (model, obj_num, &pos0, &pos1); + mp = (pos0 + pos1)/2; + if (pos0 < pos && pos < mp) + pos = pos1; + else if (mp <= pos && pos < pos1) + pos = pos0; + } + + + + if (E_TEXT_MODEL_CLASS (parent_class)->validate_pos) + pos = E_TEXT_MODEL_CLASS (parent_class)->validate_pos (model, pos); + + return pos; +} + +static gint +e_text_model_uri_get_obj_count (ETextModel *model) { - if (E_TEXT_MODEL_CLASS(parent_class)->set_text) - E_TEXT_MODEL_CLASS(parent_class)->set_text (model, text); + ETextModelURI *model_uri = E_TEXT_MODEL_URI (model); + + objectify_idle_flush (model_uri); - objectify_uris (E_TEXT_MODEL_URI (model)); + return g_list_length (model_uri->uris); } static const gchar * -e_text_model_uri_get_nth_object (ETextModel *model, gint i) +e_text_model_uri_get_nth_object (ETextModel *model, gint i, gint *len) { - return (const gchar *) g_list_nth_data (E_TEXT_MODEL_URI (model)->uris, i); + ETextModelURI *model_uri = E_TEXT_MODEL_URI (model); + ObjInfo *info; + const gchar *txt; + + objectify_idle_flush (model_uri); + + txt = e_text_model_get_text (model); + + info = (ObjInfo *) g_list_nth_data (model_uri->uris, i); + g_return_val_if_fail (info != NULL, NULL); + + + if (len) + *len = info->len; + return txt + info->offset; } static void e_text_model_uri_activate_nth_object (ETextModel *model, gint i) { - const gchar *obj_str; + gchar *obj_str; - obj_str = e_text_model_get_nth_object (model, i); + objectify_idle_flush (E_TEXT_MODEL_URI (model)); + + obj_str = e_text_model_strdup_nth_object (model, i); gnome_url_show (obj_str); + g_free (obj_str); } ETextModel * diff --git a/widgets/text/e-text-model-uri.h b/widgets/text/e-text-model-uri.h index 293c9151e8..d070e34f53 100644 --- a/widgets/text/e-text-model-uri.h +++ b/widgets/text/e-text-model-uri.h @@ -25,8 +25,10 @@ typedef struct _ETextModelURI ETextModelURI; typedef struct _ETextModelURIClass ETextModelURIClass; struct _ETextModelURI { - ETextModel item; - GList *uris; + ETextModel item; + GList *uris; + + guint objectify_idle; }; struct _ETextModelURIClass { diff --git a/widgets/text/e-text-model.c b/widgets/text/e-text-model.c index acd08ecb04..5eccaff9fb 100644 --- a/widgets/text/e-text-model.c +++ b/widgets/text/e-text-model.c @@ -16,31 +16,40 @@ * * Author: Federico Mena <federico@nuclecu.unam.mx> */ +#undef PARANOID_DEBUGGING + #include <config.h> #include <ctype.h> +#include "e-text-model-repos.h" #include "e-text-model.h" +#define CLASS(obj) (E_TEXT_MODEL_CLASS (GTK_OBJECT (obj)->klass)) + enum { E_TEXT_MODEL_CHANGED, + E_TEXT_MODEL_REPOSITION, + E_TEXT_MODEL_OBJECT_ACTIVATED, E_TEXT_MODEL_LAST_SIGNAL }; static guint e_text_model_signals[E_TEXT_MODEL_LAST_SIGNAL] = { 0 }; -static void e_text_model_class_init (ETextModelClass *class); -static void e_text_model_init (ETextModel *model); -static void e_text_model_destroy (GtkObject *object); - -static gchar *e_text_model_real_get_text(ETextModel *model); -static void e_text_model_real_set_text(ETextModel *model, gchar *text); -static void e_text_model_real_insert(ETextModel *model, gint postion, gchar *text); -static void e_text_model_real_insert_length(ETextModel *model, gint postion, gchar *text, gint length); -static void e_text_model_real_delete(ETextModel *model, gint postion, gint length); +struct _ETextModelPrivate { + gchar *text; + gint len; +}; -static gint e_text_model_real_object_count(ETextModel *model); -static const gchar *e_text_model_real_get_nth_object(ETextModel *model, gint n); -static void e_text_model_real_activate_nth_object(ETextModel *mode, gint n); +static void e_text_model_class_init (ETextModelClass *class); +static void e_text_model_init (ETextModel *model); +static void e_text_model_destroy (GtkObject *object); +static gint e_text_model_real_validate_position (ETextModel *, gint pos); +static const gchar *e_text_model_real_get_text (ETextModel *model); +static gint e_text_model_real_get_text_length (ETextModel *model); +static void e_text_model_real_set_text (ETextModel *model, const gchar *text); +static void e_text_model_real_insert (ETextModel *model, gint postion, const gchar *text); +static void e_text_model_real_insert_length (ETextModel *model, gint postion, const gchar *text, gint length); +static void e_text_model_real_delete (ETextModel *model, gint postion, gint length); static GtkObject *parent_class; @@ -95,18 +104,45 @@ e_text_model_class_init (ETextModelClass *klass) GTK_SIGNAL_OFFSET (ETextModelClass, changed), gtk_marshal_NONE__NONE, GTK_TYPE_NONE, 0); + + e_text_model_signals[E_TEXT_MODEL_REPOSITION] = + gtk_signal_new ("reposition", + GTK_RUN_LAST, + object_class->type, + GTK_SIGNAL_OFFSET (ETextModelClass, reposition), + gtk_marshal_NONE__POINTER_POINTER, + GTK_TYPE_NONE, 2, + GTK_TYPE_POINTER, GTK_TYPE_POINTER); + + e_text_model_signals[E_TEXT_MODEL_OBJECT_ACTIVATED] = + gtk_signal_new ("object_activated", + GTK_RUN_LAST, + object_class->type, + GTK_SIGNAL_OFFSET (ETextModelClass, object_activated), + gtk_marshal_NONE__INT, + GTK_TYPE_NONE, 1, + GTK_TYPE_INT); gtk_object_class_add_signals (object_class, e_text_model_signals, E_TEXT_MODEL_LAST_SIGNAL); - klass->changed = NULL; - klass->get_text = e_text_model_real_get_text; - klass->set_text = e_text_model_real_set_text; - klass->insert = e_text_model_real_insert; + /* No default signal handlers. */ + klass->changed = NULL; + klass->reposition = NULL; + klass->object_activated = NULL; + + klass->validate_pos = e_text_model_real_validate_position; + + klass->get_text = e_text_model_real_get_text; + klass->get_text_len = e_text_model_real_get_text_length; + klass->set_text = e_text_model_real_set_text; + klass->insert = e_text_model_real_insert; klass->insert_length = e_text_model_real_insert_length; - klass->delete = e_text_model_real_delete; - klass->obj_count = e_text_model_real_object_count; - klass->get_nth_obj = e_text_model_real_get_nth_object; - klass->activate_nth_obj = e_text_model_real_activate_nth_object; + klass->delete = e_text_model_real_delete; + + /* We explicitly don't define default handlers for these. */ + klass->objectify = NULL; + klass->obj_count = NULL; + klass->get_nth_obj = NULL; object_class->destroy = e_text_model_destroy; } @@ -115,7 +151,9 @@ e_text_model_class_init (ETextModelClass *klass) static void e_text_model_init (ETextModel *model) { - model->text = NULL; + model->priv = g_new0 (struct _ETextModelPrivate, 1); + model->priv->text = g_strdup (""); + model->priv->len = 0; } /* Destroy handler for the text item */ @@ -129,250 +167,424 @@ e_text_model_destroy (GtkObject *object) model = E_TEXT_MODEL (object); - if (model->text) - g_free (model->text); + g_free (model->priv->text); + + g_free (model->priv); + model->priv = NULL; if (GTK_OBJECT_CLASS (parent_class)->destroy) - (* GTK_OBJECT_CLASS (parent_class)->destroy) (object); + GTK_OBJECT_CLASS (parent_class)->destroy (object); } -static gchar * -e_text_model_real_get_text(ETextModel *model) +static gint +e_text_model_real_validate_position (ETextModel *model, gint pos) { - if (model->text) - return model->text; + gint len; + + if (pos < 0) + pos = 0; + else if (pos > ( len = e_text_model_get_text_length (model) )) + pos = len; + + return pos; +} + +static const gchar * +e_text_model_real_get_text (ETextModel *model) +{ + if (model->priv->text) + return model->priv->text; else return ""; } -static void -e_text_model_real_set_text(ETextModel *model, gchar *text) +static gint +e_text_model_real_get_text_length (ETextModel *model) { - if (model->text) - g_free(model->text); - model->text = g_strdup(text); - e_text_model_changed(model); + if (model->priv->len < 0) + model->priv->len = strlen (e_text_model_get_text (model)); + + return model->priv->len; } static void -e_text_model_real_insert(ETextModel *model, gint position, gchar *text) +e_text_model_real_set_text (ETextModel *model, const gchar *text) { - gchar *temp = g_strdup_printf("%.*s%s%s", position, model->text, text, model->text + position); - if (model->text) - g_free(model->text); - model->text = temp; - e_text_model_changed(model); + EReposAbsolute repos; + gboolean changed = FALSE; + + if (text == NULL) { + + changed = (model->priv->text != NULL); + + g_free (model->priv->text); + model->priv->text = NULL; + model->priv->len = -1; + + } else if (model->priv->text == NULL || strcmp (model->priv->text, text)) { + + g_free (model->priv->text); + model->priv->text = g_strdup (text); + model->priv->len = -1; + + changed = TRUE; + } + + if (changed) { + e_text_model_changed (model); + repos.model = model; + repos.pos = -1; + e_text_model_reposition (model, e_repos_absolute, &repos); + } } static void -e_text_model_real_insert_length(ETextModel *model, gint position, gchar *text, gint length) +e_text_model_real_insert (ETextModel *model, gint position, const gchar *text) { - gchar *temp = g_strdup_printf("%.*s%.*s%s", position, model->text, length, text, model->text + position); - if (model->text) - g_free(model->text); - model->text = temp; - e_text_model_changed(model); + EReposInsertShift repos; + gchar *temp; + gint ins_len; + + temp = g_strdup_printf ("%.*s%s%s", position, model->priv->text, text, model->priv->text + position); + ins_len = strlen (text); + + if (model->priv->text) + g_free (model->priv->text); + + model->priv->text = temp; + + if (model->priv->len >= 0) + model->priv->len += ins_len; + + e_text_model_changed (model); + + repos.model = model; + repos.pos = position; + repos.len = ins_len; + + e_text_model_reposition (model, e_repos_insert_shift, &repos); } static void -e_text_model_real_delete(ETextModel *model, gint position, gint length) +e_text_model_real_insert_length (ETextModel *model, gint position, const gchar *text, gint length) { - memmove(model->text + position, model->text + position + length, strlen(model->text + position + length) + 1); - e_text_model_changed(model); -} + EReposInsertShift repos; + gchar *temp = g_strdup_printf ("%.*s%.*s%s", position, model->priv->text, length, text, model->priv->text + position); -static gint -e_text_model_real_object_count(ETextModel *model) -{ - gint count = 0; - gchar *c = model->text; - - if (c) { - while (*c) { - if (*c == '\1') - ++count; - ++c; - } - } - return count; + if (model->priv->text) + g_free (model->priv->text); + model->priv->text = temp; + + if (model->priv->len >= 0) + model->priv->len += length; + + e_text_model_changed (model); + + repos.model = model; + repos.pos = position; + repos.len = length; + + e_text_model_reposition (model, e_repos_insert_shift, &repos); } -static const gchar * -e_text_model_real_get_nth_object(ETextModel *model, gint n) +static void +e_text_model_real_delete (ETextModel *model, gint position, gint length) { - return ""; + EReposDeleteShift repos; + + memmove (model->priv->text + position, model->priv->text + position + length, strlen (model->priv->text + position + length) + 1); + + if (model->priv->len >= 0) + model->priv->len -= length; + + e_text_model_changed (model); + + repos.model = model; + repos.pos = position; + repos.len = length; + + e_text_model_reposition (model, e_repos_delete_shift, &repos); } -static void -e_text_model_real_activate_nth_object(ETextModel *model, gint n) +void +e_text_model_changed (ETextModel *model) { - /* By default, do nothing */ + g_return_if_fail (model != NULL); + g_return_if_fail (E_IS_TEXT_MODEL (model)); + + /* + Objectify before emitting any signal. + While this method could, in theory, do pretty much anything, it is meant + for scanning objects and converting substrings into embedded objects. + */ + if (CLASS (model)->objectify) + CLASS (model)->objectify (model); + + gtk_signal_emit (GTK_OBJECT (model), + e_text_model_signals[E_TEXT_MODEL_CHANGED]); } void -e_text_model_changed(ETextModel *model) +e_text_model_reposition (ETextModel *model, ETextModelReposFn fn, gpointer repos_data) { g_return_if_fail (model != NULL); g_return_if_fail (E_IS_TEXT_MODEL (model)); + g_return_if_fail (fn != NULL); gtk_signal_emit (GTK_OBJECT (model), - e_text_model_signals [E_TEXT_MODEL_CHANGED]); + e_text_model_signals[E_TEXT_MODEL_REPOSITION], + fn, repos_data); } -gchar * -e_text_model_get_text(ETextModel *model) +gint +e_text_model_validate_position (ETextModel *model, gint pos) +{ + g_return_val_if_fail (model != NULL, 0); + g_return_val_if_fail (E_IS_TEXT_MODEL (model), 0); + + if (CLASS (model)->validate_pos) + pos = CLASS (model)->validate_pos (model, pos); + + return pos; +} + +const gchar * +e_text_model_get_text (ETextModel *model) { g_return_val_if_fail (model != NULL, NULL); g_return_val_if_fail (E_IS_TEXT_MODEL (model), NULL); - if ( E_TEXT_MODEL_CLASS(GTK_OBJECT(model)->klass)->get_text ) - return E_TEXT_MODEL_CLASS(GTK_OBJECT(model)->klass)->get_text(model); - else - return ""; + if (CLASS (model)->get_text) + return CLASS (model)->get_text (model); + + return ""; +} + +gint +e_text_model_get_text_length (ETextModel *model) +{ + g_return_val_if_fail (model != NULL, 0); + g_return_val_if_fail (E_IS_TEXT_MODEL (model), 0); + + if (CLASS (model)->get_text_len (model)) { + + gint len = CLASS (model)->get_text_len (model); + +#ifdef PARANOID_DEBUGGING + const gchar *str = e_text_model_get_text (model); + gint len2 = str ? strlen (str) : 0; + if (len != len) + g_error ("\"%s\" length reported as %d, not %d.", str, len, len2); +#endif + + return len; + + } else { + /* Calculate length the old-fashioned way... */ + const gchar *str = e_text_model_get_text (model); + return str ? strlen (str) : 0; + } +} + +void +e_text_model_set_text (ETextModel *model, const gchar *text) +{ + g_return_if_fail (model != NULL); + g_return_if_fail (E_IS_TEXT_MODEL (model)); + + if (CLASS (model)->set_text) + CLASS (model)->set_text (model, text); +} + +void +e_text_model_insert (ETextModel *model, gint position, const gchar *text) +{ + g_return_if_fail (model != NULL); + g_return_if_fail (E_IS_TEXT_MODEL (model)); + + if (text == NULL) + return; + + if (CLASS (model)->insert) + CLASS (model)->insert (model, position, text); } void -e_text_model_set_text(ETextModel *model, gchar *text) +e_text_model_insert_length (ETextModel *model, gint position, const gchar *text, gint length) { g_return_if_fail (model != NULL); g_return_if_fail (E_IS_TEXT_MODEL (model)); + g_return_if_fail (length >= 0); + + + if (text == NULL || length == 0) + return; - if ( E_TEXT_MODEL_CLASS(GTK_OBJECT(model)->klass)->set_text ) - E_TEXT_MODEL_CLASS(GTK_OBJECT(model)->klass)->set_text(model, text); + if (CLASS (model)->insert_length) + CLASS (model)->insert_length (model, position, text, length); } void -e_text_model_insert(ETextModel *model, gint position, gchar *text) +e_text_model_prepend (ETextModel *model, const gchar *text) { g_return_if_fail (model != NULL); g_return_if_fail (E_IS_TEXT_MODEL (model)); - if ( E_TEXT_MODEL_CLASS(GTK_OBJECT(model)->klass)->insert ) - E_TEXT_MODEL_CLASS(GTK_OBJECT(model)->klass)->insert(model, position, text); + if (text == NULL) + return; + + e_text_model_insert (model, 0, text); } void -e_text_model_insert_length(ETextModel *model, gint position, gchar *text, gint length) +e_text_model_append (ETextModel *model, const gchar *text) { g_return_if_fail (model != NULL); g_return_if_fail (E_IS_TEXT_MODEL (model)); - if ( E_TEXT_MODEL_CLASS(GTK_OBJECT(model)->klass)->insert_length ) - E_TEXT_MODEL_CLASS(GTK_OBJECT(model)->klass)->insert_length(model, position, text, length); + if (text == NULL) + return; + + e_text_model_insert (model, e_text_model_get_text_length (model), text); } void -e_text_model_delete(ETextModel *model, gint position, gint length) +e_text_model_delete (ETextModel *model, gint position, gint length) { + gint txt_len; + g_return_if_fail (model != NULL); g_return_if_fail (E_IS_TEXT_MODEL (model)); + g_return_if_fail (length >= 0); + + txt_len = e_text_model_get_text_length (model); + if (position + length > txt_len) + length = txt_len - position; + + if (length <= 0) + return; - if ( E_TEXT_MODEL_CLASS(GTK_OBJECT(model)->klass)->delete ) - E_TEXT_MODEL_CLASS(GTK_OBJECT(model)->klass)->delete(model, position, length); + if (CLASS (model)->delete) + CLASS (model)->delete (model, position, length); } gint -e_text_model_object_count(ETextModel *model) +e_text_model_object_count (ETextModel *model) { g_return_val_if_fail (model != NULL, 0); g_return_val_if_fail (E_IS_TEXT_MODEL (model), 0); - if ( E_TEXT_MODEL_CLASS(GTK_OBJECT(model)->klass)->obj_count) - return E_TEXT_MODEL_CLASS(GTK_OBJECT(model)->klass)->obj_count(model); - else - return 0; + if (CLASS (model)->obj_count) + return CLASS (model)->obj_count (model); + + return 0; } const gchar * -e_text_model_get_nth_object(ETextModel *model, gint n) +e_text_model_get_nth_object (ETextModel *model, gint n, gint *len) { g_return_val_if_fail (model != NULL, NULL); g_return_val_if_fail (E_IS_TEXT_MODEL (model), NULL); - g_return_val_if_fail (n >= 0, NULL); - if ( E_TEXT_MODEL_CLASS(GTK_OBJECT(model)->klass)->get_nth_obj ) - return E_TEXT_MODEL_CLASS(GTK_OBJECT(model)->klass)->get_nth_obj(model, n); - else - return ""; -} + if (n < 0 || n >= e_text_model_object_count (model)) + return NULL; -void -e_text_model_activate_nth_object(ETextModel *model, gint n) -{ - g_return_if_fail (model != NULL); - g_return_if_fail (E_IS_TEXT_MODEL (model)); - g_return_if_fail (n >= 0); + if (CLASS (model)->get_nth_obj) + return CLASS (model)->get_nth_obj (model, n, len); - if ( E_TEXT_MODEL_CLASS(GTK_OBJECT(model)->klass)->activate_nth_obj ) - E_TEXT_MODEL_CLASS(GTK_OBJECT(model)->klass)->activate_nth_obj(model, n); + return NULL; } gchar * -e_text_model_strdup_expanded_text(ETextModel *model) +e_text_model_strdup_nth_object (ETextModel *model, gint n) { - gint len = 0, i, N; - gchar *expanded, *dest; - const gchar *src; + const gchar *obj; + gint len = 0; g_return_val_if_fail (model != NULL, NULL); g_return_val_if_fail (E_IS_TEXT_MODEL (model), NULL); - if (model->text == NULL) - return NULL; + obj = e_text_model_get_nth_object (model, n, &len); + + return obj ? g_strndup (obj, n) : NULL; +} - N = e_text_model_object_count (model); - if (N == 0) - return g_strdup (model->text); +void +e_text_model_get_nth_object_bounds (ETextModel *model, gint n, gint *start, gint *end) +{ + const gchar *txt = NULL, *obj = NULL; + gint len = 0; - /* First, compute the length of the expanded string. */ + g_return_if_fail (model != NULL); + g_return_if_fail (E_IS_TEXT_MODEL (model)); - len = strlen (model->text); - len -= N; /* Subtract out the \1s that signify the objects. */ + txt = e_text_model_get_text (model); + obj = e_text_model_get_nth_object (model, n, &len); - for (i=0; i<N; ++i) - len += strlen (e_text_model_get_nth_object (model ,i)); - + g_return_if_fail (obj != NULL); - /* Next, allocate and build the expanded string. */ - expanded = g_new0 (gchar, len+1); + if (start) + *start = obj - txt; + if (end) + *end = obj - txt + len; +} - src = model->text; - dest = expanded; - i = 0; - while (*src) { - if (*src == '\1') { - const gchar *src_obj; - - g_assert (i < N); - src_obj = e_text_model_get_nth_object (model, i); - - if (src_obj) { - while (*src_obj) { - *dest = *src_obj; - ++src_obj; - ++dest; - } - } - - ++src; - ++i; +gint +e_text_model_get_object_at_offset (ETextModel *model, gint offset) +{ + g_return_val_if_fail (model != NULL, -1); + g_return_val_if_fail (E_IS_TEXT_MODEL (model), -1); + + if (offset < 0 || offset >= e_text_model_get_text_length (model)) + return -1; + + /* If an optimized version has been provided, we use it. */ + if (CLASS (model)->obj_at_offset) { + + return CLASS (model)->obj_at_offset (model, offset); + + } else { + /* If not, we fake it.*/ - } else { + gint i, N, pos0, pos1; - *dest = *src; - ++src; - ++dest; + N = e_text_model_object_count (model); + for (i = 0; i < N; ++i) { + e_text_model_get_nth_object_bounds (model, i, &pos0, &pos1); + if (pos0 <= offset && offset < pos1) + return i; } + } - return expanded; + return -1; +} + +gint +e_text_model_get_object_at_pointer (ETextModel *model, const gchar *s) +{ + g_return_val_if_fail (model != NULL, -1); + g_return_val_if_fail (E_IS_TEXT_MODEL (model), -1); + g_return_val_if_fail (s != NULL, -1); + + return e_text_model_get_object_at_offset (model, s - e_text_model_get_text (model)); +} + +void +e_text_model_activate_nth_object (ETextModel *model, gint n) +{ + g_return_if_fail (model != NULL); + g_return_if_fail (E_IS_TEXT_MODEL (model)); + g_return_if_fail (n >= 0); + g_return_if_fail (n < e_text_model_object_count (model)); + + gtk_signal_emit (GTK_OBJECT (model), e_text_model_signals[E_TEXT_MODEL_OBJECT_ACTIVATED], n); } ETextModel * -e_text_model_new(void) +e_text_model_new (void) { ETextModel *model = gtk_type_new (e_text_model_get_type ()); - model->text = g_strdup(""); return model; } diff --git a/widgets/text/e-text-model.h b/widgets/text/e-text-model.h index 7006b24eca..afd9b79960 100644 --- a/widgets/text/e-text-model.h +++ b/widgets/text/e-text-model.h @@ -16,6 +16,7 @@ * * Author: Federico Mena <federico@nuclecu.unam.mx> */ + #ifndef E_TEXT_MODEL_H #define E_TEXT_MODEL_H @@ -33,46 +34,78 @@ BEGIN_GNOME_DECLS typedef struct _ETextModel ETextModel; typedef struct _ETextModelClass ETextModelClass; +struct _ETextModelPrivate; + +typedef gint (*ETextModelReposFn) (gint, gpointer); + struct _ETextModel { GtkObject item; - char *text; /* Text to display */ + struct _ETextModelPrivate *priv; }; struct _ETextModelClass { GtkObjectClass parent_class; /* Signal */ - void (* changed) (ETextModel *model); + void (* changed) (ETextModel *model); + void (* reposition) (ETextModel *model, ETextModelReposFn fn, gpointer repos_fn_data); + void (* object_activated) (ETextModel *model, gint obj_num); /* Virtual methods */ - char *(* get_text) (ETextModel *model); - void (* set_text) (ETextModel *model, gchar *text); - void (* insert) (ETextModel *model, gint position, gchar *text); - void (* insert_length) (ETextModel *model, gint position, gchar *text, gint length); - void (* delete) (ETextModel *model, gint position, gint length); - gint (* obj_count) (ETextModel *model); - const gchar *(* get_nth_obj) (ETextModel *model, gint n); - void (* activate_nth_obj) (ETextModel *model, gint n); + + gint (* validate_pos) (ETextModel *model, gint pos); + + const char *(* get_text) (ETextModel *model); + gint (* get_text_len) (ETextModel *model); + void (* set_text) (ETextModel *model, const gchar *text); + void (* insert) (ETextModel *model, gint position, const gchar *text); + void (* insert_length) (ETextModel *model, gint position, const gchar *text, gint length); + void (* delete) (ETextModel *model, gint position, gint length); + + void (* objectify) (ETextModel *model); + gint (* obj_count) (ETextModel *model); + const gchar *(* get_nth_obj) (ETextModel *model, gint n, gint *len); + gint (* obj_at_offset) (ETextModel *model, gint offset); }; +GtkType e_text_model_get_type (void); + +ETextModel *e_text_model_new (void); + +void e_text_model_changed (ETextModel *model); + +void e_text_model_reposition (ETextModel *model, ETextModelReposFn fn, gpointer repos_data); +gint e_text_model_validate_position (ETextModel *model, gint pos); + + +/* Functions for manipulating the underlying text. */ + +const gchar *e_text_model_get_text (ETextModel *model); +gint e_text_model_get_text_length (ETextModel *model); +void e_text_model_set_text (ETextModel *model, const gchar *text); +void e_text_model_insert (ETextModel *model, gint position, const gchar *text); +void e_text_model_insert_length (ETextModel *model, gint position, const gchar *text, gint length); +void e_text_model_prepend (ETextModel *model, const gchar *text); +void e_text_model_append (ETextModel *model, const gchar *text); +void e_text_model_delete (ETextModel *model, gint position, gint length); + + +/* Functions for accessing embedded objects. */ + +gint e_text_model_object_count (ETextModel *model); +const gchar *e_text_model_get_nth_object (ETextModel *model, gint n, gint *len); +gchar *e_text_model_strdup_nth_object (ETextModel *model, gint n); +void e_text_model_get_nth_object_bounds (ETextModel *model, gint n, gint *start_pos, gint *end_pos); +gint e_text_model_get_object_at_offset (ETextModel *model, gint offset); +gint e_text_model_get_object_at_pointer (ETextModel *model, const gchar *c); +void e_text_model_activate_nth_object (ETextModel *model, gint n); + + -/* Standard Gtk function */ -GtkType e_text_model_get_type (void); -ETextModel *e_text_model_new(void); -void e_text_model_changed(ETextModel *model); -gchar *e_text_model_get_text(ETextModel *model); -void e_text_model_set_text(ETextModel *model, gchar *text); -void e_text_model_insert(ETextModel *model, gint position, gchar *text); -void e_text_model_insert_length(ETextModel *model, gint position, gchar *text, gint length); -void e_text_model_delete(ETextModel *model, gint position, gint length); -gint e_text_model_object_count(ETextModel *model); -const gchar *e_text_model_get_nth_object(ETextModel *model, gint n); -void e_text_model_activate_nth_object(ETextModel *model, gint n); -gchar *e_text_model_strdup_expanded_text(ETextModel *model); END_GNOME_DECLS diff --git a/widgets/text/e-text.c b/widgets/text/e-text.c index 8edd06ca07..73cffd8579 100644 --- a/widgets/text/e-text.c +++ b/widgets/text/e-text.c @@ -46,11 +46,10 @@ static guint e_text_signals[E_TEXT_LAST_SIGNAL] = { 0 }; /* This defines a line of text */ struct line { - char *text; /* Line's text, it is a pointer into the text->text string */ + const char *text; /* Line's text, it is a pointer into the text->text string */ int length; /* Line's length IN BYTES */ int width; /* Line's width in pixels */ int ellipsis_length; /* Length before adding ellipsis */ - gint first_obj; /* First embedded object number */ }; /* Object argument IDs */ @@ -128,6 +127,7 @@ static void e_text_get_selection(EText *text, GdkAtom selection, guint32 time); static void e_text_supply_selection (EText *text, guint time, GdkAtom selection, guchar *data, gint length); static void e_text_text_model_changed(ETextModel *model, EText *text); +static void e_text_text_model_reposition (ETextModel *model, ETextModelReposFn fn, gpointer repos_data, gpointer data); static void _get_tep(EText *text); @@ -152,9 +152,9 @@ static void e_suck_font_free (ETextSuckFont *suckfont); static void e_text_free_lines(EText *text); -static gint text_width_with_objects (ETextModel *model, gint first_object, +static gint text_width_with_objects (ETextModel *model, EFont *font, EFontStyle style, - gchar *text, gint bytelen); + const gchar *text, gint bytelen); static void calc_height (EText *text); static void calc_line_widths (EText *text); @@ -321,16 +321,22 @@ e_text_class_init (ETextClass *klass) static void e_text_init (EText *text) { - text->model = e_text_model_new(); + text->model = e_text_model_new (); text->text = e_text_model_get_text (text->model); - gtk_object_ref (GTK_OBJECT(text->model)); - gtk_object_sink (GTK_OBJECT(text->model)); + gtk_object_ref (GTK_OBJECT (text->model)); + gtk_object_sink (GTK_OBJECT (text->model)); + text->model_changed_signal_id = - gtk_signal_connect(GTK_OBJECT(text->model), + gtk_signal_connect (GTK_OBJECT (text->model), "changed", - GTK_SIGNAL_FUNC(e_text_text_model_changed), - text); + GTK_SIGNAL_FUNC (e_text_text_model_changed), + text); + text->model_repos_signal_id = + gtk_signal_connect (GTK_OBJECT (text->model), + "reposition", + GTK_SIGNAL_FUNC (e_text_text_model_reposition), + text); text->anchor = GTK_ANCHOR_CENTER; text->justification = GTK_JUSTIFY_LEFT; @@ -404,8 +410,12 @@ e_text_destroy (GtkObject *object) text = E_TEXT (object); if (text->model_changed_signal_id) - gtk_signal_disconnect(GTK_OBJECT(text->model), - text->model_changed_signal_id); + gtk_signal_disconnect (GTK_OBJECT (text->model), + text->model_changed_signal_id); + + if (text->model_repos_signal_id) + gtk_signal_disconnect (GTK_OBJECT (text->model), + text->model_repos_signal_id); if (text->model) gtk_object_unref(GTK_OBJECT(text->model)); @@ -475,6 +485,41 @@ e_text_text_model_changed (ETextModel *model, EText *text) } static void +e_text_text_model_reposition (ETextModel *model, ETextModelReposFn fn, gpointer repos_data, gpointer user_data) +{ + EText *text = E_TEXT (user_data); +#if 0 + gint org_start = text->selection_start, org_end = text->selection_end; +#endif + gint model_len = e_text_model_get_text_length (model); + + text->selection_start = fn (text->selection_start, repos_data); + text->selection_end = fn (text->selection_end, repos_data); + + /* Our repos function should make sure we don't overrun the buffer, but it never + hurts to be paranoid. */ + text->selection_start = CLAMP (text->selection_start, 0, model_len); + text->selection_end = CLAMP (text->selection_end, 0, model_len); + + if (text->selection_start > text->selection_end) { + gint tmp = text->selection_start; + text->selection_start = text->selection_end; + text->selection_end = tmp; + } + +#if 0 + if (org_start != text->selection_start || org_end != text->selection_end) { + /* + In general we shouldn't need to do anything to refresh the + canvas to redraw the (moved) selection, since "reposition" events + will only be generated in association with ETextModel-changing + activities. + */ + } +#endif +} + +static void get_bounds_item_relative (EText *text, double *px1, double *py1, double *px2, double *py2) { GnomeCanvasItem *item; @@ -714,7 +759,7 @@ calc_line_widths (EText *text) struct line *lines; int i; gdouble clip_width; - gchar *p; + const gchar *p; /* Make sure line has been split */ if (text->text && text->num_lines == 0) @@ -737,7 +782,7 @@ calc_line_widths (EText *text) for (i = 0; i < text->num_lines; i++) { if (lines->length != 0) { if (text->font) { - lines->width = text_width_with_objects (text->model, lines->first_obj, + lines->width = text_width_with_objects (text->model, text->font, E_FONT_PLAIN, lines->text, lines->length); lines->ellipsis_length = 0; @@ -753,7 +798,7 @@ calc_line_widths (EText *text) if (text->font) { lines->ellipsis_length = 0; for (p = lines->text; p && *p && (p - lines->text) < lines->length; p = unicode_next_utf8 (p)) { - gint text_width = text_width_with_objects (text->model, lines->first_obj, + gint text_width = text_width_with_objects (text->model, text->font, E_FONT_PLAIN, lines->text, p - lines->text); if (clip_width >= text_width + text->ellipsis_width) @@ -764,7 +809,7 @@ calc_line_widths (EText *text) } else lines->ellipsis_length = 0; - lines->width = text_width_with_objects (text->model, lines->first_obj, + lines->width = text_width_with_objects (text->model, text->font, E_FONT_PLAIN, lines->text, lines->ellipsis_length) + text->ellipsis_width; @@ -791,135 +836,74 @@ e_text_free_lines(EText *text) } static gint -text_width_with_objects (ETextModel *model, gint object_num, +text_width_with_objects (ETextModel *model, EFont *font, EFontStyle style, - gchar *text, gint numbytes) + const gchar *text, gint numbytes) { - gchar *c; - gint width = 0; - - while (*text && numbytes > 0) { - - c = text; - - while (*c && *c != '\1' && numbytes > 0) { - ++c; - --numbytes; - } - - width += e_font_utf8_text_width (font, style, text, c-text); - - if (*c == '\1' && numbytes > 0) { - const gchar *obj_str; - g_assert (object_num < e_text_model_object_count (model)); - obj_str = e_text_model_get_nth_object (model, object_num); - width += e_font_utf8_text_width (font, E_FONT_BOLD, obj_str, strlen (obj_str)); - ++object_num; - ++c; - --numbytes; - } - - text = c; - } - - return width; -} - -static gint -unicode_strlen_with_objects(ETextModel *model, gint object_num, gchar *s) -{ - gint unival; - gint len=0; - gchar *p; - - for (p = unicode_get_utf8 (s, &unival); (unival && p); p = unicode_get_utf8 (p, &unival)) { - if (unival == '\1') { - const gchar *obj_str = e_text_model_get_nth_object (model, object_num); - len += unicode_strlen (obj_str, -1); - ++object_num; - } else { - ++len; - } - } - - return len; + return e_font_utf8_text_width (font, style, text, numbytes); } static void -text_draw_with_objects (ETextModel *model, gint object_num, +text_draw_with_objects (ETextModel *model, GdkDrawable *drawable, EFont *font, EFontStyle style, GdkGC *gc, gint x, gint y, - gchar *text, gint numbytes) + const gchar *text, gint numbytes) { - gchar *c; + const gchar *c; while (*text && numbytes > 0) { + gint obj_num = -1; c = text; - while (*c && *c != '\1' && numbytes > 0) { + while (*c + && (obj_num = e_text_model_get_object_at_pointer (model, c)) == -1 + && numbytes > 0) { ++c; --numbytes; } e_font_draw_utf8_text (drawable, font, style, gc, x, y, text, c-text); x += e_font_utf8_text_width (font, style, text, c-text); - - if (*c == '\1' && numbytes > 0) { - const gchar *obj_str; - gint start_x = x; + + if (obj_num != -1 && numbytes > 0) { gint len; - g_assert (object_num < e_text_model_object_count (model)); + gint start_x = x; - obj_str = e_text_model_get_nth_object (model, object_num); + e_text_model_get_nth_object (model, obj_num, &len); - len = strlen (obj_str); - e_font_draw_utf8_text (drawable, font, style, gc, x, y, obj_str, len); - x += e_font_utf8_text_width (font, style, obj_str, len); + if (len > numbytes) + len = numbytes; + e_font_draw_utf8_text (drawable, font, style, gc, x, y, c, len); + x += e_font_utf8_text_width (font, style, c, len); /* We underline our objects. */ gdk_draw_line (drawable, gc, start_x, y+1, x, y+1); - ++object_num; - ++c; - --numbytes; + c += len; + numbytes -= len; } text = c; } } -static gint -object_number_advance (gint object_num, gchar *start, gint numbytes) -{ - while (*start && numbytes > 0) { - if (*start == '\1') - ++object_num; - ++start; - --numbytes; - } - - return object_num; -} - - #define IS_BREAKCHAR(text,c) ((text)->break_characters && unicode_strchr ((text)->break_characters, (c))) /* Splits the text of the text item into lines */ static void split_into_lines (EText *text) { - char *p, *cp; + const char *p, *cp; struct line *lines; int len; int line_num; - char *laststart; - char *lastend; - char *linestart; + const char *laststart; + const char *lastend; + const char *linestart; double clip_width; unicode_char_t unival; - int object_num; /* Free old array of lines */ e_text_free_lines(text); @@ -941,18 +925,17 @@ split_into_lines (EText *text) } cp = text->text; - object_num = 0; for (p = unicode_get_utf8 (cp, &unival); (unival && p); cp = p, p = unicode_get_utf8 (p, &unival)) { - if (text->line_wrap && (unicode_isspace (unival) || unival == '\n')) { + if (text->line_wrap + && (unicode_isspace (unival) || unival == '\n') + && e_text_model_get_object_at_pointer (text->model, cp) == -1) { /* don't break mid-object */ if (laststart != lastend - && clip_width < text_width_with_objects (text->model, object_num, + && clip_width < text_width_with_objects (text->model, text->font, E_FONT_PLAIN, linestart, cp - linestart)) { text->num_lines ++; - - object_num = object_number_advance (object_num, linestart, lastend-linestart); - + linestart = laststart; laststart = p; lastend = cp; @@ -960,17 +943,16 @@ split_into_lines (EText *text) laststart = p; lastend = cp; } - } else if (text->line_wrap && (IS_BREAKCHAR(text, unival) || unival == '\1')) { - - if ((unival == '\1' || laststart != lastend) - && (unival == '\1' || unicode_index_to_offset (linestart, cp - linestart) != 1) - && clip_width < text_width_with_objects (text->model, object_num, + } else if (text->line_wrap + && IS_BREAKCHAR (text, unival)) { + + if (laststart != lastend + && unicode_index_to_offset (linestart, cp - linestart) != 1 + && clip_width < text_width_with_objects (text->model, text->font, E_FONT_PLAIN, linestart, p - linestart)) { text->num_lines ++; - - object_num = object_number_advance (object_num, linestart, lastend-linestart); - + linestart = laststart; laststart = p; lastend = p; @@ -979,11 +961,10 @@ split_into_lines (EText *text) lastend = p; } } + if (unival == '\n') { text->num_lines ++; - object_num = object_number_advance (object_num, linestart, lastend-linestart); - lastend = p; laststart = p; linestart = p; @@ -993,11 +974,10 @@ split_into_lines (EText *text) if ( text->line_wrap && p && laststart != lastend - && clip_width < text_width_with_objects (text->model, object_num, + && clip_width < text_width_with_objects (text->model, text->font, E_FONT_PLAIN, linestart, cp - linestart)) { text->num_lines ++; - object_num = object_number_advance (object_num, linestart, lastend-linestart); } text->num_lines++; @@ -1015,22 +995,21 @@ split_into_lines (EText *text) laststart = text->text; cp = text->text; - object_num = 0; for (p = unicode_get_utf8 (cp, &unival); p && unival && line_num < text->num_lines; cp = p, p = unicode_get_utf8 (p, &unival)) { gboolean handled = FALSE; if (len == 0) lines->text = cp; - if (text->line_wrap && (unicode_isspace (unival) || unival == '\n')) { - if (clip_width < text_width_with_objects (text->model, object_num, + if (text->line_wrap + && (unicode_isspace (unival) || unival == '\n') + && e_text_model_get_object_at_pointer (text->model, cp) == -1) { /* don't break mid-object */ + if (clip_width < text_width_with_objects (text->model, text->font, E_FONT_PLAIN, lines->text, cp - lines->text) && laststart != lastend) { lines->length = lastend - lines->text; - lines->first_obj = object_num; - object_num = object_number_advance (object_num, lines->text, lines->length); lines++; line_num++; @@ -1044,16 +1023,16 @@ split_into_lines (EText *text) len ++; } handled = TRUE; - } else if (text->line_wrap && (IS_BREAKCHAR(text, unival) || unival == '\1')) { - if ((unival == '\1' || laststart != lastend) - && (unival == '\1' || unicode_index_to_offset (lines->text, cp - lines->text) != 1) - && clip_width < text_width_with_objects (text->model, object_num, + } else if (text->line_wrap + && IS_BREAKCHAR(text, unival) + && e_text_model_get_object_at_pointer (text->model, cp) == -1) { + if (laststart != lastend + && unicode_index_to_offset (lines->text, cp - lines->text) != 1 + && clip_width < text_width_with_objects (text->model, text->font, E_FONT_PLAIN, lines->text, p - lines->text)) { lines->length = lastend - lines->text; - lines->first_obj = object_num; - object_num = object_number_advance (object_num, lines->text, lines->length); lines++; line_num++; @@ -1072,8 +1051,6 @@ split_into_lines (EText *text) if (unival == '\n') { lines->length = cp - lines->text; - lines->first_obj = object_num; - object_num = object_number_advance (object_num, lines->text, lines->length); lines++; line_num++; @@ -1087,14 +1064,12 @@ split_into_lines (EText *text) } if ( line_num < text->num_lines && text->line_wrap ) { - if (clip_width < text_width_with_objects (text->model, object_num, + if (clip_width < text_width_with_objects (text->model, text->font, E_FONT_PLAIN, lines->text, cp - lines->text) && laststart != lastend ) { lines->length = lastend - lines->text; - lines->first_obj = object_num; - object_num = object_number_advance (object_num, lines->text, lines->length); lines++; line_num++; @@ -1108,7 +1083,6 @@ split_into_lines (EText *text) if (len == 0) lines->text = cp; lines->length = strlen (lines->text); - lines->first_obj = object_num; } /* Convenience function to set the text's GC's foreground color */ @@ -1163,18 +1137,30 @@ e_text_set_arg (GtkObject *object, GtkArg *arg, guint arg_id) switch (arg_id) { case ARG_MODEL: + if ( text->model_changed_signal_id ) - gtk_signal_disconnect(GTK_OBJECT(text->model), - text->model_changed_signal_id); - gtk_object_unref(GTK_OBJECT(text->model)); - text->model = E_TEXT_MODEL(GTK_VALUE_OBJECT (*arg)); - gtk_object_ref(GTK_OBJECT(text->model)); + gtk_signal_disconnect (GTK_OBJECT (text->model), + text->model_changed_signal_id); + + if ( text->model_repos_signal_id ) + gtk_signal_disconnect (GTK_OBJECT (text->model), + text->model_repos_signal_id); + + gtk_object_unref (GTK_OBJECT (text->model)); + text->model = E_TEXT_MODEL (GTK_VALUE_OBJECT (*arg)); + gtk_object_ref (GTK_OBJECT (text->model)); text->model_changed_signal_id = - gtk_signal_connect(GTK_OBJECT(text->model), - "changed", - GTK_SIGNAL_FUNC(e_text_text_model_changed), - text); + gtk_signal_connect (GTK_OBJECT (text->model), + "changed", + GTK_SIGNAL_FUNC (e_text_text_model_changed), + text); + + text->model_repos_signal_id = + gtk_signal_connect (GTK_OBJECT (text->model), + "reposition", + GTK_SIGNAL_FUNC (e_text_text_model_reposition), + text); e_text_free_lines(text); @@ -1672,7 +1658,7 @@ e_text_reflow (GnomeCanvasItem *item, int flags) } lines --; i--; - x = text_width_with_objects (text->model, lines->first_obj, + x = text_width_with_objects (text->model, text->font, E_FONT_PLAIN, lines->text, text->selection_end - (lines->text - text->text)); @@ -2050,12 +2036,12 @@ e_text_draw (GnomeCanvasItem *item, GdkDrawable *drawable, if ( sel_end > end_char ) sel_end = end_char; if ( sel_start < sel_end ) { - sel_rect.x = xpos - x + text_width_with_objects (text->model, lines->first_obj, + sel_rect.x = xpos - x + text_width_with_objects (text->model, text->font, E_FONT_PLAIN, lines->text, sel_start - start_char); sel_rect.y = ypos - y - e_font_ascent (text->font); - sel_rect.width = text_width_with_objects (text->model, lines->first_obj, + sel_rect.width = text_width_with_objects (text->model, text->font, E_FONT_PLAIN, lines->text + sel_start - start_char, sel_end - sel_start); @@ -2073,7 +2059,7 @@ e_text_draw (GnomeCanvasItem *item, GdkDrawable *drawable, sel_rect.y, sel_rect.width, sel_rect.height); - text_draw_with_objects (text->model, lines->first_obj, + text_draw_with_objects (text->model, drawable, text->font, E_FONT_PLAIN, text->gc, @@ -2081,22 +2067,22 @@ e_text_draw (GnomeCanvasItem *item, GdkDrawable *drawable, ypos - y, lines->text, sel_start - start_char); - text_draw_with_objects (text->model, lines->first_obj, + text_draw_with_objects (text->model, drawable, text->font, E_FONT_PLAIN, fg_gc, - xpos - x + text_width_with_objects (text->model, lines->first_obj, + xpos - x + text_width_with_objects (text->model, text->font, E_FONT_PLAIN, lines->text, sel_start - start_char), ypos - y, lines->text + sel_start - start_char, sel_end - sel_start); - text_draw_with_objects (text->model, lines->first_obj, + text_draw_with_objects (text->model, drawable, text->font, E_FONT_PLAIN, text->gc, - xpos - x + text_width_with_objects (text->model, lines->first_obj, + xpos - x + text_width_with_objects (text->model, text->font, E_FONT_PLAIN, lines->text, sel_end - start_char), @@ -2104,7 +2090,7 @@ e_text_draw (GnomeCanvasItem *item, GdkDrawable *drawable, lines->text + sel_end - start_char, end_char - sel_end); } else { - text_draw_with_objects (text->model, lines->first_obj, + text_draw_with_objects (text->model, drawable, text->font, E_FONT_PLAIN, text->gc, @@ -2120,7 +2106,7 @@ e_text_draw (GnomeCanvasItem *item, GdkDrawable *drawable, gdk_draw_rectangle (drawable, text->gc, TRUE, - xpos - x + text_width_with_objects (text->model, lines->first_obj, + xpos - x + text_width_with_objects (text->model, text->font, E_FONT_PLAIN, lines->text, sel_start - start_char), @@ -2130,7 +2116,7 @@ e_text_draw (GnomeCanvasItem *item, GdkDrawable *drawable, } } else { if (text->clip && text->use_ellipsis && lines->ellipsis_length < lines->length) { - text_draw_with_objects (text->model, lines->first_obj, + text_draw_with_objects (text->model, drawable, text->font, E_FONT_PLAIN, text->gc, @@ -2146,7 +2132,7 @@ e_text_draw (GnomeCanvasItem *item, GdkDrawable *drawable, text->ellipsis ? text->ellipsis : "...", text->ellipsis ? strlen (text->ellipsis) : 3); } else - text_draw_with_objects (text->model, lines->first_obj, + text_draw_with_objects (text->model, drawable, text->font, E_FONT_PLAIN, text->gc, @@ -2450,7 +2436,7 @@ _get_xy_from_position (EText *text, gint position, gint *xp, gint *yp) lines --; y -= e_font_descent (text->font); - x += text_width_with_objects (text->model, lines->first_obj, + x += text_width_with_objects (text->model, text->font, E_FONT_PLAIN, lines->text, position - (lines->text - text->text)); @@ -2474,9 +2460,8 @@ _get_position_from_xy (EText *text, gint x, gint y) int ypos = text->yofs; int xpos; double xd, yd; - char *p; + const char *p; unicode_char_t unival; - gint object_num; gint font_ht, adjust=0; struct line *lines; @@ -2526,13 +2511,13 @@ _get_position_from_xy (EText *text, gint x, gint y) x += text->xofs_edit; xpos = get_line_xpos_item_relative (text, lines); - object_num = lines->first_obj; for (i = 0, p = lines->text; p && i < lines->length; i++, p = unicode_get_utf8 (p, &unival)) { int charwidth; int step1, step2; +#if 0 if (unival == '\1') { - const gchar *obj_str = e_text_model_get_nth_object (text->model, object_num); + const gchar *obj_str = NULL; /*e_text_model_get_nth_object (text->model, object_num);*/ charwidth = e_font_utf8_text_width (text->font, E_FONT_PLAIN, obj_str, strlen (obj_str)); ++object_num; @@ -2541,12 +2526,15 @@ _get_position_from_xy (EText *text, gint x, gint y) adjust = -1; } else { - charwidth = e_font_utf8_char_width (text->font, E_FONT_PLAIN, p); +#endif + charwidth = e_font_utf8_char_width (text->font, E_FONT_PLAIN, (gchar *) p); step1 = charwidth / 2; step2 = (charwidth + 1) / 2; adjust = 0; +#if 0 } +#endif xpos += step1; if (xpos > x) { @@ -2734,8 +2722,7 @@ _do_tooltip (gpointer data) for (lines = text->lines, i = 0; i < text->num_lines; lines++, i++) { gdouble line_width; - line_width = text_width_with_objects (text->model, lines->first_obj, - text->font, E_FONT_PLAIN, lines->text, lines->length); + line_width = text_width_with_objects (text->model, text->font, E_FONT_PLAIN, lines->text, lines->length); max_width = MAX (max_width, line_width); } @@ -2944,6 +2931,8 @@ e_text_event (GnomeCanvasItem *item, GdkEvent *event) e_tep_event.key.state = key.state; e_tep_event.key.keyval = key.keyval; + // g_print ("etext got keyval \"%s\"\n", gdk_keyval_name (key.keyval)); + /* This is probably ugly hack, but we have to handle UTF-8 input somehow */ #if 0 e_tep_event.key.length = key.length; @@ -3114,139 +3103,178 @@ e_text_event (GnomeCanvasItem *item, GdkEvent *event) static int _get_position(EText *text, ETextEventProcessorCommand *command) { - int length; + int length, obj_num; int x, y; unicode_char_t unival; - char *p; + char *p = NULL; + gint new_pos = 0; switch (command->position) { case E_TEP_VALUE: - return command->value; + new_pos = command->value; + break; case E_TEP_SELECTION: - return text->selection_end; + new_pos = text->selection_end; + break; case E_TEP_START_OF_BUFFER: - return 0; + new_pos = 0; + break; + case E_TEP_END_OF_BUFFER: - return unicode_strlen_with_objects (text->model, 0, text->text); + new_pos = unicode_strlen (text->text, -1); + break; case E_TEP_START_OF_LINE: - if (text->selection_end < 1) return 0; - p = unicode_previous_utf8 (text->text, text->text + text->selection_end); - if (p == text->text) return 0; - p = unicode_previous_utf8 (text->text, p); - - while (p && p > text->text) { - if (*p == '\n') return p - text->text + 1; - p = unicode_previous_utf8 (text->text, p); + new_pos = 0; + + if (text->selection_end >= 1) { + + p = unicode_previous_utf8 (text->text, text->text + text->selection_end); + if (p != text->text) { + p = unicode_previous_utf8 (text->text, p); + + while (p && p > text->text && !new_pos) { + if (*p == '\n') + new_pos = p - text->text + 1; + p = unicode_previous_utf8 (text->text, p); + } + } } - return 0; + break; case E_TEP_END_OF_LINE: + new_pos = -1; length = strlen (text->text); - if (text->selection_end >= length) return length; + + if (text->selection_end >= length) { + new_pos = length; + } else { - p = unicode_next_utf8 (text->text + text->selection_end); + p = unicode_next_utf8 (text->text + text->selection_end); - while (*p) { - if (*p == '\n') return p - text->text; - p = unicode_next_utf8 (p); + while (p && *p) { + if (*p == '\n') { + new_pos = p - text->text; + p = NULL; + } else + p = unicode_next_utf8 (p); + } } - return p - text->text; + if (new_pos == -1) + new_pos = p - text->text; + + break; case E_TEP_FORWARD_CHARACTER: length = strlen (text->text); - if (text->selection_end >= length) return length; - p = unicode_next_utf8 (text->text + text->selection_end); + if (text->selection_end >= length) { + new_pos = length; + } else { + p = unicode_next_utf8 (text->text + text->selection_end); + new_pos = p - text->text; + } - return p - text->text; + break; case E_TEP_BACKWARD_CHARACTER: - if (text->selection_end < 1) return 0; - p = unicode_previous_utf8 (text->text, text->text + text->selection_end); + new_pos = 0; + if (text->selection_end >= 1) { + p = unicode_previous_utf8 (text->text, text->text + text->selection_end); - if (p == NULL) return 0; + if (p != NULL) + new_pos = p - text->text; + } - return p - text->text; + break; case E_TEP_FORWARD_WORD: + new_pos = -1; length = strlen (text->text); - if (text->selection_end >= length) return length; - p = unicode_next_utf8 (text->text + text->selection_end); + if (text->selection_end >= length) { + new_pos = length; + } else { - while (*p) { - unicode_get_utf8 (p, &unival); - if (unicode_isspace (unival)) return p - text->text; - p = unicode_next_utf8 (p); + p = unicode_next_utf8 (text->text + text->selection_end); + + while (p && *p) { + unicode_get_utf8 (p, &unival); + if (unicode_isspace (unival)) { + new_pos = p - text->text; + p = NULL; + } else + p = unicode_next_utf8 (p); + } } + + if (new_pos == -1) + new_pos = p - text->text; - return p - text->text; + break; case E_TEP_BACKWARD_WORD: - - if (text->selection_end < 1) return 0; - p = unicode_previous_utf8 (text->text, text->text + text->selection_end); - if (p == text->text) return 0; - p = unicode_previous_utf8 (text->text, p); - - while (p && p > text->text) { - unicode_get_utf8 (p, &unival); - if (unicode_isspace (unival)) return (unicode_next_utf8 (p) - text->text); - p = unicode_previous_utf8 (text->text, p); + new_pos = 0; + if (text->selection_end >= 1) { + p = unicode_previous_utf8 (text->text, text->text + text->selection_end); + if (p != text->text) { + p = unicode_previous_utf8 (text->text, p); + + while (p && p > text->text) { + unicode_get_utf8 (p, &unival); + if (unicode_isspace (unival)) { + new_pos = unicode_next_utf8 (p) - text->text; + p = NULL; + } else + p = unicode_previous_utf8 (text->text, p); + } + } } - - return 0; + + break; case E_TEP_FORWARD_LINE: _get_xy_from_position(text, text->selection_end, &x, &y); y += e_font_height (text->font); - return _get_position_from_xy(text, x, y); + new_pos = _get_position_from_xy(text, x, y); + break; + case E_TEP_BACKWARD_LINE: _get_xy_from_position(text, text->selection_end, &x, &y); y -= e_font_height (text->font); - return _get_position_from_xy(text, x, y); + new_pos = _get_position_from_xy(text, x, y); + break; case E_TEP_SELECT_WORD: - { - /* This is a silly hack to cause double-clicking on an object - to activate that object. - (Normally, double click == select word, which is why this is here.) */ - - gchar c = text->text[text->selection_start]; - gint i; - gint obj_num=0; - - if (c == '\0' - && text->selection_start > 0 - && text->text[text->selection_start-1] == '\1') { - c = '\1'; - --text->selection_start; - } - - if (c == '\1') { + /* This is a silly hack to cause double-clicking on an object + to activate that object. + (Normally, double click == select word, which is why this is here.) */ + + obj_num = e_text_model_get_object_at_offset (text->model, text->selection_start); + if (obj_num != -1) { + e_text_model_activate_nth_object (text->model, obj_num); + new_pos = text->selection_start; + break; + } - for (i=0; i<text->selection_start; ++i) - if (text->text[i] == '\1') - ++obj_num; - e_text_model_activate_nth_object (text->model, obj_num); - - return text->selection_start; - } + if (text->selection_end < 1) { + new_pos = 0; + break; } - - if (text->selection_end < 1) return 0; p = unicode_previous_utf8 (text->text, text->text + text->selection_end); - if (p == text->text) return 0; + if (p == text->text) { + new_pos = 0; + break; + } p = unicode_previous_utf8 (text->text, p); while (p && p > text->text) { @@ -3263,34 +3291,51 @@ _get_position(EText *text, ETextEventProcessorCommand *command) else text->selection_start = p - text->text; - length = strlen (text->text); - if (text->selection_end >= length) return length; + text->selection_start = e_text_model_validate_position (text->model, text->selection_start); + + length = strlen (text->text); + if (text->selection_end >= length) { + new_pos = length; + break; + } p = unicode_next_utf8 (text->text + text->selection_end); - while (*p) { + while (p && *p) { unicode_get_utf8 (p, &unival); - if (unicode_isspace (unival)) return p - text->text; - p = unicode_next_utf8 (p); + if (unicode_isspace (unival)) { + new_pos = p - text->text; + p = NULL; + } else + p = unicode_next_utf8 (p); } - return p - text->text; + if (p) + new_pos = p - text->text; + + return new_pos; case E_TEP_SELECT_ALL: text->selection_start = 0; - length = strlen (text->text); - return length; + new_pos = strlen (text->text); + break; case E_TEP_FORWARD_PARAGRAPH: case E_TEP_BACKWARD_PARAGRAPH: case E_TEP_FORWARD_PAGE: case E_TEP_BACKWARD_PAGE: - return text->selection_end; + new_pos = text->selection_end; + break; + default: - return text->selection_end; - } + new_pos = text->selection_end; + } + + new_pos = e_text_model_validate_position (text->model, new_pos); + + return new_pos; } static void @@ -3298,10 +3343,14 @@ _delete_selection(EText *text) { if ( text->selection_start < text->selection_end ) { e_text_model_delete(text->model, text->selection_start, text->selection_end - text->selection_start); +#if 0 text->selection_end = text->selection_start; +#endif } else { e_text_model_delete(text->model, text->selection_end, text->selection_start - text->selection_end); +#if 0 text->selection_start = text->selection_end; +#endif } } @@ -3311,8 +3360,10 @@ _insert(EText *text, char *string, int value) if (value > 0) { e_text_model_insert_length(text->model, text->selection_start, string, value); +#if 0 text->selection_start += value; text->selection_end = text->selection_start; +#endif } } @@ -3321,6 +3372,7 @@ e_text_command(ETextEventProcessor *tep, ETextEventProcessorCommand *command, gp { EText *text = E_TEXT(data); int sel_start, sel_end; + switch (command->action) { case E_TEP_MOVE: text->selection_start = _get_position(text, command); @@ -3330,15 +3382,21 @@ e_text_command(ETextEventProcessor *tep, ETextEventProcessorCommand *command, gp } break; case E_TEP_SELECT: + text->selection_start = e_text_model_validate_position (text->model, text->selection_start); /* paranoia */ text->selection_end = _get_position(text, command); + sel_start = MIN(text->selection_start, text->selection_end); - sel_end = MAX(text->selection_start, text->selection_end); + sel_end = MAX(text->selection_start, text->selection_end); + + sel_start = e_text_model_validate_position (text->model, sel_start); + if (sel_start != sel_end) { e_text_supply_selection (text, command->time, GDK_SELECTION_PRIMARY, - text->text + sel_start, sel_end - sel_start); + (guchar *) text->text + sel_start, sel_end - sel_start); } else if (text->timer) { g_timer_reset(text->timer); } + break; case E_TEP_DELETE: if (text->selection_end == text->selection_start) { @@ -3364,7 +3422,7 @@ e_text_command(ETextEventProcessor *tep, ETextEventProcessorCommand *command, gp sel_end = MAX(text->selection_start, text->selection_end); if (sel_start != sel_end) { e_text_supply_selection (text, command->time, clipboard_atom, - text->text + sel_start, sel_end - sel_start); + (guchar *) text->text + sel_start, sel_end - sel_start); } if (text->timer) { g_timer_reset(text->timer); @@ -3416,7 +3474,7 @@ e_text_command(ETextEventProcessor *tep, ETextEventProcessorCommand *command, gp } lines --; i --; - x = text_width_with_objects (text->model, lines->first_obj, + x = text_width_with_objects (text->model, text->font, E_FONT_PLAIN, lines->text, text->selection_end - (lines->text - text->text)); diff --git a/widgets/text/e-text.h b/widgets/text/e-text.h index 7a383e7b89..d497296a87 100644 --- a/widgets/text/e-text.h +++ b/widgets/text/e-text.h @@ -110,8 +110,9 @@ struct _EText { ETextModel *model; gint model_changed_signal_id; + gint model_repos_signal_id; - char *text; /* Text to display */ + const gchar *text; /* Text to display --- from the ETextModel */ gpointer lines; /* Text split into lines (private field) */ int num_lines; /* Number of lines of text */ |