diff options
Diffstat (limited to 'e-util/e-text-model.c')
-rw-r--r-- | e-util/e-text-model.c | 642 |
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; +} |