Files
evolution/widgets/text/e-text.c
Paul Menzel 5ddad03992 Remove Ctrl + l ( ) character from source files
From ccc980da1fd84ebfca25cf8caf9a5d62333099fc Mon Sep 17 00:00:00 2001
From: Paul Menzel <paulepanter@users.sourceforge.net>
Date: Wed, 28 Sep 2011 10:18:18 +0200
Subject: [PATCH] Remove Ctrl + l () character from source files

The following commits

        git show aac3f2c8
        git show 1510304c
        git show 13cabd9e
        git show 350a7a33
        git show 9b7cc54d
        git show e6972011
        git show 1d3a7938
        git show 934524b9
        git show b2954936
        git show a7f677b5
        git show 4369c400
        git show d509f47a
        git show a6d5818f
        git show c3876df7
        git show 4583098b
        git show 2831ada5
        git show 4e1bce59
        git show 1609f699
        git show 4e4c1676
        git show d6fade43

among others(?) introduced several occurrences of Ctrl + l (). Probably this was caused by the used editor.

These control characters can be searched for using the following command [1].

	$ git grep ^L

[1] http://unstableme.blogspot.com/2009/10/grep-and-print-control-characters-in.html
2011-09-29 21:12:10 +02:00

3564 lines
88 KiB
C

