aboutsummaryrefslogtreecommitdiffstats
path: root/widgets/text/e-text-model.c
diff options
context:
space:
mode:
Diffstat (limited to 'widgets/text/e-text-model.c')
-rw-r--r--widgets/text/e-text-model.c520
1 files changed, 366 insertions, 154 deletions
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;
}