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