/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* e-text.c - Text item for evolution.
* Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
*
* Authors:
* Chris Lahey <clahey@ximian.com>
* Jon Trowbridge <trow@ximian.com>
*
* A majority of code taken from:
*
* Text item type for GnomeCanvas widget
*
* GnomeCanvas is basically a port of the Tk toolkit's most excellent
* canvas widget. Tk is copyrighted by the Regents of the University
* of California, Sun Microsystems, and other parties.
*
* Copyright (C) 1998 The Free Software Foundation
*
* Author: Federico Mena <federico@nuclecu.unam.mx>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <math.h>
#include <ctype.h>
#include <string.h>
#include <gdk/gdkkeysyms.h>
#include <gtk/gtk.h>
#include "gal-a11y-e-text.h"
#include "misc/e-canvas.h"
#include "misc/e-canvas-utils.h"
#include "e-util/e-unicode.h"
#include <glib/gi18n.h>
#include "e-util/e-text-event-processor-emacs-like.h"
#include "e-util/e-util.h"
#include "e-text.h"
G_DEFINE_TYPE (EText, e_text, GNOME_TYPE_CANVAS_ITEM)
#define BORDER_INDENT 3
#define d(x)
enum {
E_TEXT_CHANGED,
E_TEXT_ACTIVATE,
E_TEXT_KEYPRESS,
E_TEXT_POPULATE_POPUP,
E_TEXT_STYLE_SET,
E_TEXT_LAST_SIGNAL
};
static GQuark e_text_signals[E_TEXT_LAST_SIGNAL] = { 0 };
/* Object argument IDs */
enum {
PROP_0,
PROP_MODEL,
PROP_EVENT_PROCESSOR,
PROP_TEXT,
PROP_BOLD,
PROP_STRIKEOUT,
PROP_ANCHOR,
PROP_JUSTIFICATION,
PROP_CLIP_WIDTH,
PROP_CLIP_HEIGHT,
PROP_CLIP,
PROP_FILL_CLIP_RECTANGLE,
PROP_X_OFFSET,
PROP_Y_OFFSET,
PROP_FILL_COLOR,
PROP_FILL_COLOR_GDK,
PROP_FILL_COLOR_RGBA,
PROP_TEXT_WIDTH,
PROP_TEXT_HEIGHT,
PROP_EDITABLE,
PROP_USE_ELLIPSIS,
PROP_ELLIPSIS,
PROP_LINE_WRAP,
PROP_BREAK_CHARACTERS,
PROP_MAX_LINES,
PROP_WIDTH,
PROP_HEIGHT,
PROP_DRAW_BORDERS,
PROP_ALLOW_NEWLINES,
PROP_DRAW_BACKGROUND,
PROP_DRAW_BUTTON,
PROP_CURSOR_POS,
PROP_IM_CONTEXT,
PROP_HANDLE_POPUP
};
static void e_text_command (ETextEventProcessor *tep,
ETextEventProcessorCommand *command,
gpointer data);
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);
static void calc_height (EText *text);
static gboolean show_pango_rectangle (EText *text, PangoRectangle rect);
static void e_text_do_popup (EText *text, GdkEventButton *button, gint position);
static void e_text_update_primary_selection (EText *text);
static void e_text_paste (EText *text, GdkAtom selection);
static void e_text_insert (EText *text, const gchar *string);
static void e_text_reset_im_context (EText *text);
static void reset_layout_attrs (EText *text);
#if 0
/* GtkEditable Methods */
static void e_text_editable_do_insert_text (GtkEditable *editable,
const gchar *text,
gint length,
gint *position);
static void e_text_editable_do_delete_text (GtkEditable *editable,
gint start_pos,
gint end_pos);
static gchar * e_text_editable_get_chars (GtkEditable *editable,
gint start_pos,
gint end_pos);
static void e_text_editable_set_selection_bounds (GtkEditable *editable,
gint start_pos,
gint end_pos);
static gboolean e_text_editable_get_selection_bounds (GtkEditable *editable,
gint *start_pos,
gint *end_pos);
static void e_text_editable_set_position (GtkEditable *editable,
gint position);
static gint e_text_editable_get_position (GtkEditable *editable);
#endif
/* IM Context Callbacks */
static void e_text_commit_cb (GtkIMContext *context,
const gchar *str,
EText *text);
static void e_text_preedit_changed_cb (GtkIMContext *context,
EText *text);
static gboolean e_text_retrieve_surrounding_cb (GtkIMContext *context,
EText *text);
static gboolean e_text_delete_surrounding_cb (GtkIMContext *context,
gint offset,
gint n_chars,
EText *text);
static GdkAtom clipboard_atom = GDK_NONE;
static void
disconnect_im_context (EText *text)
{
if (!text || !text->im_context)
return;
g_signal_handlers_disconnect_matched (
text->im_context, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, text);
text->im_context_signals_registered = FALSE;
}
/* Dispose handler for the text item */
#if 0
static void
e_text_style_set (EText *text,
GtkStyle *previous_style)
{
if (text->line_wrap) {
text->needs_split_into_lines = 1;
} else {
text->needs_calc_height = 1;
}
e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (text));
}
#endif
static void
e_text_dispose (GObject *object)
{
EText *text;
g_return_if_fail (object != NULL);
g_return_if_fail (E_IS_TEXT (object));
text = E_TEXT (object);
if (text->model_changed_signal_id)
g_signal_handler_disconnect (text->model,
text->model_changed_signal_id);
text->model_changed_signal_id = 0;
if (text->model_repos_signal_id)
g_signal_handler_disconnect (text->model,
text->model_repos_signal_id);
text->model_repos_signal_id = 0;
if (text->model)
g_object_unref (text->model);
text->model = NULL;
if (text->tep_command_id)
g_signal_handler_disconnect (text->tep,
text->tep_command_id);
text->tep_command_id = 0;
if (text->tep)
g_object_unref (text->tep);
text->tep = NULL;
g_free (text->revert);
text->revert = NULL;
if (text->timeout_id) {
g_source_remove (text->timeout_id);
text->timeout_id = 0;
}
if (text->timer) {
g_timer_stop (text->timer);
g_timer_destroy (text->timer);
text->timer = NULL;
}
if (text->dbl_timeout) {
g_source_remove (text->dbl_timeout);
text->dbl_timeout = 0;
}
if (text->tpl_timeout) {
g_source_remove (text->tpl_timeout);
text->tpl_timeout = 0;
}
if (text->layout) {
g_object_unref (text->layout);
text->layout = NULL;
}
if (text->im_context) {
disconnect_im_context (text);
g_object_unref (text->im_context);
text->im_context = NULL;
}
if (text->font_desc) {
pango_font_description_free (text->font_desc);
text->font_desc = NULL;
}
/* Chain up to parent's dispose() method. */
G_OBJECT_CLASS (e_text_parent_class)->dispose (object);
}
static void
insert_preedit_text (EText *text)
{
PangoAttrList *attrs = NULL;
PangoAttrList *preedit_attrs = NULL;
gchar *preedit_string = NULL;
GString *tmp_string = g_string_new (NULL);
gint length = 0, cpos = 0;
gboolean new_attrs = FALSE;
if (text->layout == NULL || !GTK_IS_IM_CONTEXT (text->im_context))
return;
text->text = e_text_model_get_text (text->model);
length = strlen (text->text);
g_string_prepend_len (tmp_string, text->text,length);
/* we came into this function only when text->preedit_len was not 0
* so we can safely fetch the preedit string */
gtk_im_context_get_preedit_string (
text->im_context, &preedit_string, &preedit_attrs, NULL);
if (preedit_string && g_utf8_validate (preedit_string, -1, NULL)) {
text->preedit_len = strlen (preedit_string);
cpos = g_utf8_offset_to_pointer (
text->text, text->selection_start) - text->text;
g_string_insert (tmp_string, cpos, preedit_string);
reset_layout_attrs (text);
attrs = pango_layout_get_attributes (text->layout);
if (!attrs) {
attrs = pango_attr_list_new ();
new_attrs = TRUE;
}
pango_layout_set_text (text->layout, tmp_string->str, tmp_string->len);
pango_attr_list_splice (attrs, preedit_attrs, cpos, text->preedit_len);
if (new_attrs) {
pango_layout_set_attributes (text->layout, attrs);
pango_attr_list_unref (attrs);
}
} else
text->preedit_len = 0;
if (preedit_string)
g_free (preedit_string);
if (preedit_attrs)
pango_attr_list_unref (preedit_attrs);
if (tmp_string)
g_string_free (tmp_string, TRUE);
}
static void
reset_layout_attrs (EText *text)
{
PangoAttrList *attrs = NULL;
gint object_count;
if (text->layout == NULL)
return;
object_count = e_text_model_object_count (text->model);
if (text->bold || text->strikeout || object_count > 0) {
gint length = 0;
gint i;
attrs = pango_attr_list_new ();
for (i = 0; i < object_count; i++) {
gint start_pos, end_pos;
PangoAttribute *attr = pango_attr_underline_new (PANGO_UNDERLINE_SINGLE);
e_text_model_get_nth_object_bounds (text->model, i, &start_pos, &end_pos);
attr->start_index = g_utf8_offset_to_pointer (
text->text, start_pos) - text->text;
attr->end_index = g_utf8_offset_to_pointer (
text->text, end_pos) - text->text;
pango_attr_list_insert (attrs, attr);
}
if (text->bold || text->strikeout)
length = strlen (text->text);
if (text->bold) {
PangoAttribute *attr = pango_attr_weight_new (PANGO_WEIGHT_BOLD);
attr->start_index = 0;
attr->end_index = length;
pango_attr_list_insert_before (attrs, attr);
}
if (text->strikeout) {
PangoAttribute *attr = pango_attr_strikethrough_new (TRUE);
attr->start_index = 0;
attr->end_index = length;
pango_attr_list_insert_before (attrs, attr);
}
}
pango_layout_set_attributes (text->layout, attrs);
if (attrs)
pango_attr_list_unref (attrs);
calc_height (text);
}
static void
create_layout (EText *text)
{
GnomeCanvasItem *item = GNOME_CANVAS_ITEM (text);
if (text->layout)
return;
text->layout = gtk_widget_create_pango_layout (
GTK_WIDGET (item->canvas), text->text);
if (text->line_wrap)
pango_layout_set_width (
text->layout, text->clip_width < 0
? -1 : text->clip_width * PANGO_SCALE);
reset_layout_attrs (text);
}
static void
reset_layout (EText *text)
{
GnomeCanvasItem *item = GNOME_CANVAS_ITEM (text);
if (text->layout == NULL) {
create_layout (text);
}
else {
GtkStyle *style;
style = gtk_widget_get_style (GTK_WIDGET (item->canvas));
if (text->font_desc) {
pango_font_description_free (text->font_desc);
}
text->font_desc = pango_font_description_new ();
if (!pango_font_description_get_size_is_absolute (style->font_desc))
pango_font_description_set_size (text->font_desc,
pango_font_description_get_size (style->font_desc));
else
pango_font_description_set_absolute_size (text->font_desc,
pango_font_description_get_size (style->font_desc));
pango_font_description_set_family (text->font_desc,
pango_font_description_get_family (style->font_desc));
pango_layout_set_font_description (text->layout, text->font_desc);
pango_layout_set_text (text->layout, text->text, -1);
reset_layout_attrs (text);
}
if (!text->button_down) {
PangoRectangle strong_pos, weak_pos;
gchar *offs = g_utf8_offset_to_pointer (text->text, text->selection_start);
pango_layout_get_cursor_pos (
text->layout, offs - text->text,
&strong_pos, &weak_pos);
if (strong_pos.x != weak_pos.x ||
strong_pos.y != weak_pos.y ||
strong_pos.width != weak_pos.width ||
strong_pos.height != weak_pos.height)
show_pango_rectangle (text, weak_pos);
show_pango_rectangle (text, strong_pos);
}
}
static void
e_text_text_model_changed (ETextModel *model,
EText *text)
{
gint model_len = e_text_model_get_text_length (model);
text->text = e_text_model_get_text (model);
/* Make sure our selection doesn't extend past the bounds of our text. */
text->selection_start = CLAMP (text->selection_start, 0, model_len);
text->selection_end = CLAMP (text->selection_end, 0, model_len);
text->needs_reset_layout = 1;
text->needs_split_into_lines = 1;
text->needs_redraw = 1;
e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (text));
gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (text));
g_signal_emit (text, e_text_signals[E_TEXT_CHANGED], 0);
}
static void
e_text_text_model_reposition (ETextModel *model,
ETextModelReposFn fn,
gpointer repos_data,
gpointer user_data)
{
EText *text = E_TEXT (user_data);
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;
}
}
static void
get_bounds (EText *text,
gdouble *px1,
gdouble *py1,
gdouble *px2,
gdouble *py2)
{
GnomeCanvasItem *item;
gdouble wx, wy, clip_width, clip_height;
item = GNOME_CANVAS_ITEM (text);
/* Get canvas pixel coordinates for text position */
wx = 0;
wy = 0;
gnome_canvas_item_i2w (item, &wx, &wy);
gnome_canvas_w2c (item->canvas, wx, wy, &text->cx, &text->cy);
gnome_canvas_w2c (item->canvas, wx, wy, &text->clip_cx, &text->clip_cy);
if (text->clip_width < 0)
clip_width = text->width;
else
clip_width = text->clip_width;
if (text->clip_height < 0)
clip_height = text->height;
else
clip_height = text->clip_height;
/* Get canvas pixel coordinates for clip rectangle position */
text->clip_cwidth = clip_width;
text->clip_cheight = clip_height;
text->text_cx = text->cx;
text->text_cy = text->cy;
if (text->draw_borders) {
text->text_cx += BORDER_INDENT;
text->text_cy += BORDER_INDENT;
}
/* Bounds */
if (text->clip) {
*px1 = text->clip_cx;
*py1 = text->clip_cy;
*px2 = text->clip_cx + text->clip_cwidth;
*py2 = text->clip_cy + text->clip_cheight;
} else {
*px1 = text->cx;
*py1 = text->cy;
*px2 = text->cx + text->width;
*py2 = text->cy + text->height;
}
}
static void
calc_height (EText *text)
{
GnomeCanvasItem *item;
gint old_height;
gint old_width;
gint width = 0;
gint height = 0;
item = GNOME_CANVAS_ITEM (text);
/* Calculate text dimensions */
old_height = text->height;
old_width = text->width;
if (text->layout)
pango_layout_get_pixel_size (text->layout, &width, &height);
text->height = height;
text->width = width;
if (old_height != text->height || old_width != text->width)
e_canvas_item_request_parent_reflow (item);
}
static void
calc_ellipsis (EText *text)
{
/* FIXME: a pango layout per calc_ellipsis sucks */
gint width;
PangoLayout *layout = gtk_widget_create_pango_layout (
GTK_WIDGET (GNOME_CANVAS_ITEM (text)->canvas),
text->ellipsis ? text->ellipsis : "...");
pango_layout_get_size (layout, &width, NULL);
text->ellipsis_width = width;
g_object_unref (layout);
}
static void
split_into_lines (EText *text)
{
text->num_lines = pango_layout_get_line_count (text->layout);
}
/* Set_arg handler for the text item */
static void
e_text_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
GnomeCanvasItem *item;
EText *text;
GdkColor color = { 0, 0, 0, 0, };
GdkColor *pcolor;
gboolean needs_update = 0;
gboolean needs_reflow = 0;
item = GNOME_CANVAS_ITEM (object);
text = E_TEXT (object);
switch (property_id) {
case PROP_MODEL:
if (text->model_changed_signal_id)
g_signal_handler_disconnect (text->model,
text->model_changed_signal_id);
if (text->model_repos_signal_id)
g_signal_handler_disconnect (text->model,
text->model_repos_signal_id);
g_object_unref (text->model);
text->model = E_TEXT_MODEL (g_value_get_object (value));
g_object_ref (text->model);
text->model_changed_signal_id =
g_signal_connect (text->model,
"changed",
G_CALLBACK (e_text_text_model_changed),
text);
text->model_repos_signal_id =
g_signal_connect (text->model,
"reposition",
G_CALLBACK (e_text_text_model_reposition),
text);
text->text = e_text_model_get_text (text->model);
g_signal_emit (text, e_text_signals[E_TEXT_CHANGED], 0);
text->needs_split_into_lines = 1;
needs_reflow = 1;
break;
case PROP_EVENT_PROCESSOR:
if (text->tep && text->tep_command_id)
g_signal_handler_disconnect (text->tep,
text->tep_command_id);
if (text->tep) {
g_object_unref (text->tep);
}
text->tep = E_TEXT_EVENT_PROCESSOR (g_value_get_object (value));
g_object_ref (text->tep);
text->tep_command_id =
g_signal_connect (text->tep,
"command",
G_CALLBACK (e_text_command),
text);
if (!text->allow_newlines)
g_object_set (text->tep,
"allow_newlines", FALSE,
NULL);
break;
case PROP_TEXT:
e_text_model_set_text (text->model, g_value_get_string (value));
break;
case PROP_BOLD:
text->bold = g_value_get_boolean (value);
text->needs_redraw = 1;
text->needs_recalc_bounds = 1;
if (text->line_wrap)
text->needs_split_into_lines = 1;
else {
text->needs_calc_height = 1;
}
needs_update = 1;
needs_reflow = 1;
break;
case PROP_STRIKEOUT:
text->strikeout = g_value_get_boolean (value);
text->needs_redraw = 1;
needs_update = 1;
break;
case PROP_JUSTIFICATION:
text->justification = g_value_get_enum (value);
text->needs_redraw = 1;
needs_update = 1;
break;
case PROP_CLIP_WIDTH:
text->clip_width = fabs (g_value_get_double (value));
calc_ellipsis (text);
if (text->line_wrap) {
if (text->layout)
pango_layout_set_width (
text->layout, text->clip_width < 0
? -1 : text->clip_width * PANGO_SCALE);
text->needs_split_into_lines = 1;
} else {
text->needs_calc_height = 1;
}
needs_reflow = 1;
break;
case PROP_CLIP_HEIGHT:
text->clip_height = fabs (g_value_get_double (value));
text->needs_recalc_bounds = 1;
/* toshok: kind of a hack - set needs_reset_layout
* here so when something about the style/them
* changes, we redraw the text at the proper size/with
* the proper font. */
text->needs_reset_layout = 1;
needs_reflow = 1;
break;
case PROP_CLIP:
text->clip = g_value_get_boolean (value);
calc_ellipsis (text);
if (text->line_wrap)
text->needs_split_into_lines = 1;
else {
text->needs_calc_height = 1;
}
needs_reflow = 1;
break;
case PROP_FILL_CLIP_RECTANGLE:
text->fill_clip_rectangle = g_value_get_boolean (value);
needs_update = 1;
break;
case PROP_X_OFFSET:
text->xofs = g_value_get_double (value);
text->needs_recalc_bounds = 1;
needs_update = 1;
break;
case PROP_Y_OFFSET:
text->yofs = g_value_get_double (value);
text->needs_recalc_bounds = 1;
needs_update = 1;
break;
case PROP_FILL_COLOR:
if (g_value_get_string (value))
gdk_color_parse (g_value_get_string (value), &color);
text->rgba = ((color.red & 0xff00) << 16 |
(color.green & 0xff00) << 8 |
(color.blue & 0xff00) |
0xff);
text->needs_redraw = 1;
needs_update = 1;
break;
case PROP_FILL_COLOR_GDK:
pcolor = g_value_get_boxed (value);
if (pcolor) {
color = *pcolor;
}
text->rgba = ((color.red & 0xff00) << 16 |
(color.green & 0xff00) << 8 |
(color.blue & 0xff00) |
0xff);
text->needs_redraw = 1;
needs_update = 1;
break;
case PROP_FILL_COLOR_RGBA:
text->rgba = g_value_get_uint (value);
color.red = ((text->rgba >> 24) & 0xff) * 0x101;
color.green = ((text->rgba >> 16) & 0xff) * 0x101;
color.blue = ((text->rgba >> 8) & 0xff) * 0x101;
text->needs_redraw = 1;
needs_update = 1;
break;
case PROP_EDITABLE:
text->editable = g_value_get_boolean (value);
text->needs_redraw = 1;
needs_update = 1;
break;
case PROP_USE_ELLIPSIS:
text->use_ellipsis = g_value_get_boolean (value);
needs_reflow = 1;
break;
case PROP_ELLIPSIS:
if (text->ellipsis)
g_free (text->ellipsis);
text->ellipsis = g_strdup (g_value_get_string (value));
calc_ellipsis (text);
needs_reflow = 1;
break;
case PROP_LINE_WRAP:
text->line_wrap = g_value_get_boolean (value);
if (text->line_wrap) {
if (text->layout) {
pango_layout_set_width (
text->layout, text->width < 0
? -1 : text->width * PANGO_SCALE);
}
}
text->needs_split_into_lines = 1;
needs_reflow = 1;
break;
case PROP_BREAK_CHARACTERS:
if (text->break_characters) {
g_free (text->break_characters);
text->break_characters = NULL;
}
if (g_value_get_string (value))
text->break_characters = g_strdup ( g_value_get_string (value) );
text->needs_split_into_lines = 1;
needs_reflow = 1;
break;
case PROP_MAX_LINES:
text->max_lines = g_value_get_int (value);
text->needs_split_into_lines = 1;
needs_reflow = 1;
break;
case PROP_WIDTH:
text->clip_width = fabs (g_value_get_double (value));
calc_ellipsis (text);
if (text->line_wrap) {
if (text->layout) {
pango_layout_set_width (
text->layout, text->width < 0 ?
-1 : text->width * PANGO_SCALE);
}
text->needs_split_into_lines = 1;
}
else {
text->needs_calc_height = 1;
}
needs_reflow = 1;
break;
case PROP_DRAW_BORDERS:
if (text->draw_borders != g_value_get_boolean (value)) {
text->draw_borders = g_value_get_boolean (value);
text->needs_calc_height = 1;
text->needs_redraw = 1;
needs_reflow = 1;
needs_update = 1;
}
break;
case PROP_DRAW_BACKGROUND:
if (text->draw_background != g_value_get_boolean (value)) {
text->draw_background = g_value_get_boolean (value);
text->needs_redraw = 1;
}
break;
case PROP_DRAW_BUTTON:
if (text->draw_button != g_value_get_boolean (value)) {
text->draw_button = g_value_get_boolean (value);
text->needs_redraw = 1;
}
break;
case PROP_ALLOW_NEWLINES:
text->allow_newlines = g_value_get_boolean (value);
_get_tep (text);
g_object_set (text->tep,
"allow_newlines", g_value_get_boolean (value),
NULL);
break;
case PROP_CURSOR_POS: {
ETextEventProcessorCommand command;
command.action = E_TEP_MOVE;
command.position = E_TEP_VALUE;
command.value = g_value_get_int (value);
command.time = GDK_CURRENT_TIME;
e_text_command (text->tep, &command, text);
break;
}
case PROP_IM_CONTEXT:
if (text->im_context) {
disconnect_im_context (text);
g_object_unref (text->im_context);
}
text->im_context = g_value_get_object (value);
if (text->im_context)
g_object_ref (text->im_context);
text->need_im_reset = TRUE;
break;
case PROP_HANDLE_POPUP:
text->handle_popup = g_value_get_boolean (value);
break;
default:
return;
}
if (needs_reflow)
e_canvas_item_request_reflow (item);
if (needs_update)
gnome_canvas_item_request_update (item);
}
/* Get_arg handler for the text item */
static void
e_text_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
EText *text;
text = E_TEXT (object);
switch (property_id) {
case PROP_MODEL:
g_value_set_object (value, text->model);
break;
case PROP_EVENT_PROCESSOR:
_get_tep (text);
g_value_set_object (value, text->tep);
break;
case PROP_TEXT:
g_value_set_string (value, text->text);
break;
case PROP_BOLD:
g_value_set_boolean (value, text->bold);
break;
case PROP_STRIKEOUT:
g_value_set_boolean (value, text->strikeout);
break;
case PROP_JUSTIFICATION:
g_value_set_enum (value, text->justification);
break;
case PROP_CLIP_WIDTH:
g_value_set_double (value, text->clip_width);
break;
case PROP_CLIP_HEIGHT:
g_value_set_double (value, text->clip_height);
break;
case PROP_CLIP:
g_value_set_boolean (value, text->clip);
break;
case PROP_FILL_CLIP_RECTANGLE:
g_value_set_boolean (value, text->fill_clip_rectangle);
break;
case PROP_X_OFFSET:
g_value_set_double (value, text->xofs);
break;
case PROP_Y_OFFSET:
g_value_set_double (value, text->yofs);
break;
case PROP_FILL_COLOR_RGBA:
g_value_set_uint (value, text->rgba);
break;
case PROP_TEXT_WIDTH:
g_value_set_double (value, text->width);
break;
case PROP_TEXT_HEIGHT:
g_value_set_double (value, text->height);
break;
case PROP_EDITABLE:
g_value_set_boolean (value, text->editable);
break;
case PROP_USE_ELLIPSIS:
g_value_set_boolean (value, text->use_ellipsis);
break;
case PROP_ELLIPSIS:
g_value_set_string (value, text->ellipsis);
break;
case PROP_LINE_WRAP:
g_value_set_boolean (value, text->line_wrap);
break;
case PROP_BREAK_CHARACTERS:
g_value_set_string (value, text->break_characters);
break;
case PROP_MAX_LINES:
g_value_set_int (value, text->max_lines);
break;
case PROP_WIDTH:
g_value_set_double (value, text->clip_width);
break;
case PROP_HEIGHT:
g_value_set_double (
value, text->clip &&
text->clip_height != -1 ?
text->clip_height : text->height);
break;
case PROP_DRAW_BORDERS:
g_value_set_boolean (value, text->draw_borders);
break;
case PROP_DRAW_BACKGROUND:
g_value_set_boolean (value, text->draw_background);
break;
case PROP_DRAW_BUTTON:
g_value_set_boolean (value, text->draw_button);
break;
case PROP_ALLOW_NEWLINES:
g_value_set_boolean (value, text->allow_newlines);
break;
case PROP_CURSOR_POS:
g_value_set_int (value, text->selection_start);
break;
case PROP_IM_CONTEXT:
g_value_set_object (value, text->im_context);
break;
case PROP_HANDLE_POPUP:
g_value_set_boolean (value, text->handle_popup);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
/* Update handler for the text item */
static void
e_text_reflow (GnomeCanvasItem *item,
gint flags)
{
EText *text;
text = E_TEXT (item);
if (text->needs_reset_layout) {
reset_layout (text);
text->needs_reset_layout = 0;
text->needs_calc_height = 1;
}
if (text->needs_split_into_lines) {
split_into_lines (text);
text->needs_split_into_lines = 0;
text->needs_calc_height = 1;
}
if (text->needs_calc_height) {
calc_height (text);
gnome_canvas_item_request_update (item);
text->needs_calc_height = 0;
text->needs_recalc_bounds = 1;
}
}
/* Update handler for the text item */
static void
e_text_update (GnomeCanvasItem *item,
const cairo_matrix_t *i2c,
gint flags)
{
EText *text;
gdouble x1, y1, x2, y2;
text = E_TEXT (item);
if (GNOME_CANVAS_ITEM_CLASS (e_text_parent_class)->update)
GNOME_CANVAS_ITEM_CLASS (e_text_parent_class)->update (
item, i2c, flags);
if ( text->needs_recalc_bounds
|| (flags & GNOME_CANVAS_UPDATE_AFFINE)) {
get_bounds (text, &x1, &y1, &x2, &y2);
if ( item->x1 != x1 ||
item->x2 != x2 ||
item->y1 != y1 ||
item->y2 != y2 ) {
gnome_canvas_request_redraw (
item->canvas, item->x1, item->y1,
item->x2, item->y2);
item->x1 = x1;
item->y1 = y1;
item->x2 = x2;
item->y2 = y2;
text->needs_redraw = 1;
item->canvas->need_repick = TRUE;
}
if (!text->fill_clip_rectangle)
item->canvas->need_repick = TRUE;
text->needs_recalc_bounds = 0;
}
if (text->needs_redraw) {
gnome_canvas_request_redraw (
item->canvas, item->x1, item->y1, item->x2, item->y2);
text->needs_redraw = 0;
}
}
/* Realize handler for the text item */
static void
e_text_realize (GnomeCanvasItem *item)
{
EText *text;
text = E_TEXT (item);
if (GNOME_CANVAS_ITEM_CLASS (e_text_parent_class)->realize)
(* GNOME_CANVAS_ITEM_CLASS (e_text_parent_class)->realize) (item);
create_layout (text);
text->i_cursor = gdk_cursor_new (GDK_XTERM);
text->default_cursor = gdk_cursor_new (GDK_LEFT_PTR);
}
/* Unrealize handler for the text item */
static void
e_text_unrealize (GnomeCanvasItem *item)
{
EText *text;
text = E_TEXT (item);
g_object_unref (text->i_cursor);
text->i_cursor = NULL;
g_object_unref (text->default_cursor);
text->default_cursor = NULL;
if (GNOME_CANVAS_ITEM_CLASS (e_text_parent_class)->unrealize)
(* GNOME_CANVAS_ITEM_CLASS (e_text_parent_class)->unrealize) (item);
}
static void
_get_tep (EText *text)
{
if (!text->tep) {
text->tep = e_text_event_processor_emacs_like_new ();
text->tep_command_id =
g_signal_connect (text->tep,
"command",
G_CALLBACK (e_text_command),
text);
}
}
static void
draw_pango_rectangle (cairo_t *cr,
gint x1,
gint y1,
PangoRectangle rect)
{
gint width = rect.width / PANGO_SCALE;
gint height = rect.height / PANGO_SCALE;
if (width <= 0)
width = 1;
if (height <= 0)
height = 1;
cairo_rectangle (
cr, x1 + rect.x / PANGO_SCALE,
y1 + rect.y / PANGO_SCALE, width, height);
cairo_fill (cr);
}
static gboolean
show_pango_rectangle (EText *text,
PangoRectangle rect)
{
gint x1 = rect.x / PANGO_SCALE;
gint x2 = (rect.x + rect.width) / PANGO_SCALE;
gint y1 = rect.y / PANGO_SCALE;
gint y2 = (rect.y + rect.height) / PANGO_SCALE;
gint new_xofs_edit = text->xofs_edit;
gint new_yofs_edit = text->yofs_edit;
gint clip_width, clip_height;
clip_width = text->clip_width;
if (clip_width >= 0 && text->draw_borders) {
clip_width -= 6;
if (clip_width < 0)
clip_width = 0;
}
clip_height = text->clip_height;
if (clip_height >= 0 && text->draw_borders) {
clip_height -= 6;
if (clip_height < 0)
clip_height = 0;
}
if (x1 < new_xofs_edit)
new_xofs_edit = x1;
if (y1 < new_yofs_edit)
new_yofs_edit = y1;
if (clip_width >= 0) {
if (2 + x2 - clip_width > new_xofs_edit)
new_xofs_edit = 2 + x2 - clip_width;
} else {
new_xofs_edit = 0;
}
if (clip_height >= 0) {
if (y2 - clip_height > new_yofs_edit)
new_yofs_edit = y2 - clip_height;
} else {
new_yofs_edit = 0;
}
if (new_xofs_edit < 0)
new_xofs_edit = 0;
if (new_yofs_edit < 0)
new_yofs_edit = 0;
if (new_xofs_edit != text->xofs_edit ||
new_yofs_edit != text->yofs_edit) {
text->xofs_edit = new_xofs_edit;
text->yofs_edit = new_yofs_edit;
return TRUE;
}
return FALSE;
}
/* Draw handler for the text item */
static void
e_text_draw (GnomeCanvasItem *item,
cairo_t *cr,
gint x,
gint y,
gint width,
gint height)
{
EText *text;
gint xpos, ypos;
GnomeCanvas *canvas;
GtkWidget *widget;
GtkStyle *style;
GtkStateType state;
text = E_TEXT (item);
canvas = GNOME_CANVAS_ITEM (text)->canvas;
widget = GTK_WIDGET (canvas);
state = gtk_widget_get_state (widget);
style = gtk_widget_get_style (widget);
cairo_save (cr);
if (text->draw_background || text->draw_button) {
gdk_cairo_set_source_color (cr, &style->fg[state]);
} else {
cairo_set_source_rgba (cr,
((text->rgba >> 24) & 0xff) / 255.0,
((text->rgba >> 16) & 0xff) / 255.0,
((text->rgba >> 8) & 0xff) / 255.0,
( text->rgba & 0xff) / 255.0);
}
if (text->draw_borders || text->draw_background) {
gdouble thisx = item->x1 - x;
gdouble thisy = item->y1 - y;
gdouble thiswidth, thisheight;
widget = GTK_WIDGET (item->canvas);
g_object_get (text,
"width", &thiswidth,
"height", &thisheight,
NULL);
if (text->draw_borders) {
gtk_paint_shadow (style, cr,
GTK_STATE_NORMAL, GTK_SHADOW_IN,
widget, "entry",
thisx, thisy, thiswidth, thisheight);
}
if (text->draw_background) {
gtk_paint_flat_box (style, cr,
state, GTK_SHADOW_NONE,
widget, "entry_bg",
thisx + style->xthickness,
thisy + style->ythickness,
thiswidth - style->xthickness * 2,
thisheight - style->ythickness * 2);
}
}
if (text->draw_button) {
GtkAllocation allocation;
gint xoff = item->x1 - x;
gint yoff = item->y1 - y;
widget = GTK_WIDGET (item->canvas);
gtk_widget_get_allocation (widget, &allocation);
xoff -= allocation.x;
yoff -= allocation.y;
widget = gtk_widget_get_parent (widget);
while (widget && !GTK_IS_BUTTON (widget)) {
if (gtk_widget_get_has_window (widget)) {
widget = NULL;
break;
}
widget = gtk_widget_get_parent (widget);
}
if (widget) {
GtkShadowType shadow_type;
GtkAllocation allocation;
GtkReliefStyle relief;
guint border_width;
gint thisx, thisy, thisheight, thiswidth;
gint default_spacing;
gtk_widget_get_allocation (widget, &allocation);
relief = gtk_button_get_relief (GTK_BUTTON (widget));
border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
#define DEFAULT_SPACING 7
#if 0
default_spacing = gtk_style_get_prop_experimental (style,
"GtkButton::default_spacing",
DEFAULT_SPACING);
#endif
default_spacing = 7;
thisx = 0;
thisy = 0;
thiswidth = allocation.width - border_width * 2;
thisheight = allocation.height - border_width * 2;
if (gtk_widget_has_default (widget) &&
relief == GTK_RELIEF_NORMAL)
{
gtk_paint_box (style, cr,
GTK_STATE_NORMAL, GTK_SHADOW_IN,
widget, "buttondefault",
thisx + xoff, thisy + yoff, thiswidth, thisheight);
}
if (gtk_widget_get_can_default (widget)) {
thisx += style->xthickness;
thisy += style->ythickness;
thiswidth -= 2 * thisx + default_spacing;
thisheight -= 2 * thisy + default_spacing;
thisx += (1 + default_spacing) / 2;
thisy += (1 + default_spacing) / 2;
}
if (gtk_widget_has_focus (widget)) {
thisx += 1;
thisy += 1;
thiswidth -= 2;
thisheight -= 2;
}
if (state == GTK_STATE_ACTIVE)
shadow_type = GTK_SHADOW_IN;
else
shadow_type = GTK_SHADOW_OUT;
if ((relief != GTK_RELIEF_NONE) ||
((state != GTK_STATE_NORMAL) &&
(state != GTK_STATE_INSENSITIVE)))
gtk_paint_box (style, cr, state,
shadow_type, widget, "button",
thisx + xoff, thisy + yoff,
thiswidth, thisheight);
if (gtk_widget_has_focus (widget)) {
thisx -= 1;
thisy -= 1;
thiswidth += 2;
thisheight += 2;
gtk_paint_focus (style, cr, state,
widget, "button",
thisx + xoff, thisy + yoff,
thiswidth - 1, thisheight - 1);
}
}
}
/* Insert preedit text only when im_context signals are connected &
* text->preedit_len is not zero */
if (text->im_context_signals_registered && text->preedit_len)
insert_preedit_text (text);
/* Need to reset the layout to cleanly clear the preedit buffer when
* typing in CJK & using backspace on the preedit */
if (!text->preedit_len)
reset_layout (text);
if (!pango_layout_get_text (text->layout)) {
cairo_restore (cr);
return;
}
xpos = text->text_cx;
ypos = text->text_cy;
xpos = xpos - x + text->xofs;
ypos = ypos - y + text->yofs;
cairo_save (cr);
if (text->clip) {
cairo_rectangle (cr,
xpos, ypos,
text->clip_cwidth - text->xofs,
text->clip_cheight - text->yofs);
cairo_clip (cr);
}
if (text->editing) {
xpos -= text->xofs_edit;
ypos -= text->yofs_edit;
}
cairo_move_to (cr, xpos, ypos);
pango_cairo_show_layout (cr, text->layout);
if (text->editing) {
if (text->selection_start != text->selection_end) {
cairo_region_t *clip_region = cairo_region_create ();
gint indices[2];
GtkStateType state;
if (text->has_selection)
state = GTK_STATE_SELECTED;
else
state = GTK_STATE_ACTIVE;
indices[0] = MIN (
text->selection_start,
text->selection_end);
indices[1] = MAX (
text->selection_start,
text->selection_end);
/* convert these into byte indices */
indices[0] = g_utf8_offset_to_pointer (
text->text, indices[0]) - text->text;
indices[1] = g_utf8_offset_to_pointer (
text->text, indices[1]) - text->text;
clip_region = gdk_pango_layout_get_clip_region (
text->layout, xpos, ypos, indices, 1);
gdk_cairo_region (cr, clip_region);
cairo_clip (cr);
cairo_region_destroy (clip_region);
gdk_cairo_set_source_color (cr, &style->base[state]);
cairo_paint (cr);
gdk_cairo_set_source_color (cr, &style->text[state]);
cairo_move_to (cr, xpos, ypos);
pango_cairo_show_layout (cr, text->layout);
} else {
if (text->show_cursor) {
PangoRectangle strong_pos, weak_pos;
gchar *offs;
offs = g_utf8_offset_to_pointer (
text->text, text->selection_start);
pango_layout_get_cursor_pos (
text->layout, offs - text->text +
text->preedit_len, &strong_pos,
&weak_pos);
draw_pango_rectangle (cr, xpos, ypos, strong_pos);
if (strong_pos.x != weak_pos.x ||
strong_pos.y != weak_pos.y ||
strong_pos.width != weak_pos.width ||
strong_pos.height != weak_pos.height)
draw_pango_rectangle (cr, xpos, ypos, weak_pos);
}
}
}
cairo_restore (cr);
cairo_restore (cr);
}
/* Point handler for the text item */
static GnomeCanvasItem *
e_text_point (GnomeCanvasItem *item,
gdouble x,
gdouble y,
gint cx,
gint cy)
{
EText *text;
gdouble clip_width;
gdouble clip_height;
text = E_TEXT (item);
/* The idea is to build bounding rectangles for each of the lines of
* text (clipped by the clipping rectangle, if it is activated) and see
* whether the point is inside any of these. If it is, we are done.
* Otherwise, calculate the distance to the nearest rectangle.
*/
if (text->clip_width < 0)
clip_width = text->width;
else
clip_width = text->clip_width;
if (text->clip_height < 0)
clip_height = text->height;
else
clip_height = text->clip_height;
if (cx < text->clip_cx ||
cx > text->clip_cx + clip_width ||
cy < text->clip_cy ||
cy > text->clip_cy + clip_height)
return NULL;
if (text->fill_clip_rectangle || !text->text || !*text->text)
return item;
cx -= text->cx;
if (pango_layout_xy_to_index (text->layout, cx, cy, NULL, NULL))
return item;
return NULL;
}
/* Bounds handler for the text item */
static void
e_text_bounds (GnomeCanvasItem *item,
gdouble *x1,
gdouble *y1,
gdouble *x2,
gdouble *y2)
{
EText *text;
gdouble width, height;
text = E_TEXT (item);
*x1 = 0;
*y1 = 0;
width = text->width;
height = text->height;
if (text->clip) {
if (text->clip_width >= 0)
width = text->clip_width;
if (text->clip_height >= 0)
height = text->clip_height;
}
*x2 = *x1 + width;
*y2 = *y1 + height;
}
static gint
get_position_from_xy (EText *text,
gint x,
gint y)
{
gint index;
gint trailing;
if (text->draw_borders) {
x -= BORDER_INDENT;
y -= BORDER_INDENT;
}
x -= text->xofs;
y -= text->yofs;
if (text->editing) {
x += text->xofs_edit;
y += text->yofs_edit;
}
x -= text->cx;
y -= text->cy;
pango_layout_xy_to_index (
text->layout, x * PANGO_SCALE,
y * PANGO_SCALE, &index, &trailing);
return g_utf8_pointer_to_offset (text->text, text->text + index + trailing);
}
#define SCROLL_WAIT_TIME 30000
static gboolean
_blink_scroll_timeout (gpointer data)
{
EText *text = E_TEXT (data);
gulong current_time;
gboolean scroll = FALSE;
gboolean redraw = FALSE;
g_timer_elapsed (text->timer, &current_time);
if (text->scroll_start + SCROLL_WAIT_TIME > 1000000) {
if (current_time > text->scroll_start - (1000000 - SCROLL_WAIT_TIME) &&
current_time < text->scroll_start)
scroll = TRUE;
} else {
if (current_time > text->scroll_start + SCROLL_WAIT_TIME ||
current_time < text->scroll_start)
scroll = TRUE;
}
if (scroll && text->button_down && text->clip) {
gint old_xofs_edit = text->xofs_edit;
gint old_yofs_edit = text->yofs_edit;
if (text->clip_cwidth >= 0 &&
text->lastx - text->clip_cx > text->clip_cwidth &&
text->xofs_edit < text->width - text->clip_cwidth) {
text->xofs_edit += 4;
if (text->xofs_edit > text->width - text->clip_cwidth + 1)
text->xofs_edit = text->width - text->clip_cwidth + 1;
}
if (text->lastx - text->clip_cx < 0 &&
text->xofs_edit > 0) {
text->xofs_edit -= 4;
if (text->xofs_edit < 0)
text->xofs_edit = 0;
}
if (text->clip_cheight >= 0 &&
text->lasty - text->clip_cy > text->clip_cheight &&
text->yofs_edit < text->height - text->clip_cheight) {
text->yofs_edit += 4;
if (text->yofs_edit > text->height - text->clip_cheight + 1)
text->yofs_edit = text->height - text->clip_cheight + 1;
}
if (text->lasty - text->clip_cy < 0 &&
text->yofs_edit > 0) {
text->yofs_edit -= 4;
if (text->yofs_edit < 0)
text->yofs_edit = 0;
}
if (old_xofs_edit != text->xofs_edit ||
old_yofs_edit != text->yofs_edit) {
ETextEventProcessorEvent e_tep_event;
e_tep_event.type = GDK_MOTION_NOTIFY;
e_tep_event.motion.state = text->last_state;
e_tep_event.motion.time = 0;
e_tep_event.motion.position =
get_position_from_xy (
text, text->lastx, text->lasty);
_get_tep (text);
e_text_event_processor_handle_event (text->tep,
&e_tep_event);
text->scroll_start = current_time;
redraw = TRUE;
}
}
if (!((current_time / 500000) % 2)) {
if (!text->show_cursor)
redraw = TRUE;
text->show_cursor = TRUE;
} else {
if (text->show_cursor)
redraw = TRUE;
text->show_cursor = FALSE;
}
if (redraw) {
text->needs_redraw = 1;
gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (text));
}
return TRUE;
}
static void
start_editing (EText *text)
{
if (text->editing)
return;
e_text_reset_im_context (text);
g_free (text->revert);
text->revert = g_strdup (text->text);
text->editing = TRUE;
if (text->pointer_in) {
GdkWindow *window;
window = gtk_widget_get_window (
GTK_WIDGET (GNOME_CANVAS_ITEM (text)->canvas));
if (text->default_cursor_shown && (!text->draw_borders)) {
gdk_window_set_cursor (window, text->i_cursor);
text->default_cursor_shown = FALSE;
}
}
text->select_by_word = FALSE;
text->xofs_edit = 0;
text->yofs_edit = 0;
if (text->timeout_id == 0)
text->timeout_id = g_timeout_add (10, _blink_scroll_timeout, text);
text->timer = g_timer_new ();
g_timer_elapsed (text->timer, &(text->scroll_start));
g_timer_start (text->timer);
}
void
e_text_stop_editing (EText *text)
{
if (!text->editing)
return;
g_free (text->revert);
text->revert = NULL;
text->editing = FALSE;
if ((!text->default_cursor_shown) && (!text->draw_borders)) {
GdkWindow *window;
window = gtk_widget_get_window (
GTK_WIDGET (GNOME_CANVAS_ITEM (text)->canvas));
gdk_window_set_cursor (window, text->default_cursor);
text->default_cursor_shown = TRUE;
}
if (text->timer) {
g_timer_stop (text->timer);
g_timer_destroy (text->timer);
text->timer = NULL;
}
text->need_im_reset = TRUE;
text->preedit_len = 0;
text->preedit_pos = 0;
}
void
e_text_cancel_editing (EText *text)
{
if (text->revert)
e_text_model_set_text (text->model, text->revert);
e_text_stop_editing (text);
}
static gboolean
_click (gpointer data)
{
*(gint *)data = 0;
return FALSE;
}
static gint
e_text_event (GnomeCanvasItem *item,
GdkEvent *event)
{
EText *text = E_TEXT (item);
ETextEventProcessorEvent e_tep_event;
GdkWindow *window;
gint return_val = 0;
if (!text->model)
return 0;
window = gtk_widget_get_window (GTK_WIDGET (item->canvas));
e_tep_event.type = event->type;
switch (event->type) {
case GDK_FOCUS_CHANGE:
if (text->editable) {
GdkEventFocus *focus_event;
focus_event = (GdkEventFocus *) event;
if (focus_event->in) {
if (text->im_context) {
if (!text->im_context_signals_registered) {
g_signal_connect (text->im_context, "commit",
G_CALLBACK (e_text_commit_cb), text);
g_signal_connect (text->im_context, "preedit_changed",
G_CALLBACK (e_text_preedit_changed_cb), text);
g_signal_connect (text->im_context, "retrieve_surrounding",
G_CALLBACK (e_text_retrieve_surrounding_cb), text);
g_signal_connect (text->im_context, "delete_surrounding",
G_CALLBACK (e_text_delete_surrounding_cb), text);
text->im_context_signals_registered = TRUE;
}
gtk_im_context_focus_in (text->im_context);
}
start_editing (text);
/* So we'll redraw and the
* cursor will be shown. */
text->show_cursor = FALSE;
} else {
if (text->im_context) {
gtk_im_context_focus_out (text->im_context);
disconnect_im_context (text);
text->need_im_reset = TRUE;
}
e_text_stop_editing (text);
if (text->timeout_id) {
g_source_remove (text->timeout_id);
text->timeout_id = 0;
}
if (text->show_cursor || text->draw_borders) {
text->show_cursor = FALSE;
text->needs_redraw = 1;
gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (text));
}
}
if (text->line_wrap)
text->needs_split_into_lines = 1;
e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (text));
}
return_val = 0;
break;
case GDK_KEY_PRESS:
/* Handle S-F10 key binding here. */
if (event->key.keyval == GDK_KEY_F10
&& (event->key.state & GDK_SHIFT_MASK)
&& text->handle_popup ) {
/* Simulate a GdkEventButton here, so that we can
* call e_text_do_popup directly */
GdkEventButton *button;
button = (GdkEventButton *)
gdk_event_new (GDK_BUTTON_PRESS);
button->time = event->key.time;
button->button = 0;
e_text_do_popup (text, button, 0);
return 1;
}
/* Fall Through */
case GDK_KEY_RELEASE:
if (text->editing) {
GdkEventKey key;
gint ret;
if (text->im_context &&
gtk_im_context_filter_keypress (
text->im_context,
(GdkEventKey *) event)) {
text->need_im_reset = TRUE;
return 1;
}
key = event->key;
e_tep_event.key.time = key.time;
e_tep_event.key.state = key.state;
e_tep_event.key.keyval = 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;
e_tep_event.key.string = key.string;
#else
e_tep_event.key.string = e_utf8_from_gtk_event_key (
GTK_WIDGET (item->canvas),
key.keyval, key.string);
if (e_tep_event.key.string != NULL) {
e_tep_event.key.length = strlen (e_tep_event.key.string);
} else {
e_tep_event.key.length = 0;
}
#endif
_get_tep (text);
ret = e_text_event_processor_handle_event (text->tep, &e_tep_event);
if (event->type == GDK_KEY_PRESS)
g_signal_emit (text, e_text_signals[E_TEXT_KEYPRESS], 0,
e_tep_event.key.keyval, e_tep_event.key.state);
if (e_tep_event.key.string)
g_free ((gpointer) e_tep_event.key.string);
return ret;
}
break;
case GDK_BUTTON_PRESS: /* Fall Through */
case GDK_BUTTON_RELEASE:
#if 0
if ((!text->editing)
&& text->editable
&& event->type == GDK_BUTTON_RELEASE
&& event->button.button == 1) {
GdkEventButton button = event->button;
e_canvas_item_grab_focus (item, TRUE);
e_tep_event.type = GDK_BUTTON_RELEASE;
e_tep_event.button.time = button.time;
e_tep_event.button.state = button.state;
e_tep_event.button.button = button.button;
e_tep_event.button.position =
get_position_from_xy (
text, button.x, button.y);
_get_tep (text);
return_val = e_text_event_processor_handle_event (
text->tep, &e_tep_event);
e_tep_event.type = GDK_BUTTON_RELEASE;
}
#else
if ((!text->editing)
&& text->editable
&& (event->button.button == 1 ||
event->button.button == 2)) {
e_canvas_item_grab_focus (item, TRUE);
start_editing (text);
}
#endif
/* We follow convention and emit popup events on right-clicks. */
if (event->type == GDK_BUTTON_PRESS && event->button.button == 3) {
if (text->handle_popup) {
e_text_do_popup (
text, &(event->button),
get_position_from_xy (
text, event->button.x,
event->button.y));
return 1;
}
else {
break;
}
}
/* Create our own double and triple click events,
* as gnome-canvas doesn't forward them to us */
if (event->type == GDK_BUTTON_PRESS) {
if (text->dbl_timeout == 0 &&
text->tpl_timeout == 0) {
text->dbl_timeout = g_timeout_add (
200, _click, &(text->dbl_timeout));
} else {
if (text->tpl_timeout == 0) {
e_tep_event.type = GDK_2BUTTON_PRESS;
text->tpl_timeout = g_timeout_add (
200, _click, &(text->tpl_timeout));
} else {
e_tep_event.type = GDK_3BUTTON_PRESS;
}
}
}
if (text->editing) {
GdkEventButton button = event->button;
e_tep_event.button.time = button.time;
e_tep_event.button.state = button.state;
e_tep_event.button.button = button.button;
e_tep_event.button.position =
get_position_from_xy (
text, button.x, button.y);
_get_tep (text);
return_val = e_text_event_processor_handle_event (
text->tep, &e_tep_event);
if (event->button.button == 1) {
if (event->type == GDK_BUTTON_PRESS)
text->button_down = TRUE;
else
text->button_down = FALSE;
}
text->lastx = button.x;
text->lasty = button.y;
text->last_state = button.state;
}
break;
case GDK_MOTION_NOTIFY:
if (text->editing) {
GdkEventMotion motion = event->motion;
e_tep_event.motion.time = motion.time;
e_tep_event.motion.state = motion.state;
e_tep_event.motion.position =
get_position_from_xy (
text, motion.x, motion.y);
_get_tep (text);
return_val = e_text_event_processor_handle_event (
text->tep, &e_tep_event);
text->lastx = motion.x;
text->lasty = motion.y;
text->last_state = motion.state;
}
break;
case GDK_ENTER_NOTIFY:
text->pointer_in = TRUE;
if (text->editing || text->draw_borders) {
if (text->default_cursor_shown) {
gdk_window_set_cursor (window, text->i_cursor);
text->default_cursor_shown = FALSE;
}
}
break;
case GDK_LEAVE_NOTIFY:
text->pointer_in = FALSE;
if (text->editing || text->draw_borders) {
if (!text->default_cursor_shown) {
gdk_window_set_cursor (
window, text->default_cursor);
text->default_cursor_shown = TRUE;
}
}
break;
default:
break;
}
if (return_val)
return return_val;
if (GNOME_CANVAS_ITEM_CLASS (e_text_parent_class)->event)
return GNOME_CANVAS_ITEM_CLASS (e_text_parent_class)->event (item, event);
else
return 0;
}
void
e_text_copy_clipboard (EText *text)
{
gint selection_start_pos;
gint selection_end_pos;
selection_start_pos = MIN (text->selection_start, text->selection_end);
selection_end_pos = MAX (text->selection_start, text->selection_end);
/* convert sel_start/sel_end to byte indices */
selection_start_pos = g_utf8_offset_to_pointer (
text->text, selection_start_pos) - text->text;
selection_end_pos = g_utf8_offset_to_pointer (
text->text, selection_end_pos) - text->text;
gtk_clipboard_set_text (
gtk_widget_get_clipboard (
GTK_WIDGET (GNOME_CANVAS_ITEM (text)->canvas),
GDK_SELECTION_CLIPBOARD),
text->text + selection_start_pos,
selection_end_pos - selection_start_pos);
}
void
e_text_delete_selection (EText *text)
{
gint sel_start, sel_end;
sel_start = MIN (text->selection_start, text->selection_end);
sel_end = MAX (text->selection_start, text->selection_end);
if (sel_start != sel_end)
e_text_model_delete (text->model, sel_start, sel_end - sel_start);
text->need_im_reset = TRUE;
}
void
e_text_cut_clipboard (EText *text)
{
e_text_copy_clipboard (text);
e_text_delete_selection (text);
}
void
e_text_paste_clipboard (EText *text)
{
ETextEventProcessorCommand command;
command.action = E_TEP_PASTE;
command.position = E_TEP_SELECTION;
command.string = "";
command.value = 0;
e_text_command (text->tep, &command, text);
}
void
e_text_select_all (EText *text)
{
ETextEventProcessorCommand command;
command.action = E_TEP_SELECT;
command.position = E_TEP_SELECT_ALL;
command.string = "";
command.value = 0;
e_text_command (text->tep, &command, text);
}
static void
primary_get_cb (GtkClipboard *clipboard,
GtkSelectionData *selection_data,
guint info,
gpointer data)
{
EText *text = E_TEXT (data);
gint sel_start, sel_end;
sel_start = MIN (text->selection_start, text->selection_end);
sel_end = MAX (text->selection_start, text->selection_end);
/* convert sel_start/sel_end to byte indices */
sel_start = g_utf8_offset_to_pointer (text->text, sel_start) - text->text;
sel_end = g_utf8_offset_to_pointer (text->text, sel_end) - text->text;
if (sel_start != sel_end) {
gtk_selection_data_set_text (selection_data,
text->text + sel_start,
sel_end - sel_start);
}
}
static void
primary_clear_cb (GtkClipboard *clipboard,
gpointer data)
{
#ifdef notyet
/* XXX */
gtk_editable_select_region (
GTK_EDITABLE (entry), entry->current_pos, entry->current_pos);
#endif
}
static void
e_text_update_primary_selection (EText *text)
{
static const GtkTargetEntry targets[] = {
{ (gchar *) "UTF8_STRING", 0, 0 },
{ (gchar *) "UTF-8", 0, 0 },
{ (gchar *) "STRING", 0, 0 },
{ (gchar *) "TEXT", 0, 0 },
{ (gchar *) "COMPOUND_TEXT", 0, 0 }
};
GtkClipboard *clipboard;
clipboard = gtk_widget_get_clipboard (
GTK_WIDGET (GNOME_CANVAS_ITEM (text)->canvas),
GDK_SELECTION_PRIMARY);
if (text->selection_start != text->selection_end) {
if (!gtk_clipboard_set_with_owner (clipboard, targets, G_N_ELEMENTS (targets),
primary_get_cb, primary_clear_cb, G_OBJECT (text)))
primary_clear_cb (clipboard, text);
}
else {
if (gtk_clipboard_get_owner (clipboard) == G_OBJECT (text))
gtk_clipboard_clear (clipboard);
}
}
static void
paste_received (GtkClipboard *clipboard,
const gchar *text,
gpointer data)
{
EText *etext = E_TEXT (data);
if (text && g_utf8_validate (text, strlen (text), NULL)) {
if (etext->selection_end != etext->selection_start)
e_text_delete_selection (etext);
e_text_insert (etext, text);
}
g_object_unref (etext);
}
static void
e_text_paste (EText *text,
GdkAtom selection)
{
g_object_ref (text);
gtk_clipboard_request_text (
gtk_widget_get_clipboard (
GTK_WIDGET (GNOME_CANVAS_ITEM (text)->canvas),
selection), paste_received, text);
}
typedef struct {
EText *text;
GdkEventButton *button;
gint position;
} PopupClosure;
static void
popup_menu_detach (GtkWidget *attach_widget,
GtkMenu *menu)
{
}
static void
popup_menu_placement_cb (GtkMenu *menu,
gint *x,
gint *y,
gboolean *push_in,
gpointer user_data)
{
EText *text = E_TEXT (user_data);
GnomeCanvasItem *item = &text->item;
GnomeCanvas *parent = item->canvas;
if (parent) {
GdkWindow *window;
window = gtk_widget_get_window (GTK_WIDGET (parent));
gdk_window_get_origin (window, x, y);
*x += item->x1 + text->width / 2;
*y += item->y1 + text->height / 2;
}
return;
}
static void
popup_targets_received (GtkClipboard *clipboard,
GtkSelectionData *data,
gpointer user_data)
{
PopupClosure *closure = user_data;
EText *text = closure->text;
GdkEventButton *button = closure->button;
gint position = closure->position;
GtkWidget *popup_menu = gtk_menu_new ();
GtkWidget *menuitem, *submenu;
g_free (closure);
gtk_menu_attach_to_widget (GTK_MENU (popup_menu),
GTK_WIDGET (GNOME_CANVAS_ITEM (text)->canvas),
popup_menu_detach);
/* cut menu item */
menuitem = gtk_image_menu_item_new_from_stock (GTK_STOCK_CUT, NULL);
gtk_widget_show (menuitem);
gtk_menu_shell_append (GTK_MENU_SHELL (popup_menu), menuitem);
g_signal_connect_swapped (menuitem, "activate",
G_CALLBACK (e_text_cut_clipboard), text);
gtk_widget_set_sensitive (
menuitem, text->editable &&
(text->selection_start != text->selection_end));
/* copy menu item */
menuitem = gtk_image_menu_item_new_from_stock (GTK_STOCK_COPY, NULL);
gtk_widget_show (menuitem);
gtk_menu_shell_append (GTK_MENU_SHELL (popup_menu), menuitem);
g_signal_connect_swapped (menuitem, "activate",
G_CALLBACK (e_text_copy_clipboard), text);
gtk_widget_set_sensitive (menuitem, text->selection_start != text->selection_end);
/* paste menu item */
menuitem = gtk_image_menu_item_new_from_stock (GTK_STOCK_PASTE, NULL);
gtk_widget_show (menuitem);
gtk_menu_shell_append (GTK_MENU_SHELL (popup_menu), menuitem);
g_signal_connect_swapped (menuitem, "activate",
G_CALLBACK (e_text_paste_clipboard), text);
gtk_widget_set_sensitive (
menuitem, text->editable &&
gtk_selection_data_targets_include_text (data));
menuitem = gtk_menu_item_new_with_label (_("Select All"));
gtk_widget_show (menuitem);
gtk_menu_shell_append (GTK_MENU_SHELL (popup_menu), menuitem);
g_signal_connect_swapped (menuitem, "activate",
G_CALLBACK (e_text_select_all), text);
gtk_widget_set_sensitive (menuitem, strlen (text->text) > 0);
menuitem = gtk_separator_menu_item_new ();
gtk_widget_show (menuitem);
gtk_menu_shell_append (GTK_MENU_SHELL (popup_menu), menuitem);
if (text->im_context && GTK_IS_IM_MULTICONTEXT (text->im_context)) {
menuitem = gtk_menu_item_new_with_label (_("Input Methods"));
gtk_widget_show (menuitem);
submenu = gtk_menu_new ();
gtk_menu_item_set_submenu (GTK_MENU_ITEM (menuitem), submenu);
gtk_menu_shell_append (GTK_MENU_SHELL (popup_menu), menuitem);
gtk_im_multicontext_append_menuitems (GTK_IM_MULTICONTEXT (text->im_context),
GTK_MENU_SHELL (submenu));
}
g_signal_emit (text,
e_text_signals[E_TEXT_POPULATE_POPUP],
0,
button, position,
popup_menu);
/* If invoked by S-F10 key binding, button will be 0. */
if (button->button == 0) {
gtk_menu_popup (GTK_MENU (popup_menu), NULL, NULL,
popup_menu_placement_cb, (gpointer) text,
button->button, GDK_CURRENT_TIME);
} else {
gtk_menu_popup (GTK_MENU (popup_menu), NULL, NULL,
NULL, NULL,
button->button, button->time);
}
g_object_unref (text);
gdk_event_free ((GdkEvent *) button);
}
static void
e_text_do_popup (EText *text,
GdkEventButton *button,
gint position)
{
PopupClosure *closure = g_new (PopupClosure, 1);
closure->text = g_object_ref (text);
closure->button = (GdkEventButton *) gdk_event_copy ((GdkEvent *) button);
closure->position = position;
gtk_clipboard_request_contents (
gtk_widget_get_clipboard (
GTK_WIDGET (GNOME_CANVAS_ITEM (text)->canvas),
GDK_SELECTION_CLIPBOARD),
gdk_atom_intern ("TARGETS", FALSE),
popup_targets_received,
closure);
}
static void
e_text_reset_im_context (EText *text)
{
if (text->need_im_reset && text->im_context) {
text->need_im_reset = FALSE;
gtk_im_context_reset (text->im_context);
}
}
/* fixme: */
static gint
next_word (EText *text,
gint start)
{
gchar *p = g_utf8_offset_to_pointer (text->text, start);
gint length;
length = g_utf8_strlen (text->text, -1);
if (start >= length) {
return length;
} else {
p = g_utf8_next_char (p);
start++;
while (p && *p) {
gunichar unival = g_utf8_get_char (p);
if (g_unichar_isspace (unival)) {
return start + 1;
}
else {
p = g_utf8_next_char (p);
start++;
}
}
}
return g_utf8_pointer_to_offset (text->text, p);
}
static gint
find_offset_into_line (EText *text,
gint offset_into_text,
gchar **start_of_line)
{
gchar *p;
p = g_utf8_offset_to_pointer (text->text, offset_into_text);
if (p == text->text) {
if (start_of_line)
*start_of_line = (gchar *)text->text;
return 0;
}
else {
p = g_utf8_find_prev_char (text->text, p);
while (p && p > text->text) {
if (*p == '\n') {
if (start_of_line)
*start_of_line = p+1;
return offset_into_text - g_utf8_pointer_to_offset (text->text, p + 1);
}
p = g_utf8_find_prev_char (text->text, p);
}
if (start_of_line)
*start_of_line = (gchar *)text->text;
return offset_into_text;
}
}
/* direction = TRUE (move forward), FALSE (move backward)
* Any error shall return length (text->text) or 0 or
* text->selection_end (as deemed fit) */
static gint
_get_updated_position (EText *text,
gboolean direction)
{
PangoLogAttr *log_attrs = NULL;
gint n_attrs;
gchar *p = NULL;
gint new_pos = 0;
gint length = 0;
/* Basic sanity test, return whatever position we are currently at. */
g_return_val_if_fail (text->layout != NULL, text->selection_end);
length = g_utf8_strlen (text->text, -1);
/* length checks to make sure we are not wandering
* off into nonexistant memory... */
if ((text->selection_end >= length) && (TRUE == direction)) /* forward */
return length;
/* checking for -ve value wont hurt! */
if ((text->selection_end <= 0) && (FALSE == direction)) /* backward */
return 0;
/* check for validness of full text->text */
if (!g_utf8_validate (text->text, -1, NULL))
return text->selection_end;
/* get layout's PangoLogAttr to facilitate moving when
* moving across grapheme cluster as in indic langs */
pango_layout_get_log_attrs (text->layout, &log_attrs, &n_attrs);
/* Fetch the current gchar index in the line & keep moving
* forward until we can display cursor */
p = g_utf8_offset_to_pointer (text->text, text->selection_end);
new_pos = text->selection_end;
while (1)
{
/* check before moving forward/backwards
* if we have more chars to move or not */
if (TRUE == direction)
p = g_utf8_next_char (p);
else
p = g_utf8_prev_char (p);
/* validate the new string & return with original position if check fails */
if (!g_utf8_validate (p, -1, NULL))
break; /* will return old value of new_pos */
new_pos = g_utf8_pointer_to_offset (text->text, p);
/* if is_cursor_position is set, cursor can appear in front of character.
* i.e. this is a grapheme boundary AND make some sanity checks */
if ((new_pos >=0) && (new_pos < n_attrs) &&
(log_attrs[new_pos].is_cursor_position))
break;
else if ((new_pos < 0) || (new_pos >= n_attrs))
{
new_pos = text->selection_end;
break;
}
}
if (log_attrs)
g_free (log_attrs);
return new_pos;
}
static gint
_get_position (EText *text,
ETextEventProcessorCommand *command)
{
gint length, obj_num;
gunichar unival;
gchar *p = NULL;
gint new_pos = 0;
switch (command->position) {
case E_TEP_VALUE:
new_pos = command->value;
break;
case E_TEP_SELECTION:
new_pos = text->selection_end;
break;
case E_TEP_START_OF_BUFFER:
new_pos = 0;
break;
case E_TEP_END_OF_BUFFER:
new_pos = strlen (text->text);
break;
case E_TEP_START_OF_LINE:
if (text->selection_end >= 1) {
p = g_utf8_offset_to_pointer (text->text, text->selection_end);
if (p != text->text) {
p = g_utf8_find_prev_char (text->text, p);
while (p && p > text->text) {
if (*p == '\n') {
new_pos = g_utf8_pointer_to_offset (text->text, p) + 1;
break;
}
p = g_utf8_find_prev_char (text->text, p);
}
}
}
break;
case E_TEP_END_OF_LINE:
new_pos = -1;
length = g_utf8_strlen (text->text, -1);
if (text->selection_end >= length) {
new_pos = length;
} else {
p = g_utf8_offset_to_pointer (text->text, text->selection_end);
while (p && *p) {
if (*p == '\n') {
new_pos = g_utf8_pointer_to_offset (text->text, p);
p = NULL;
} else
p = g_utf8_next_char (p);
}
}
if (new_pos == -1)
new_pos = g_utf8_pointer_to_offset (text->text, p);
break;
case E_TEP_FORWARD_CHARACTER:
length = g_utf8_strlen (text->text, -1);
if (text->selection_end >= length)
new_pos = length;
else
/* get updated position to display cursor */
new_pos = _get_updated_position (text, TRUE);
break;
case E_TEP_BACKWARD_CHARACTER:
new_pos = 0;
if (text->selection_end >= 1)
/* get updated position to display cursor */
new_pos = _get_updated_position (text, FALSE);
break;
case E_TEP_FORWARD_WORD:
new_pos = next_word (text, text->selection_end);
break;
case E_TEP_BACKWARD_WORD:
new_pos = 0;
if (text->selection_end >= 1) {
gint pos = text->selection_end;
p = g_utf8_find_prev_char (
text->text, g_utf8_offset_to_pointer (
text->text, text->selection_end));
pos--;
if (p != text->text) {
p = g_utf8_find_prev_char (text->text, p);
pos--;
while (p && p > text->text) {
unival = g_utf8_get_char (p);
if (g_unichar_isspace (unival)) {
new_pos = pos + 1;
p = NULL;
}
else {
p = g_utf8_find_prev_char (text->text, p);
pos--;
}
}
}
}
break;
case E_TEP_FORWARD_LINE: {
gint offset_into_line;
offset_into_line = find_offset_into_line (text, text->selection_end, NULL);
if (offset_into_line == -1)
return text->selection_end;
/* now we search forward til we hit a \n, and then
* offset_into_line more characters */
p = g_utf8_offset_to_pointer (text->text, text->selection_end);
while (p && *p) {
if (*p == '\n')
break;
p = g_utf8_next_char (p);
}
if (p && *p == '\n') {
/* now we loop forward offset_into_line
* characters, or until we hit \n or \0 */
p = g_utf8_next_char (p);
while (offset_into_line > 0 && p && *p != '\n' && *p != '\0') {
p = g_utf8_next_char (p);
offset_into_line--;
}
}
/* at this point, p points to the new location,
* convert it to an offset and we're done */
new_pos = g_utf8_pointer_to_offset (text->text, p);
break;
}
case E_TEP_BACKWARD_LINE: {
gint offset_into_line = find_offset_into_line (text, text->selection_end, &p);
if (offset_into_line == -1)
return text->selection_end;
/* p points to the first character on our line. if we
* have a \n before it, skip it and scan til we hit
* the next one */
if (p != text->text) {
p = g_utf8_find_prev_char (text->text, p);
if (*p == '\n') {
p = g_utf8_find_prev_char (text->text, p);
while (p > text->text) {
if (*p == '\n') {
p++;
break;
}
p = g_utf8_find_prev_char (text->text, p);
}
}
}
/* at this point 'p' points to the start of the
* previous line, move forward 'offset_into_line'
* times. */
while (offset_into_line > 0 && p && *p != '\n' && *p != '\0') {
p = g_utf8_next_char (p);
offset_into_line--;
}
/* at this point, p points to the new location,
* convert it to an offset and we're done */
new_pos = g_utf8_pointer_to_offset (text->text, p);
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.) */
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;
}
if (text->selection_end < 1) {
new_pos = 0;
break;
}
p = g_utf8_offset_to_pointer (text->text, text->selection_end);
p = g_utf8_find_prev_char (text->text, p);
while (p && p > text->text) {
unival = g_utf8_get_char (p);
if (g_unichar_isspace (unival)) {
p = g_utf8_next_char (p);
break;
}
p = g_utf8_find_prev_char (text->text, p);
}
if (!p)
text->selection_start = 0;
else
text->selection_start = g_utf8_pointer_to_offset (text->text, p);
text->selection_start =
e_text_model_validate_position (
text->model, text->selection_start);
length = g_utf8_strlen (text->text, -1);
if (text->selection_end >= length) {
new_pos = length;
break;
}
p = g_utf8_offset_to_pointer (text->text, text->selection_end);
while (p && *p) {
unival = g_utf8_get_char (p);
if (g_unichar_isspace (unival)) {
new_pos = g_utf8_pointer_to_offset (text->text, p);
break;
} else
p = g_utf8_next_char (p);
}
if (!new_pos)
new_pos = g_utf8_strlen (text->text, -1);
return new_pos;
case E_TEP_SELECT_ALL:
text->selection_start = 0;
new_pos = g_utf8_strlen (text->text, -1);
break;
case E_TEP_FORWARD_PARAGRAPH:
case E_TEP_BACKWARD_PARAGRAPH:
case E_TEP_FORWARD_PAGE:
case E_TEP_BACKWARD_PAGE:
new_pos = text->selection_end;
break;
default:
new_pos = text->selection_end;
break;
}
new_pos = e_text_model_validate_position (text->model, new_pos);
return new_pos;
}
static void
e_text_insert (EText *text,
const gchar *string)
{
gint len = strlen (string);
if (len > 0) {
gint utf8len = 0;
if (!text->allow_newlines) {
const gchar *i;
gchar *new_string = g_malloc (len + 1);
gchar *j = new_string;
for (i = string; *i; i = g_utf8_next_char (i)) {
if (*i != '\n') {
gunichar c;
gint charlen;
c = g_utf8_get_char (i);
charlen = g_unichar_to_utf8 (c, j);
j += charlen;
utf8len++;
}
}
*j = 0;
e_text_model_insert_length (
text->model, text->selection_start,
new_string, utf8len);
g_free (new_string);
}
else {
utf8len = g_utf8_strlen (string, -1);
e_text_model_insert_length (
text->model, text->selection_start,
string, utf8len);
}
}
}
static void
capitalize (EText *text,
gint start,
gint end,
ETextEventProcessorCaps type)
{
gboolean first = TRUE;
const gchar *p = g_utf8_offset_to_pointer (text->text, start);
const gchar *text_end = g_utf8_offset_to_pointer (text->text, end);
gint utf8len = text_end - p;
if (utf8len > 0) {
gchar *new_text = g_new0 (char, utf8len * 6);
gchar *output = new_text;
while (p && *p && p < text_end) {
gunichar unival = g_utf8_get_char (p);
gunichar newval = unival;
switch (type) {
case E_TEP_CAPS_UPPER:
newval = g_unichar_toupper (unival);
break;
case E_TEP_CAPS_LOWER:
newval = g_unichar_tolower (unival);
break;
case E_TEP_CAPS_TITLE:
if (g_unichar_isalpha (unival)) {
if (first)
newval = g_unichar_totitle (unival);
else
newval = g_unichar_tolower (unival);
first = FALSE;
} else {
first = TRUE;
}
break;
}
g_unichar_to_utf8 (newval, output);
output = g_utf8_next_char (output);
p = g_utf8_next_char (p);
}
*output = 0;
e_text_model_delete (text->model, start, utf8len);
e_text_model_insert_length (text->model, start, new_text, utf8len);
g_free (new_text);
}
}
static void
e_text_command (ETextEventProcessor *tep,
ETextEventProcessorCommand *command,
gpointer data)
{
EText *text = E_TEXT (data);
gboolean scroll = TRUE;
gboolean use_start = TRUE;
switch (command->action) {
case E_TEP_MOVE:
text->selection_start = _get_position (text, command);
text->selection_end = text->selection_start;
if (text->timer) {
g_timer_reset (text->timer);
}
text->need_im_reset = TRUE;
use_start = TRUE;
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);
e_text_update_primary_selection (text);
text->need_im_reset = TRUE;
use_start = FALSE;
break;
case E_TEP_DELETE:
if (text->selection_end == text->selection_start) {
text->selection_end = _get_position (text, command);
}
e_text_delete_selection (text);
if (text->timer) {
g_timer_reset (text->timer);
}
text->need_im_reset = TRUE;
use_start = FALSE;
break;
case E_TEP_INSERT:
if (g_utf8_validate (command->string, command->value, NULL)) {
if (text->selection_end != text->selection_start) {
e_text_delete_selection (text);
}
e_text_insert (text, command->string);
if (text->timer) {
g_timer_reset (text->timer);
}
text->need_im_reset = TRUE;
}
break;
case E_TEP_COPY:
e_text_copy_clipboard (text);
if (text->timer) {
g_timer_reset (text->timer);
}
scroll = FALSE;
break;
case E_TEP_PASTE:
e_text_paste (text, GDK_NONE);
if (text->timer) {
g_timer_reset (text->timer);
}
text->need_im_reset = TRUE;
break;
case E_TEP_GET_SELECTION:
e_text_paste (text, GDK_SELECTION_PRIMARY);
break;
case E_TEP_ACTIVATE:
g_signal_emit (text, e_text_signals[E_TEXT_ACTIVATE], 0);
if (text->timer) {
g_timer_reset (text->timer);
}
break;
case E_TEP_SET_SELECT_BY_WORD:
text->select_by_word = command->value;
break;
case E_TEP_GRAB:
e_canvas_item_grab (E_CANVAS (GNOME_CANVAS_ITEM (text)->canvas),
GNOME_CANVAS_ITEM (text),
GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK,
text->i_cursor,
command->time,
NULL,
NULL);
scroll = FALSE;
break;
case E_TEP_UNGRAB:
e_canvas_item_ungrab (E_CANVAS (GNOME_CANVAS_ITEM (text)->canvas),
GNOME_CANVAS_ITEM (text),
command->time);
scroll = FALSE;
break;
case E_TEP_CAPS:
if (text->selection_start == text->selection_end) {
capitalize (
text, text->selection_start,
next_word (text, text->selection_start),
command->value);
} else {
gint selection_start = MIN (
text->selection_start, text->selection_end);
gint selection_end = MAX (
text->selection_start, text->selection_end);
capitalize (
text, selection_start,
selection_end, command->value);
}
break;
case E_TEP_NOP:
scroll = FALSE;
break;
}
e_text_reset_im_context (text);
/* it's possible to get here without ever having been realized
* by our canvas (if the e-text started completely obscured.)
* so let's create our layout object if we don't already have
* one. */
if (!text->layout)
create_layout (text);
/* We move cursor only if scroll is TRUE */
if (scroll && !text->button_down) {
/* XXX do we really need the @trailing logic here? if
* we don't we can scrap the loop and just use
* pango_layout_index_to_pos */
PangoLayoutLine *cur_line = NULL;
gint selection_index;
PangoLayoutIter *iter = pango_layout_get_iter (text->layout);
/* check if we are using selection_start or selection_end for moving? */
selection_index = use_start ? text->selection_start : text->selection_end;
/* convert to a byte index */
selection_index = g_utf8_offset_to_pointer (
text->text, selection_index) - text->text;
do {
PangoLayoutLine *line = pango_layout_iter_get_line (iter);
if (selection_index >= line->start_index &&
selection_index <= line->start_index + line->length) {
/* found the line with the start of the selection */
cur_line = line;
break;
}
} while (pango_layout_iter_next_line (iter));
if (cur_line) {
gint xpos, ypos;
gdouble clip_width, clip_height;
/* gboolean trailing = FALSE; */
PangoRectangle pango_pos;
if (selection_index > 0 && selection_index ==
cur_line->start_index + cur_line->length) {
selection_index--;
/* trailing = TRUE; */
}
pango_layout_index_to_pos (text->layout, selection_index, &pango_pos);
pango_pos.x = PANGO_PIXELS (pango_pos.x);
pango_pos.y = PANGO_PIXELS (pango_pos.y);
pango_pos.width = (pango_pos.width + PANGO_SCALE / 2) / PANGO_SCALE;
pango_pos.height = (pango_pos.height + PANGO_SCALE / 2) / PANGO_SCALE;
/* scroll for X */
xpos = pango_pos.x; /* + (trailing ? 0 : pango_pos.width);*/
if (xpos + 2 < text->xofs_edit) {
text->xofs_edit = xpos;
}
clip_width = text->clip_width;
if (clip_width >= 0 && text->draw_borders) {
clip_width -= 6;
if (clip_width < 0)
clip_width = 0;
}
if (xpos + pango_pos.width - clip_width > text->xofs_edit) {
text->xofs_edit = xpos + pango_pos.width - clip_width;
}
/* scroll for Y */
if (pango_pos.y + 2 < text->yofs_edit) {
ypos = pango_pos.y;
text->yofs_edit = ypos;
}
else {
ypos = pango_pos.y + pango_pos.height;
}
if (text->clip_height < 0)
clip_height = text->height;
else
clip_height = text->clip_height;
if (clip_height >= 0 && text->draw_borders) {
clip_height -= 6;
if (clip_height < 0)
clip_height = 0;
}
if (ypos - clip_height > text->yofs_edit) {
text->yofs_edit = ypos - clip_height;
}
}
pango_layout_iter_free (iter);
}
text->needs_redraw = 1;
gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (text));
}
/* Class initialization function for the text item */
static void
e_text_class_init (ETextClass *klass)
{
GObjectClass *gobject_class;
GnomeCanvasItemClass *item_class;
gobject_class = (GObjectClass *) klass;
item_class = (GnomeCanvasItemClass *) klass;
gobject_class->dispose = e_text_dispose;
gobject_class->set_property = e_text_set_property;
gobject_class->get_property = e_text_get_property;
item_class->update = e_text_update;
item_class->realize = e_text_realize;
item_class->unrealize = e_text_unrealize;
item_class->draw = e_text_draw;
item_class->point = e_text_point;
item_class->bounds = e_text_bounds;
item_class->event = e_text_event;
klass->changed = NULL;
klass->activate = NULL;
e_text_signals[E_TEXT_CHANGED] =
g_signal_new ("changed",
G_OBJECT_CLASS_TYPE (gobject_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (ETextClass, changed),
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, 0);
e_text_signals[E_TEXT_ACTIVATE] =
g_signal_new ("activate",
G_OBJECT_CLASS_TYPE (gobject_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (ETextClass, activate),
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, 0);
e_text_signals[E_TEXT_KEYPRESS] =
g_signal_new ("keypress",
G_OBJECT_CLASS_TYPE (gobject_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (ETextClass, keypress),
NULL, NULL,
e_marshal_NONE__INT_INT,
G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_UINT);
e_text_signals[E_TEXT_POPULATE_POPUP] =
g_signal_new ("populate_popup",
G_OBJECT_CLASS_TYPE (gobject_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (ETextClass, populate_popup),
NULL, NULL,
e_marshal_NONE__POINTER_INT_OBJECT,
G_TYPE_NONE, 3, G_TYPE_POINTER, G_TYPE_INT, GTK_TYPE_MENU);
g_object_class_install_property (gobject_class, PROP_MODEL,
g_param_spec_object ("model",
"Model",
"Model",
E_TYPE_TEXT_MODEL,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_EVENT_PROCESSOR,
g_param_spec_object ("event_processor",
"Event Processor",
"Event Processor",
E_TEXT_EVENT_PROCESSOR_TYPE,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_TEXT,
g_param_spec_string ("text",
"Text",
"Text",
NULL,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_BOLD,
g_param_spec_boolean ("bold",
"Bold",
"Bold",
FALSE,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_STRIKEOUT,
g_param_spec_boolean ("strikeout",
"Strikeout",
"Strikeout",
FALSE,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_JUSTIFICATION,
g_param_spec_enum ("justification",
"Justification",
"Justification",
GTK_TYPE_JUSTIFICATION, GTK_JUSTIFY_LEFT,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_CLIP_WIDTH,
g_param_spec_double ("clip_width",
"Clip Width",
"Clip Width",
0.0, G_MAXDOUBLE, 0.0,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_CLIP_HEIGHT,
g_param_spec_double ("clip_height",
"Clip Height",
"Clip Height",
0.0, G_MAXDOUBLE, 0.0,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_CLIP,
g_param_spec_boolean ("clip",
"Clip",
"Clip",
FALSE,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_FILL_CLIP_RECTANGLE,
g_param_spec_boolean ("fill_clip_rectangle",
"Fill clip rectangle",
"Fill clip rectangle",
FALSE,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_X_OFFSET,
g_param_spec_double ("x_offset",
"X Offset",
"X Offset",
0.0, G_MAXDOUBLE, 0.0,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_Y_OFFSET,
g_param_spec_double ("y_offset",
"Y Offset",
"Y Offset",
0.0, G_MAXDOUBLE, 0.0,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_FILL_COLOR,
g_param_spec_string ("fill_color",
"Fill color",
"Fill color",
NULL,
G_PARAM_WRITABLE));
g_object_class_install_property (gobject_class, PROP_FILL_COLOR_GDK,
g_param_spec_boxed ("fill_color_gdk",
"GDK fill color",
"GDK fill color",
GDK_TYPE_COLOR,
G_PARAM_WRITABLE));
g_object_class_install_property (gobject_class, PROP_FILL_COLOR_RGBA,
g_param_spec_uint ("fill_color_rgba",
"GDK fill color",
"GDK fill color",
0, G_MAXUINT, 0,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_TEXT_WIDTH,
g_param_spec_double ("text_width",
"Text width",
"Text width",
0.0, G_MAXDOUBLE, 0.0,
G_PARAM_READABLE));
g_object_class_install_property (gobject_class, PROP_TEXT_HEIGHT,
g_param_spec_double ("text_height",
"Text height",
"Text height",
0.0, G_MAXDOUBLE, 0.0,
G_PARAM_READABLE));
g_object_class_install_property (gobject_class, PROP_EDITABLE,
g_param_spec_boolean ("editable",
"Editable",
"Editable",
FALSE,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_USE_ELLIPSIS,
g_param_spec_boolean ("use_ellipsis",
"Use ellipsis",
"Use ellipsis",
FALSE,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_ELLIPSIS,
g_param_spec_string ("ellipsis",
"Ellipsis",
"Ellipsis",
NULL,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_LINE_WRAP,
g_param_spec_boolean ("line_wrap",
"Line wrap",
"Line wrap",
FALSE,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_BREAK_CHARACTERS,
g_param_spec_string ("break_characters",
"Break characters",
"Break characters",
NULL,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_MAX_LINES,
g_param_spec_int ("max_lines",
"Max lines",
"Max lines",
0, G_MAXINT, 0,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_WIDTH,
g_param_spec_double ("width",
"Width",
"Width",
0.0, G_MAXDOUBLE, 0.0,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_HEIGHT,
g_param_spec_double ("height",
"Height",
"Height",
0.0, G_MAXDOUBLE, 0.0,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_DRAW_BORDERS,
g_param_spec_boolean ("draw_borders",
"Draw borders",
"Draw borders",
FALSE,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_ALLOW_NEWLINES,
g_param_spec_boolean ("allow_newlines",
"Allow newlines",
"Allow newlines",
FALSE,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_DRAW_BACKGROUND,
g_param_spec_boolean ("draw_background",
"Draw background",
"Draw background",
FALSE,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_DRAW_BUTTON,
g_param_spec_boolean ("draw_button",
"Draw button",
"Draw button",
FALSE,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_CURSOR_POS,
g_param_spec_int ("cursor_pos",
"Cursor position",
"Cursor position",
0, G_MAXINT, 0,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_IM_CONTEXT,
g_param_spec_object ("im_context",
"IM Context",
"IM Context",
GTK_TYPE_IM_CONTEXT,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_HANDLE_POPUP,
g_param_spec_boolean ("handle_popup",
"Handle Popup",
"Handle Popup",
FALSE,
G_PARAM_READWRITE));
if (!clipboard_atom)
clipboard_atom = gdk_atom_intern ("CLIPBOARD", FALSE);
gal_a11y_e_text_init ();
}
/* Object initialization function for the text item */
static void
e_text_init (EText *text)
{
text->model = e_text_model_new ();
text->text = e_text_model_get_text (text->model);
text->preedit_len = 0;
text->preedit_pos = 0;
text->layout = NULL;
text->revert = NULL;
text->model_changed_signal_id =
g_signal_connect (text->model,
"changed",
G_CALLBACK (e_text_text_model_changed),
text);
text->model_repos_signal_id =
g_signal_connect (text->model,
"reposition",
G_CALLBACK (e_text_text_model_reposition),
text);
text->justification = GTK_JUSTIFY_LEFT;
text->clip_width = -1.0;
text->clip_height = -1.0;
text->xofs = 0.0;
text->yofs = 0.0;
text->ellipsis = NULL;
text->use_ellipsis = FALSE;
text->ellipsis_width = 0;
text->editable = FALSE;
text->editing = FALSE;
text->xofs_edit = 0;
text->yofs_edit = 0;
text->selection_start = 0;
text->selection_end = 0;
text->select_by_word = FALSE;
text->timeout_id = 0;
text->timer = NULL;
text->lastx = 0;
text->lasty = 0;
text->last_state = 0;
text->scroll_start = 0;
text->show_cursor = TRUE;
text->button_down = FALSE;
text->tep = NULL;
text->tep_command_id = 0;
text->has_selection = FALSE;
text->pointer_in = FALSE;
text->default_cursor_shown = TRUE;
text->line_wrap = FALSE;
text->break_characters = NULL;
text->max_lines = -1;
text->dbl_timeout = 0;
text->tpl_timeout = 0;
text->draw_background = FALSE;
text->draw_button = FALSE;
text->bold = FALSE;
text->strikeout = FALSE;
text->allow_newlines = TRUE;
text->last_type_request = -1;
text->last_time_request = 0;
text->queued_requests = NULL;
text->im_context = NULL;
text->need_im_reset = FALSE;
text->im_context_signals_registered = FALSE;
text->handle_popup = FALSE;
e_canvas_item_set_reflow_callback (GNOME_CANVAS_ITEM (text), e_text_reflow);
}
/* IM Context Callbacks */
static void
e_text_commit_cb (GtkIMContext *context,
const gchar *str,
EText *text)
{
if (g_utf8_validate (str, strlen (str), NULL)) {
if (text->selection_end != text->selection_start)
e_text_delete_selection (text);
e_text_insert (text, str);
g_signal_emit (text, e_text_signals[E_TEXT_KEYPRESS], 0, 0, 0);
}
}
static void
e_text_preedit_changed_cb (GtkIMContext *context,
EText *etext)
{
gchar *preedit_string = NULL;
gint cursor_pos;
gtk_im_context_get_preedit_string (context, &preedit_string,
NULL, &cursor_pos);
cursor_pos = CLAMP (cursor_pos, 0, g_utf8_strlen (preedit_string, -1));
etext->preedit_len = strlen (preedit_string);
etext->preedit_pos = g_utf8_offset_to_pointer (
preedit_string, cursor_pos) - preedit_string;
g_free (preedit_string);
g_signal_emit (etext, e_text_signals[E_TEXT_KEYPRESS], 0, 0, 0);
}
static gboolean
e_text_retrieve_surrounding_cb (GtkIMContext *context,
EText *text)
{
gtk_im_context_set_surrounding (
context, text->text, strlen (text->text),
g_utf8_offset_to_pointer (text->text, MIN (
text->selection_start, text->selection_end)) - text->text);
return TRUE;
}
static gboolean
e_text_delete_surrounding_cb (GtkIMContext *context,
gint offset,
gint n_chars,
EText *text)
{
e_text_model_delete (text->model,
MIN (text->selection_start, text->selection_end) + offset,
n_chars);
return TRUE;
}