aboutsummaryrefslogtreecommitdiffstats
path: root/e-util/e-text-model.c
diff options
context:
space:
mode:
Diffstat (limited to 'e-util/e-text-model.c')
-rw-r--r--e-util/e-text-model.c642
1 files changed, 642 insertions, 0 deletions
diff --git a/e-util/e-text-model.c b/e-util/e-text-model.c
new file mode 100644
index 0000000000..ab6bff8ff3
--- /dev/null
+++ b/e-util/e-text-model.c
@@ -0,0 +1,642 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ * Chris Lahey <clahey@ximian.com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#undef PARANOID_DEBUGGING
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-text-model.h"
+
+#include <ctype.h>
+#include <string.h>
+
+#include <gtk/gtk.h>
+
+#include "e-marshal.h"
+#include "e-text-model-repos.h"
+
+#define E_TEXT_MODEL_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_TEXT_MODEL, ETextModelPrivate))
+
+enum {
+ E_TEXT_MODEL_CHANGED,
+ E_TEXT_MODEL_REPOSITION,
+ E_TEXT_MODEL_OBJECT_ACTIVATED,
+ E_TEXT_MODEL_CANCEL_COMPLETION,
+ E_TEXT_MODEL_LAST_SIGNAL
+};
+
+static guint signals[E_TEXT_MODEL_LAST_SIGNAL] = { 0 };
+
+struct _ETextModelPrivate {
+ GString *text;
+};
+
+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);
+
+G_DEFINE_TYPE (ETextModel, e_text_model, G_TYPE_OBJECT)
+
+static void
+e_text_model_finalize (GObject *object)
+{
+ ETextModelPrivate *priv;
+
+ priv = E_TEXT_MODEL_GET_PRIVATE (object);
+
+ g_string_free (priv->text, TRUE);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (e_text_model_parent_class)->finalize (object);
+}
+
+static void
+e_text_model_class_init (ETextModelClass *class)
+{
+ GObjectClass *object_class;
+
+ g_type_class_add_private (class, sizeof (ETextModelPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->finalize = e_text_model_finalize;
+
+ signals[E_TEXT_MODEL_CHANGED] = g_signal_new (
+ "changed",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (ETextModelClass, changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[E_TEXT_MODEL_REPOSITION] = g_signal_new (
+ "reposition",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (ETextModelClass, reposition),
+ NULL, NULL,
+ e_marshal_NONE__POINTER_POINTER,
+ G_TYPE_NONE, 2,
+ G_TYPE_POINTER,
+ G_TYPE_POINTER);
+
+ signals[E_TEXT_MODEL_OBJECT_ACTIVATED] = g_signal_new (
+ "object_activated",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (ETextModelClass, object_activated),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__INT,
+ G_TYPE_NONE, 1,
+ G_TYPE_INT);
+
+ signals[E_TEXT_MODEL_CANCEL_COMPLETION] = g_signal_new (
+ "cancel_completion",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (ETextModelClass, cancel_completion),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ /* No default signal handlers. */
+ class->changed = NULL;
+ class->reposition = NULL;
+ class->object_activated = NULL;
+
+ class->validate_pos = e_text_model_real_validate_position;
+
+ class->get_text = e_text_model_real_get_text;
+ class->get_text_len = e_text_model_real_get_text_length;
+ class->set_text = e_text_model_real_set_text;
+ class->insert = e_text_model_real_insert;
+ class->insert_length = e_text_model_real_insert_length;
+ class->delete = e_text_model_real_delete;
+
+ /* We explicitly don't define default handlers for these. */
+ class->objectify = NULL;
+ class->obj_count = NULL;
+ class->get_nth_obj = NULL;
+}
+
+static void
+e_text_model_init (ETextModel *model)
+{
+ model->priv = E_TEXT_MODEL_GET_PRIVATE (model);
+ model->priv->text = g_string_new ("");
+}
+
+static gint
+e_text_model_real_validate_position (ETextModel *model,
+ gint pos)
+{
+ gint len = e_text_model_get_text_length (model);
+
+ if (pos < 0)
+ pos = 0;
+ else if (pos > len)
+ pos = len;
+
+ return pos;
+}
+
+static const gchar *
+e_text_model_real_get_text (ETextModel *model)
+{
+ if (model->priv->text)
+ return model->priv->text->str;
+ else
+ return "";
+}
+
+static gint
+e_text_model_real_get_text_length (ETextModel *model)
+{
+ return g_utf8_strlen (model->priv->text->str, -1);
+}
+
+static void
+e_text_model_real_set_text (ETextModel *model,
+ const gchar *text)
+{
+ EReposAbsolute repos;
+ gboolean changed = FALSE;
+
+ if (text == NULL) {
+ changed = (*model->priv->text->str != '\0');
+
+ g_string_set_size (model->priv->text, 0);
+
+ } else if (*model->priv->text->str == '\0' ||
+ strcmp (model->priv->text->str, text)) {
+
+ g_string_assign (model->priv->text, text);
+
+ 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 (ETextModel *model,
+ gint position,
+ const gchar *text)
+{
+ e_text_model_insert_length (model, position, text, strlen (text));
+}
+
+static void
+e_text_model_real_insert_length (ETextModel *model,
+ gint position,
+ const gchar *text,
+ gint length)
+{
+ EReposInsertShift repos;
+ gint model_len = e_text_model_real_get_text_length (model);
+ gchar *offs;
+ const gchar *p;
+ gint byte_length, l;
+
+ if (position > model_len)
+ return;
+
+ offs = g_utf8_offset_to_pointer (model->priv->text->str, position);
+
+ for (p = text, l = 0;
+ l < length;
+ p = g_utf8_next_char (p), l++);
+
+ byte_length = p - text;
+
+ g_string_insert_len (
+ model->priv->text,
+ offs - model->priv->text->str,
+ text, byte_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 void
+e_text_model_real_delete (ETextModel *model,
+ gint position,
+ gint length)
+{
+ EReposDeleteShift repos;
+ gint byte_position, byte_length;
+ gchar *offs, *p;
+ gint l;
+
+ offs = g_utf8_offset_to_pointer (model->priv->text->str, position);
+ byte_position = offs - model->priv->text->str;
+
+ for (p = offs, l = 0;
+ l < length;
+ p = g_utf8_next_char (p), l++);
+
+ byte_length = p - offs;
+
+ g_string_erase (
+ model->priv->text,
+ byte_position, byte_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);
+}
+
+void
+e_text_model_changed (ETextModel *model)
+{
+ ETextModelClass *class;
+
+ g_return_if_fail (E_IS_TEXT_MODEL (model));
+
+ class = E_TEXT_MODEL_GET_CLASS (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->objectify != NULL)
+ class->objectify (model);
+
+ g_signal_emit (model, signals[E_TEXT_MODEL_CHANGED], 0);
+}
+
+void
+e_text_model_cancel_completion (ETextModel *model)
+{
+ g_return_if_fail (E_IS_TEXT_MODEL (model));
+
+ g_signal_emit (model, signals[E_TEXT_MODEL_CANCEL_COMPLETION], 0);
+}
+
+void
+e_text_model_reposition (ETextModel *model,
+ ETextModelReposFn fn,
+ gpointer repos_data)
+{
+ g_return_if_fail (E_IS_TEXT_MODEL (model));
+ g_return_if_fail (fn != NULL);
+
+ g_signal_emit (
+ model, signals[E_TEXT_MODEL_REPOSITION], 0, fn, repos_data);
+}
+
+gint
+e_text_model_validate_position (ETextModel *model,
+ gint pos)
+{
+ ETextModelClass *class;
+
+ g_return_val_if_fail (E_IS_TEXT_MODEL (model), 0);
+
+ class = E_TEXT_MODEL_GET_CLASS (model);
+
+ if (class->validate_pos != NULL)
+ pos = class->validate_pos (model, pos);
+
+ return pos;
+}
+
+const gchar *
+e_text_model_get_text (ETextModel *model)
+{
+ ETextModelClass *class;
+
+ g_return_val_if_fail (E_IS_TEXT_MODEL (model), NULL);
+
+ class = E_TEXT_MODEL_GET_CLASS (model);
+
+ if (class->get_text == NULL)
+ return "";
+
+ return class->get_text (model);
+}
+
+gint
+e_text_model_get_text_length (ETextModel *model)
+{
+ ETextModelClass *class;
+
+ g_return_val_if_fail (E_IS_TEXT_MODEL (model), 0);
+
+ class = E_TEXT_MODEL_GET_CLASS (model);
+
+ if (class->get_text_len (model)) {
+
+ gint len = class->get_text_len (model);
+
+#ifdef PARANOID_DEBUGGING
+ const gchar *str = e_text_model_get_text (model);
+ gint len2 = str ? g_utf8_strlen (str, -1) : 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 ? g_utf8_strlen (str, -1) : 0;
+ }
+}
+
+void
+e_text_model_set_text (ETextModel *model,
+ const gchar *text)
+{
+ ETextModelClass *class;
+
+ g_return_if_fail (E_IS_TEXT_MODEL (model));
+
+ class = E_TEXT_MODEL_GET_CLASS (model);
+
+ if (class->set_text != NULL)
+ class->set_text (model, text);
+}
+
+void
+e_text_model_insert (ETextModel *model,
+ gint position,
+ const gchar *text)
+{
+ ETextModelClass *class;
+
+ g_return_if_fail (E_IS_TEXT_MODEL (model));
+
+ if (text == NULL)
+ return;
+
+ class = E_TEXT_MODEL_GET_CLASS (model);
+
+ if (class->insert != NULL)
+ class->insert (model, position, text);
+}
+
+void
+e_text_model_insert_length (ETextModel *model,
+ gint position,
+ const gchar *text,
+ gint length)
+{
+ ETextModelClass *class;
+
+ g_return_if_fail (E_IS_TEXT_MODEL (model));
+ g_return_if_fail (length >= 0);
+
+ if (text == NULL || length == 0)
+ return;
+
+ class = E_TEXT_MODEL_GET_CLASS (model);
+
+ if (class->insert_length != NULL)
+ class->insert_length (model, position, text, length);
+}
+
+void
+e_text_model_prepend (ETextModel *model,
+ const gchar *text)
+{
+ g_return_if_fail (E_IS_TEXT_MODEL (model));
+
+ if (text == NULL)
+ return;
+
+ e_text_model_insert (model, 0, text);
+}
+
+void
+e_text_model_append (ETextModel *model,
+ const gchar *text)
+{
+ g_return_if_fail (E_IS_TEXT_MODEL (model));
+
+ 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)
+{
+ ETextModelClass *class;
+ gint txt_len;
+
+ 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;
+
+ class = E_TEXT_MODEL_GET_CLASS (model);
+
+ if (class->delete != NULL)
+ class->delete (model, position, length);
+}
+
+gint
+e_text_model_object_count (ETextModel *model)
+{
+ ETextModelClass *class;
+
+ g_return_val_if_fail (E_IS_TEXT_MODEL (model), 0);
+
+ class = E_TEXT_MODEL_GET_CLASS (model);
+
+ if (class->obj_count == NULL)
+ return 0;
+
+ return class->obj_count (model);
+}
+
+const gchar *
+e_text_model_get_nth_object (ETextModel *model,
+ gint n,
+ gint *len)
+{
+ ETextModelClass *class;
+
+ g_return_val_if_fail (E_IS_TEXT_MODEL (model), NULL);
+
+ if (n < 0 || n >= e_text_model_object_count (model))
+ return NULL;
+
+ class = E_TEXT_MODEL_GET_CLASS (model);
+
+ if (class->get_nth_obj == NULL)
+ return NULL;
+
+ return class->get_nth_obj (model, n, len);
+}
+
+gchar *
+e_text_model_strdup_nth_object (ETextModel *model,
+ gint n)
+{
+ const gchar *obj;
+ gint len = 0;
+
+ g_return_val_if_fail (E_IS_TEXT_MODEL (model), NULL);
+
+ obj = e_text_model_get_nth_object (model, n, &len);
+
+ if (obj) {
+ gint byte_len;
+ byte_len = g_utf8_offset_to_pointer (obj, len) - obj;
+ return g_strndup (obj, byte_len);
+ }
+ else {
+ return NULL;
+ }
+}
+
+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;
+
+ g_return_if_fail (E_IS_TEXT_MODEL (model));
+
+ txt = e_text_model_get_text (model);
+ obj = e_text_model_get_nth_object (model, n, &len);
+
+ g_return_if_fail (obj != NULL);
+
+ if (start)
+ *start = g_utf8_pointer_to_offset (txt, obj);
+ if (end)
+ *end = (start ? *start : 0) + len;
+}
+
+gint
+e_text_model_get_object_at_offset (ETextModel *model,
+ gint offset)
+{
+ ETextModelClass *class;
+
+ g_return_val_if_fail (E_IS_TEXT_MODEL (model), -1);
+
+ if (offset < 0 || offset >= e_text_model_get_text_length (model))
+ return -1;
+
+ class = E_TEXT_MODEL_GET_CLASS (model);
+
+ /* If an optimized version has been provided, we use it. */
+ if (class->obj_at_offset != NULL) {
+ return class->obj_at_offset (model, offset);
+
+ } else {
+ /* If not, we fake it.*/
+
+ gint i, N, pos0, pos1;
+
+ 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 -1;
+}
+
+gint
+e_text_model_get_object_at_pointer (ETextModel *model,
+ const gchar *s)
+{
+ 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));
+
+ g_signal_emit (model, signals[E_TEXT_MODEL_OBJECT_ACTIVATED], 0, n);
+}
+
+ETextModel *
+e_text_model_new (void)
+{
+ ETextModel *model = g_object_new (E_TYPE_TEXT_MODEL, NULL);
+ return model;
+}