From 0e3e3e99143b969db1e40d72a0806f5dec5f329b Mon Sep 17 00:00:00 2001 From: Jon Trowbridge Date: Mon, 19 Feb 2001 22:47:23 +0000 Subject: Added. Allows you to attach an ECompletion to an EEntry, and have that 2001-02-19 Jon Trowbridge * gal/e-text/e-entry.c (e_entry_enable_completion_full): Added. Allows you to attach an ECompletion to an EEntry, and have that ECompletion be used for (obviously enough) completions. * gal/e-text/e-completion-view.h, gal/e-text/e-completion-view.c: Added. ECompletionView is a widget for displaying the results of a completion request in a format that is appropriate for a drop-down window. * gal/e-text/e-completion.h, gal/e-text/e-completion.c: Added. ECompletion is a "pure virtual base class" for completion-type operations. It is implemented so that completions can be either synchronous or asynchronous. * gal/e-text/e-text.c: Lots of changes to accomodate the ETextModel changes. First of all, we render embedded text objects as being underlined. We also cause the model to emit the appropriate object activation signal when an embedded object is double-clicked. Also, all of the code that moves the cursor in response to user input has been removed. Instead, the EText now listens for "reposition" events from the underlying model, and bases all cursor motions on those. (get_bounds_item_relative): Fixed bug in the handling of differently-anchored text. Being differently-anchored is not a crime or a perversion --- it is an alternative lifestyle that we have to respect. * gal/e-text/e-text-model-uri.h, gal/e-text/e-text-model-uri.c: A sample ETextModel that converts URIs into embedded objects that get opened in the browser when you double-click them. * gal/e-text/e-text-model-repos.h, gal/e-text/e-text-model-repos.c: Added. A group of simple structures & functions for handling various cursor movement rules. These are the sorts of things that are passed as arguments to ETextModel "reposition" event handlers. * gal/e-text/e-text-model.h, gal/e-text/e-text-model.c: Privitized the ETextModel struct and "methodized" all of the operations, so that derived classes can do arbitrarily respond to get/set requests in arbitrarily strange ways. Also added the concept of declaring regions of the text as "embedded text objects". Finally, caused operations that change the text to emit a "reposition" signal that passes information that can be used by a view (like an EText) to move the cursor or selection in an intelligent way in response to those changes. This means that you can now open two ETexts that look at the same ETextModel, and have the cursor in one do the right thing when you edit the other. (As opposed to producing a lot of potential segfaults, as it was before.) svn path=/trunk/; revision=8280 --- widgets/text/e-completion-test.c | 169 +++++++++++ widgets/text/e-completion.c | 511 +++++++++++++++++++++++++++++++ widgets/text/e-completion.h | 95 ++++++ widgets/text/e-entry.c | 621 ++++++++++++++++++++++++++++++++------ widgets/text/e-entry.h | 73 ++++- widgets/text/e-text-model-repos.c | 73 +++++ widgets/text/e-text-model-repos.h | 55 ++++ widgets/text/e-text-model-test.c | 58 ++-- widgets/text/e-text-model-uri.c | 273 +++++++++++++---- widgets/text/e-text-model-uri.h | 6 +- widgets/text/e-text-model.c | 520 +++++++++++++++++++++---------- widgets/text/e-text-model.h | 79 +++-- widgets/text/e-text.c | 570 ++++++++++++++++++---------------- widgets/text/e-text.h | 3 +- 14 files changed, 2483 insertions(+), 623 deletions(-) create mode 100644 widgets/text/e-completion-test.c create mode 100644 widgets/text/e-completion.c create mode 100644 widgets/text/e-completion.h create mode 100644 widgets/text/e-text-model-repos.c create mode 100644 widgets/text/e-text-model-repos.h 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 +#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 + * Adapted by Jon Trowbridge + * + */ + +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + + +#include +#include +#include "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 + * Adapted by Jon Trowbridge + * + */ + +/* + * 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 +#include + +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 + * Chris Lahey + * Jon Trowbridge + * + * 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 +#include #include #include #include +#include #ifdef HAVE_ALLOCA_H #include #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; } @@ -507,33 +926,51 @@ 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 + * Chris Lahey + * Jon Trowbridge + * + * 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 #include #include -#include +#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 + */ + +#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 + */ + +#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 #include +#include +#include #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; itext == 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 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 */ +#undef PARANOID_DEBUGGING + #include #include +#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; itext; - 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 */ + #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)); @@ -474,6 +484,41 @@ e_text_text_model_changed (ETextModel *model, EText *text) e_canvas_item_request_reflow (GNOME_CANVAS_ITEM(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) { @@ -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; iselection_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 */ -- cgit