/*
* 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;
}