Bug 793125 - Crash due to popup menus left attached too long
[evolution.git] / src / e-util / e-text.c
blob2a5cf4ba9a9d1e8c77299604d9faa3d3b5a421fe
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3 * e-text.c - Text item for evolution.
4 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
6 * Authors:
7 * Chris Lahey <clahey@ximian.com>
8 * Jon Trowbridge <trow@ximian.com>
10 * A majority of code taken from:
12 * Text item type for GnomeCanvas widget
14 * GnomeCanvas is basically a port of the Tk toolkit's most excellent
15 * canvas widget. Tk is copyrighted by the Regents of the University
16 * of California, Sun Microsystems, and other parties.
18 * Copyright (C) 1998 The Free Software Foundation
20 * Author: Federico Mena <federico@nuclecu.unam.mx>
22 * This program is free software; you can redistribute it and/or modify it
23 * under the terms of the GNU Lesser General Public License as published by
24 * published by the Free Software Foundation; either the
26 * This program is distributed in the hope that it will be useful, but
27 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
28 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
29 * for more details.
31 * You should have received a copy of the GNU Lesser General Public License
32 * along with this program; if not, see <http://www.gnu.org/licenses/>.
33 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
34 * 02110-1301, USA.
37 #include "evolution-config.h"
39 #include <math.h>
40 #include <ctype.h>
41 #include <string.h>
43 #include <glib/gi18n.h>
44 #include <gtk/gtk.h>
45 #include <gdk/gdkkeysyms.h>
47 #include <libedataserver/libedataserver.h>
49 #include "e-canvas-utils.h"
50 #include "e-canvas.h"
51 #include "e-marshal.h"
52 #include "e-text-event-processor-emacs-like.h"
53 #include "e-unicode.h"
54 #include "e-misc-utils.h"
55 #include "gal-a11y-e-text.h"
57 #include "e-text.h"
59 G_DEFINE_TYPE (EText, e_text, GNOME_TYPE_CANVAS_ITEM)
61 enum {
62 E_TEXT_CHANGED,
63 E_TEXT_ACTIVATE,
64 E_TEXT_KEYPRESS,
65 E_TEXT_POPULATE_POPUP,
66 E_TEXT_LAST_SIGNAL
69 static GQuark e_text_signals[E_TEXT_LAST_SIGNAL] = { 0 };
71 /* Object argument IDs */
72 enum {
73 PROP_0,
74 PROP_MODEL,
75 PROP_EVENT_PROCESSOR,
76 PROP_TEXT,
77 PROP_BOLD,
78 PROP_STRIKEOUT,
79 PROP_ITALIC,
80 PROP_ANCHOR,
81 PROP_JUSTIFICATION,
82 PROP_CLIP_WIDTH,
83 PROP_CLIP_HEIGHT,
84 PROP_CLIP,
85 PROP_FILL_CLIP_RECTANGLE,
86 PROP_X_OFFSET,
87 PROP_Y_OFFSET,
88 PROP_FILL_COLOR,
89 PROP_FILL_COLOR_GDK,
90 PROP_FILL_COLOR_RGBA,
91 PROP_TEXT_WIDTH,
92 PROP_TEXT_HEIGHT,
93 PROP_EDITABLE,
94 PROP_USE_ELLIPSIS,
95 PROP_ELLIPSIS,
96 PROP_LINE_WRAP,
97 PROP_BREAK_CHARACTERS,
98 PROP_MAX_LINES,
99 PROP_WIDTH,
100 PROP_HEIGHT,
101 PROP_ALLOW_NEWLINES,
102 PROP_CURSOR_POS,
103 PROP_IM_CONTEXT,
104 PROP_HANDLE_POPUP
107 static void e_text_command (ETextEventProcessor *tep,
108 ETextEventProcessorCommand *command,
109 gpointer data);
111 static void e_text_text_model_changed (ETextModel *model,
112 EText *text);
113 static void e_text_text_model_reposition (ETextModel *model,
114 ETextModelReposFn fn,
115 gpointer repos_data,
116 gpointer data);
118 static void _get_tep (EText *text);
120 static void calc_height (EText *text);
122 static gboolean show_pango_rectangle (EText *text, PangoRectangle rect);
124 static void e_text_do_popup (EText *text,
125 GdkEvent *event_button,
126 gint position);
128 static void e_text_update_primary_selection (EText *text);
129 static void e_text_paste (EText *text, GdkAtom selection);
130 static void e_text_insert (EText *text, const gchar *string);
131 static void e_text_reset_im_context (EText *text);
133 static void reset_layout_attrs (EText *text);
135 /* IM Context Callbacks */
136 static void e_text_commit_cb (GtkIMContext *context,
137 const gchar *str,
138 EText *text);
139 static void e_text_preedit_changed_cb (GtkIMContext *context,
140 EText *text);
141 static gboolean e_text_retrieve_surrounding_cb (GtkIMContext *context,
142 EText *text);
143 static gboolean e_text_delete_surrounding_cb (GtkIMContext *context,
144 gint offset,
145 gint n_chars,
146 EText *text);
148 static GdkAtom clipboard_atom = GDK_NONE;
150 static void
151 disconnect_im_context (EText *text)
153 if (!text || !text->im_context)
154 return;
156 g_signal_handlers_disconnect_matched (
157 text->im_context, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, text);
158 text->im_context_signals_registered = FALSE;
161 /* Dispose handler for the text item */
162 static void
163 e_text_dispose (GObject *object)
165 EText *text;
167 g_return_if_fail (object != NULL);
168 g_return_if_fail (E_IS_TEXT (object));
170 text = E_TEXT (object);
172 if (text->model_changed_signal_id)
173 g_signal_handler_disconnect (
174 text->model,
175 text->model_changed_signal_id);
176 text->model_changed_signal_id = 0;
178 if (text->model_repos_signal_id)
179 g_signal_handler_disconnect (
180 text->model,
181 text->model_repos_signal_id);
182 text->model_repos_signal_id = 0;
184 if (text->model)
185 g_object_unref (text->model);
186 text->model = NULL;
188 if (text->tep_command_id)
189 g_signal_handler_disconnect (
190 text->tep,
191 text->tep_command_id);
192 text->tep_command_id = 0;
194 if (text->tep)
195 g_object_unref (text->tep);
196 text->tep = NULL;
198 g_free (text->revert);
199 text->revert = NULL;
201 if (text->timeout_id) {
202 g_source_remove (text->timeout_id);
203 text->timeout_id = 0;
206 if (text->timer) {
207 g_timer_stop (text->timer);
208 g_timer_destroy (text->timer);
209 text->timer = NULL;
212 if (text->dbl_timeout) {
213 g_source_remove (text->dbl_timeout);
214 text->dbl_timeout = 0;
217 if (text->tpl_timeout) {
218 g_source_remove (text->tpl_timeout);
219 text->tpl_timeout = 0;
222 if (text->layout) {
223 g_object_unref (text->layout);
224 text->layout = NULL;
227 if (text->im_context) {
228 disconnect_im_context (text);
229 g_object_unref (text->im_context);
230 text->im_context = NULL;
233 if (text->font_desc) {
234 pango_font_description_free (text->font_desc);
235 text->font_desc = NULL;
238 /* Chain up to parent's dispose() method. */
239 G_OBJECT_CLASS (e_text_parent_class)->dispose (object);
242 static void
243 insert_preedit_text (EText *text)
245 PangoAttrList *attrs = NULL;
246 PangoAttrList *preedit_attrs = NULL;
247 gchar *preedit_string = NULL;
248 GString *tmp_string = g_string_new (NULL);
249 gint length = 0, cpos = 0;
250 gboolean new_attrs = FALSE;
252 if (text->layout == NULL || !GTK_IS_IM_CONTEXT (text->im_context))
253 return;
255 text->text = e_text_model_get_text (text->model);
256 length = strlen (text->text);
258 g_string_prepend_len (tmp_string, text->text,length);
260 /* we came into this function only when text->preedit_len was not 0
261 * so we can safely fetch the preedit string */
262 gtk_im_context_get_preedit_string (
263 text->im_context, &preedit_string, &preedit_attrs, NULL);
265 if (preedit_string && g_utf8_validate (preedit_string, -1, NULL)) {
267 text->preedit_len = strlen (preedit_string);
269 cpos = g_utf8_offset_to_pointer (
270 text->text, text->selection_start) - text->text;
272 g_string_insert (tmp_string, cpos, preedit_string);
274 reset_layout_attrs (text);
276 attrs = pango_layout_get_attributes (text->layout);
277 if (!attrs) {
278 attrs = pango_attr_list_new ();
279 new_attrs = TRUE;
282 pango_layout_set_text (text->layout, tmp_string->str, tmp_string->len);
284 pango_attr_list_splice (attrs, preedit_attrs, cpos, text->preedit_len);
286 if (new_attrs) {
287 pango_layout_set_attributes (text->layout, attrs);
288 pango_attr_list_unref (attrs);
290 } else
291 text->preedit_len = 0;
293 if (preedit_string)
294 g_free (preedit_string);
295 if (preedit_attrs)
296 pango_attr_list_unref (preedit_attrs);
297 if (tmp_string)
298 g_string_free (tmp_string, TRUE);
301 static void
302 reset_layout_attrs (EText *text)
304 PangoAttrList *attrs = NULL;
305 gint object_count;
307 if (text->layout == NULL)
308 return;
310 object_count = e_text_model_object_count (text->model);
312 if (text->bold || text->strikeout || text->italic || object_count > 0) {
313 gint length = 0;
314 gint i;
316 attrs = pango_attr_list_new ();
318 for (i = 0; i < object_count; i++) {
319 gint start_pos, end_pos;
320 PangoAttribute *attr;
322 attr = pango_attr_underline_new (PANGO_UNDERLINE_SINGLE);
324 e_text_model_get_nth_object_bounds (
325 text->model, i, &start_pos, &end_pos);
327 attr->start_index = g_utf8_offset_to_pointer (
328 text->text, start_pos) - text->text;
329 attr->end_index = g_utf8_offset_to_pointer (
330 text->text, end_pos) - text->text;
332 pango_attr_list_insert (attrs, attr);
335 if (text->bold || text->strikeout || text->italic)
336 length = strlen (text->text);
338 if (text->bold) {
339 PangoAttribute *attr = pango_attr_weight_new (PANGO_WEIGHT_BOLD);
340 attr->start_index = 0;
341 attr->end_index = length;
343 pango_attr_list_insert_before (attrs, attr);
346 if (text->strikeout) {
347 PangoAttribute *attr = pango_attr_strikethrough_new (TRUE);
348 attr->start_index = 0;
349 attr->end_index = length;
351 pango_attr_list_insert_before (attrs, attr);
354 if (text->italic) {
355 PangoAttribute *attr = pango_attr_style_new (PANGO_STYLE_ITALIC);
356 attr->start_index = 0;
357 attr->end_index = length;
359 pango_attr_list_insert_before (attrs, attr);
363 pango_layout_set_attributes (text->layout, attrs);
365 if (attrs)
366 pango_attr_list_unref (attrs);
368 calc_height (text);
371 static void
372 create_layout (EText *text)
374 GnomeCanvasItem *item = GNOME_CANVAS_ITEM (text);
376 if (text->layout)
377 return;
379 text->layout = gtk_widget_create_pango_layout (
380 GTK_WIDGET (item->canvas), text->text);
381 if (text->line_wrap)
382 pango_layout_set_width (
383 text->layout, text->clip_width < 0
384 ? -1 : text->clip_width * PANGO_SCALE);
385 reset_layout_attrs (text);
388 static void
389 reset_layout (EText *text)
391 GnomeCanvasItem *item = GNOME_CANVAS_ITEM (text);
393 if (text->layout == NULL) {
394 create_layout (text);
395 } else {
396 PangoContext *pango_context;
397 PangoFontDescription *font_desc;
399 pango_context = gtk_widget_create_pango_context (GTK_WIDGET (item->canvas));
400 font_desc = pango_context_get_font_description (pango_context);
402 if (text->font_desc) {
403 pango_font_description_free (text->font_desc);
405 text->font_desc = pango_font_description_new ();
406 if (!pango_font_description_get_size_is_absolute (font_desc))
407 pango_font_description_set_size (
408 text->font_desc,
409 pango_font_description_get_size (font_desc));
410 else
411 pango_font_description_set_absolute_size (
412 text->font_desc,
413 pango_font_description_get_size (font_desc));
414 pango_font_description_set_family (
415 text->font_desc,
416 pango_font_description_get_family (font_desc));
417 pango_layout_set_font_description (text->layout, text->font_desc);
419 pango_layout_set_text (text->layout, text->text, -1);
420 reset_layout_attrs (text);
422 g_object_unref (pango_context);
425 if (!text->button_down) {
426 PangoRectangle strong_pos, weak_pos;
427 gchar *offs = g_utf8_offset_to_pointer (text->text, text->selection_start);
429 pango_layout_get_cursor_pos (
430 text->layout, offs - text->text,
431 &strong_pos, &weak_pos);
433 if (strong_pos.x != weak_pos.x ||
434 strong_pos.y != weak_pos.y ||
435 strong_pos.width != weak_pos.width ||
436 strong_pos.height != weak_pos.height)
437 show_pango_rectangle (text, weak_pos);
439 show_pango_rectangle (text, strong_pos);
443 static void
444 e_text_text_model_changed (ETextModel *model,
445 EText *text)
447 gint model_len = e_text_model_get_text_length (model);
448 text->text = e_text_model_get_text (model);
450 /* Make sure our selection doesn't extend past the bounds of our text. */
451 text->selection_start = CLAMP (text->selection_start, 0, model_len);
452 text->selection_end = CLAMP (text->selection_end, 0, model_len);
454 text->needs_reset_layout = 1;
455 text->needs_split_into_lines = 1;
456 text->needs_redraw = 1;
457 e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (text));
458 gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (text));
460 g_signal_emit (text, e_text_signals[E_TEXT_CHANGED], 0);
463 static void
464 e_text_text_model_reposition (ETextModel *model,
465 ETextModelReposFn fn,
466 gpointer repos_data,
467 gpointer user_data)
469 EText *text = E_TEXT (user_data);
470 gint model_len = e_text_model_get_text_length (model);
472 text->selection_start = fn (text->selection_start, repos_data);
473 text->selection_end = fn (text->selection_end, repos_data);
475 /* Our repos function should make sure we don't overrun the buffer, but it never
476 * hurts to be paranoid. */
477 text->selection_start = CLAMP (text->selection_start, 0, model_len);
478 text->selection_end = CLAMP (text->selection_end, 0, model_len);
480 if (text->selection_start > text->selection_end) {
481 gint tmp = text->selection_start;
482 text->selection_start = text->selection_end;
483 text->selection_end = tmp;
487 static void
488 get_bounds (EText *text,
489 gdouble *px1,
490 gdouble *py1,
491 gdouble *px2,
492 gdouble *py2)
494 GnomeCanvasItem *item;
495 gdouble wx, wy, clip_width, clip_height;
497 item = GNOME_CANVAS_ITEM (text);
499 /* Get canvas pixel coordinates for text position */
501 wx = 0;
502 wy = 0;
503 gnome_canvas_item_i2w (item, &wx, &wy);
504 gnome_canvas_w2c (item->canvas, wx, wy, &text->cx, &text->cy);
505 gnome_canvas_w2c (item->canvas, wx, wy, &text->clip_cx, &text->clip_cy);
507 if (text->clip_width < 0)
508 clip_width = text->width;
509 else
510 clip_width = text->clip_width;
512 if (text->clip_height < 0)
513 clip_height = text->height;
514 else
515 clip_height = text->clip_height;
517 /* Get canvas pixel coordinates for clip rectangle position */
518 text->clip_cwidth = clip_width;
519 text->clip_cheight = clip_height;
521 text->text_cx = text->cx;
522 text->text_cy = text->cy;
524 /* Bounds */
526 if (text->clip) {
527 *px1 = text->clip_cx;
528 *py1 = text->clip_cy;
529 *px2 = text->clip_cx + text->clip_cwidth;
530 *py2 = text->clip_cy + text->clip_cheight;
531 } else {
532 *px1 = text->cx;
533 *py1 = text->cy;
534 *px2 = text->cx + text->width;
535 *py2 = text->cy + text->height;
539 static void
540 calc_height (EText *text)
542 GnomeCanvasItem *item;
543 gint old_height;
544 gint old_width;
545 gint width = 0;
546 gint height = 0;
548 item = GNOME_CANVAS_ITEM (text);
550 /* Calculate text dimensions */
552 old_height = text->height;
553 old_width = text->width;
555 if (text->layout)
556 pango_layout_get_pixel_size (text->layout, &width, &height);
558 text->height = height;
559 text->width = width;
561 if (old_height != text->height || old_width != text->width)
562 e_canvas_item_request_parent_reflow (item);
565 static void
566 calc_ellipsis (EText *text)
568 /* FIXME: a pango layout per calc_ellipsis sucks */
569 gint width;
570 PangoLayout *layout = gtk_widget_create_pango_layout (
571 GTK_WIDGET (GNOME_CANVAS_ITEM (text)->canvas),
572 text->ellipsis ? text->ellipsis : "...");
573 pango_layout_get_size (layout, &width, NULL);
575 text->ellipsis_width = width;
577 g_object_unref (layout);
580 static void
581 split_into_lines (EText *text)
583 text->num_lines = pango_layout_get_line_count (text->layout);
586 /* Set_arg handler for the text item */
587 static void
588 e_text_set_property (GObject *object,
589 guint property_id,
590 const GValue *value,
591 GParamSpec *pspec)
593 GnomeCanvasItem *item;
594 EText *text;
595 GdkColor color = { 0, 0, 0, 0, };
596 GdkColor *pcolor;
598 gboolean needs_update = 0;
599 gboolean needs_reflow = 0;
601 item = GNOME_CANVAS_ITEM (object);
602 text = E_TEXT (object);
604 switch (property_id) {
605 case PROP_MODEL:
607 if (text->model_changed_signal_id)
608 g_signal_handler_disconnect (
609 text->model,
610 text->model_changed_signal_id);
612 if (text->model_repos_signal_id)
613 g_signal_handler_disconnect (
614 text->model,
615 text->model_repos_signal_id);
617 g_object_unref (text->model);
618 text->model = E_TEXT_MODEL (g_value_get_object (value));
619 g_object_ref (text->model);
621 text->model_changed_signal_id = g_signal_connect (
622 text->model, "changed",
623 G_CALLBACK (e_text_text_model_changed), text);
625 text->model_repos_signal_id = g_signal_connect (
626 text->model, "reposition",
627 G_CALLBACK (e_text_text_model_reposition), text);
629 text->text = e_text_model_get_text (text->model);
630 g_signal_emit (text, e_text_signals[E_TEXT_CHANGED], 0);
632 text->needs_split_into_lines = 1;
633 needs_reflow = 1;
634 break;
636 case PROP_EVENT_PROCESSOR:
637 if (text->tep && text->tep_command_id)
638 g_signal_handler_disconnect (
639 text->tep,
640 text->tep_command_id);
641 if (text->tep) {
642 g_object_unref (text->tep);
644 text->tep = E_TEXT_EVENT_PROCESSOR (g_value_get_object (value));
645 g_object_ref (text->tep);
647 text->tep_command_id = g_signal_connect (
648 text->tep, "command",
649 G_CALLBACK (e_text_command), text);
651 if (!text->allow_newlines)
652 g_object_set (
653 text->tep,
654 "allow_newlines", FALSE,
655 NULL);
656 break;
658 case PROP_TEXT:
659 e_text_model_set_text (text->model, g_value_get_string (value));
660 break;
662 case PROP_BOLD:
663 text->bold = g_value_get_boolean (value);
665 text->needs_redraw = 1;
666 text->needs_recalc_bounds = 1;
667 if (text->line_wrap)
668 text->needs_split_into_lines = 1;
669 else {
670 text->needs_calc_height = 1;
672 needs_update = 1;
673 needs_reflow = 1;
674 break;
676 case PROP_STRIKEOUT:
677 text->strikeout = g_value_get_boolean (value);
678 text->needs_redraw = 1;
679 needs_update = 1;
680 break;
682 case PROP_ITALIC:
683 text->italic = g_value_get_boolean (value);
684 text->needs_redraw = 1;
685 needs_update = 1;
686 break;
688 case PROP_JUSTIFICATION:
689 text->justification = g_value_get_enum (value);
690 text->needs_redraw = 1;
691 needs_update = 1;
692 break;
694 case PROP_CLIP_WIDTH:
695 text->clip_width = fabs (g_value_get_double (value));
696 calc_ellipsis (text);
697 if (text->line_wrap) {
698 if (text->layout)
699 pango_layout_set_width (
700 text->layout, text->clip_width < 0
701 ? -1 : text->clip_width * PANGO_SCALE);
702 text->needs_split_into_lines = 1;
703 } else {
704 text->needs_calc_height = 1;
706 needs_reflow = 1;
707 break;
709 case PROP_CLIP_HEIGHT:
710 text->clip_height = fabs (g_value_get_double (value));
711 text->needs_recalc_bounds = 1;
712 /* toshok: kind of a hack - set needs_reset_layout
713 * here so when something about the style/them
714 * changes, we redraw the text at the proper size/with
715 * the proper font. */
716 text->needs_reset_layout = 1;
717 needs_reflow = 1;
718 break;
720 case PROP_CLIP:
721 text->clip = g_value_get_boolean (value);
722 calc_ellipsis (text);
723 if (text->line_wrap)
724 text->needs_split_into_lines = 1;
725 else {
726 text->needs_calc_height = 1;
728 needs_reflow = 1;
729 break;
731 case PROP_FILL_CLIP_RECTANGLE:
732 text->fill_clip_rectangle = g_value_get_boolean (value);
733 needs_update = 1;
734 break;
736 case PROP_X_OFFSET:
737 text->xofs = g_value_get_double (value);
738 text->needs_recalc_bounds = 1;
739 needs_update = 1;
740 break;
742 case PROP_Y_OFFSET:
743 text->yofs = g_value_get_double (value);
744 text->needs_recalc_bounds = 1;
745 needs_update = 1;
746 break;
748 case PROP_FILL_COLOR:
749 if (g_value_get_string (value) &&
750 !gdk_color_parse (g_value_get_string (value), &color)) {
751 g_warning ("%s: Failed to parse color '%s'", G_STRFUNC, g_value_get_string (value));
752 break;
755 text->rgba = ((e_color_to_value (&color) & 0xffffff) << 8) | 0xff;
756 text->rgba_set = TRUE;
757 text->needs_redraw = 1;
758 needs_update = 1;
759 break;
761 case PROP_FILL_COLOR_GDK:
762 pcolor = g_value_get_boxed (value);
763 if (pcolor) {
764 color = *pcolor;
767 text->rgba = ((e_color_to_value (&color) & 0xffffff) << 8) | 0xff;
768 text->rgba_set = TRUE;
769 text->needs_redraw = 1;
770 needs_update = 1;
771 break;
773 case PROP_FILL_COLOR_RGBA:
774 text->rgba = g_value_get_uint (value);
775 color.red = ((text->rgba >> 24) & 0xff) * 0x101;
776 color.green = ((text->rgba >> 16) & 0xff) * 0x101;
777 color.blue = ((text->rgba >> 8) & 0xff) * 0x101;
778 text->rgba_set = TRUE;
779 text->needs_redraw = 1;
780 needs_update = 1;
781 break;
783 case PROP_EDITABLE:
784 text->editable = g_value_get_boolean (value);
785 text->needs_redraw = 1;
786 needs_update = 1;
787 break;
789 case PROP_USE_ELLIPSIS:
790 text->use_ellipsis = g_value_get_boolean (value);
791 needs_reflow = 1;
792 break;
794 case PROP_ELLIPSIS:
795 if (text->ellipsis)
796 g_free (text->ellipsis);
798 text->ellipsis = g_strdup (g_value_get_string (value));
799 calc_ellipsis (text);
800 needs_reflow = 1;
801 break;
803 case PROP_LINE_WRAP:
804 text->line_wrap = g_value_get_boolean (value);
805 if (text->line_wrap) {
806 if (text->layout) {
807 pango_layout_set_width (
808 text->layout, text->width < 0
809 ? -1 : text->width * PANGO_SCALE);
812 text->needs_split_into_lines = 1;
813 needs_reflow = 1;
814 break;
816 case PROP_BREAK_CHARACTERS:
817 if (text->break_characters) {
818 g_free (text->break_characters);
819 text->break_characters = NULL;
821 if (g_value_get_string (value))
822 text->break_characters = g_strdup (g_value_get_string (value));
823 text->needs_split_into_lines = 1;
824 needs_reflow = 1;
825 break;
827 case PROP_MAX_LINES:
828 text->max_lines = g_value_get_int (value);
829 text->needs_split_into_lines = 1;
830 needs_reflow = 1;
831 break;
833 case PROP_WIDTH:
834 text->clip_width = fabs (g_value_get_double (value));
835 calc_ellipsis (text);
836 if (text->line_wrap) {
837 if (text->layout) {
838 pango_layout_set_width (
839 text->layout, text->width < 0 ?
840 -1 : text->width * PANGO_SCALE);
842 text->needs_split_into_lines = 1;
844 else {
845 text->needs_calc_height = 1;
847 needs_reflow = 1;
848 break;
850 case PROP_ALLOW_NEWLINES:
851 text->allow_newlines = g_value_get_boolean (value);
852 _get_tep (text);
853 g_object_set (
854 text->tep,
855 "allow_newlines", g_value_get_boolean (value),
856 NULL);
857 break;
859 case PROP_CURSOR_POS: {
860 ETextEventProcessorCommand command;
862 command.action = E_TEP_MOVE;
863 command.position = E_TEP_VALUE;
864 command.value = g_value_get_int (value);
865 command.time = GDK_CURRENT_TIME;
866 e_text_command (text->tep, &command, text);
867 break;
870 case PROP_IM_CONTEXT:
871 if (text->im_context) {
872 disconnect_im_context (text);
873 g_object_unref (text->im_context);
876 text->im_context = g_value_get_object (value);
877 if (text->im_context)
878 g_object_ref (text->im_context);
880 text->need_im_reset = TRUE;
881 break;
883 case PROP_HANDLE_POPUP:
884 text->handle_popup = g_value_get_boolean (value);
885 break;
887 default:
888 return;
891 if (needs_reflow)
892 e_canvas_item_request_reflow (item);
893 if (needs_update)
894 gnome_canvas_item_request_update (item);
897 /* Get_arg handler for the text item */
898 static void
899 e_text_get_property (GObject *object,
900 guint property_id,
901 GValue *value,
902 GParamSpec *pspec)
904 EText *text;
906 text = E_TEXT (object);
908 switch (property_id) {
909 case PROP_MODEL:
910 g_value_set_object (value, text->model);
911 break;
913 case PROP_EVENT_PROCESSOR:
914 _get_tep (text);
915 g_value_set_object (value, text->tep);
916 break;
918 case PROP_TEXT:
919 g_value_set_string (value, text->text);
920 break;
922 case PROP_BOLD:
923 g_value_set_boolean (value, text->bold);
924 break;
926 case PROP_STRIKEOUT:
927 g_value_set_boolean (value, text->strikeout);
928 break;
930 case PROP_ITALIC:
931 g_value_set_boolean (value, text->italic);
932 break;
934 case PROP_JUSTIFICATION:
935 g_value_set_enum (value, text->justification);
936 break;
938 case PROP_CLIP_WIDTH:
939 g_value_set_double (value, text->clip_width);
940 break;
942 case PROP_CLIP_HEIGHT:
943 g_value_set_double (value, text->clip_height);
944 break;
946 case PROP_CLIP:
947 g_value_set_boolean (value, text->clip);
948 break;
950 case PROP_FILL_CLIP_RECTANGLE:
951 g_value_set_boolean (value, text->fill_clip_rectangle);
952 break;
954 case PROP_X_OFFSET:
955 g_value_set_double (value, text->xofs);
956 break;
958 case PROP_Y_OFFSET:
959 g_value_set_double (value, text->yofs);
960 break;
962 case PROP_FILL_COLOR_RGBA:
963 g_value_set_uint (value, text->rgba);
964 break;
966 case PROP_TEXT_WIDTH:
967 g_value_set_double (value, text->width);
968 break;
970 case PROP_TEXT_HEIGHT:
971 g_value_set_double (value, text->height);
972 break;
974 case PROP_EDITABLE:
975 g_value_set_boolean (value, text->editable);
976 break;
978 case PROP_USE_ELLIPSIS:
979 g_value_set_boolean (value, text->use_ellipsis);
980 break;
982 case PROP_ELLIPSIS:
983 g_value_set_string (value, text->ellipsis);
984 break;
986 case PROP_LINE_WRAP:
987 g_value_set_boolean (value, text->line_wrap);
988 break;
990 case PROP_BREAK_CHARACTERS:
991 g_value_set_string (value, text->break_characters);
992 break;
994 case PROP_MAX_LINES:
995 g_value_set_int (value, text->max_lines);
996 break;
998 case PROP_WIDTH:
999 g_value_set_double (value, text->clip_width);
1000 break;
1002 case PROP_HEIGHT:
1003 g_value_set_double (
1004 value, text->clip &&
1005 text->clip_height != -1 ?
1006 text->clip_height : text->height);
1007 break;
1009 case PROP_ALLOW_NEWLINES:
1010 g_value_set_boolean (value, text->allow_newlines);
1011 break;
1013 case PROP_CURSOR_POS:
1014 g_value_set_int (value, text->selection_start);
1015 break;
1017 case PROP_IM_CONTEXT:
1018 g_value_set_object (value, text->im_context);
1019 break;
1021 case PROP_HANDLE_POPUP:
1022 g_value_set_boolean (value, text->handle_popup);
1023 break;
1025 default:
1026 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1027 break;
1031 /* Update handler for the text item */
1032 static void
1033 e_text_reflow (GnomeCanvasItem *item,
1034 gint flags)
1036 EText *text;
1038 text = E_TEXT (item);
1040 if (text->needs_reset_layout) {
1041 reset_layout (text);
1042 text->needs_reset_layout = 0;
1043 text->needs_calc_height = 1;
1046 if (text->needs_split_into_lines) {
1047 split_into_lines (text);
1049 text->needs_split_into_lines = 0;
1050 text->needs_calc_height = 1;
1053 if (text->needs_calc_height) {
1054 calc_height (text);
1055 gnome_canvas_item_request_update (item);
1056 text->needs_calc_height = 0;
1057 text->needs_recalc_bounds = 1;
1061 /* Update handler for the text item */
1062 static void
1063 e_text_update (GnomeCanvasItem *item,
1064 const cairo_matrix_t *i2c,
1065 gint flags)
1067 EText *text;
1068 gdouble x1, y1, x2, y2;
1070 text = E_TEXT (item);
1072 if (GNOME_CANVAS_ITEM_CLASS (e_text_parent_class)->update)
1073 GNOME_CANVAS_ITEM_CLASS (e_text_parent_class)->update (
1074 item, i2c, flags);
1076 if (text->needs_recalc_bounds
1077 || (flags & GNOME_CANVAS_UPDATE_AFFINE)) {
1078 get_bounds (text, &x1, &y1, &x2, &y2);
1079 if (item->x1 != x1 ||
1080 item->x2 != x2 ||
1081 item->y1 != y1 ||
1082 item->y2 != y2) {
1083 gnome_canvas_request_redraw (
1084 item->canvas, item->x1, item->y1,
1085 item->x2, item->y2);
1086 item->x1 = x1;
1087 item->y1 = y1;
1088 item->x2 = x2;
1089 item->y2 = y2;
1090 text->needs_redraw = 1;
1091 item->canvas->need_repick = TRUE;
1093 if (!text->fill_clip_rectangle)
1094 item->canvas->need_repick = TRUE;
1095 text->needs_recalc_bounds = 0;
1097 if (text->needs_redraw) {
1098 gnome_canvas_request_redraw (
1099 item->canvas, item->x1, item->y1, item->x2, item->y2);
1100 text->needs_redraw = 0;
1104 /* Realize handler for the text item */
1105 static void
1106 e_text_realize (GnomeCanvasItem *item)
1108 EText *text;
1110 text = E_TEXT (item);
1112 if (GNOME_CANVAS_ITEM_CLASS (e_text_parent_class)->realize)
1113 (* GNOME_CANVAS_ITEM_CLASS (e_text_parent_class)->realize) (item);
1115 create_layout (text);
1117 text->i_cursor = gdk_cursor_new (GDK_XTERM);
1118 text->default_cursor = gdk_cursor_new (GDK_LEFT_PTR);
1121 /* Unrealize handler for the text item */
1122 static void
1123 e_text_unrealize (GnomeCanvasItem *item)
1125 EText *text;
1127 text = E_TEXT (item);
1129 g_object_unref (text->i_cursor);
1130 text->i_cursor = NULL;
1131 g_object_unref (text->default_cursor);
1132 text->default_cursor = NULL;
1134 if (GNOME_CANVAS_ITEM_CLASS (e_text_parent_class)->unrealize)
1135 (* GNOME_CANVAS_ITEM_CLASS (e_text_parent_class)->unrealize) (item);
1138 static void
1139 _get_tep (EText *text)
1141 if (!text->tep) {
1142 text->tep = e_text_event_processor_emacs_like_new ();
1144 text->tep_command_id = g_signal_connect (
1145 text->tep, "command",
1146 G_CALLBACK (e_text_command), text);
1150 static void
1151 draw_pango_rectangle (cairo_t *cr,
1152 gint x1,
1153 gint y1,
1154 PangoRectangle rect)
1156 gint width = rect.width / PANGO_SCALE;
1157 gint height = rect.height / PANGO_SCALE;
1159 if (width <= 0)
1160 width = 1;
1161 if (height <= 0)
1162 height = 1;
1164 cairo_rectangle (
1165 cr, x1 + rect.x / PANGO_SCALE,
1166 y1 + rect.y / PANGO_SCALE, width, height);
1167 cairo_fill (cr);
1170 static gboolean
1171 show_pango_rectangle (EText *text,
1172 PangoRectangle rect)
1174 gint x1 = rect.x / PANGO_SCALE;
1175 gint x2 = (rect.x + rect.width) / PANGO_SCALE;
1177 gint y1 = rect.y / PANGO_SCALE;
1178 gint y2 = (rect.y + rect.height) / PANGO_SCALE;
1180 gint new_xofs_edit = text->xofs_edit;
1181 gint new_yofs_edit = text->yofs_edit;
1183 gint clip_width, clip_height;
1185 clip_width = text->clip_width;
1186 clip_height = text->clip_height;
1188 if (x1 < new_xofs_edit)
1189 new_xofs_edit = x1;
1191 if (y1 < new_yofs_edit)
1192 new_yofs_edit = y1;
1194 if (clip_width >= 0) {
1195 if (2 + x2 - clip_width > new_xofs_edit)
1196 new_xofs_edit = 2 + x2 - clip_width;
1197 } else {
1198 new_xofs_edit = 0;
1201 if (clip_height >= 0) {
1202 if (y2 - clip_height > new_yofs_edit)
1203 new_yofs_edit = y2 - clip_height;
1204 } else {
1205 new_yofs_edit = 0;
1208 if (new_xofs_edit < 0)
1209 new_xofs_edit = 0;
1210 if (new_yofs_edit < 0)
1211 new_yofs_edit = 0;
1213 if (new_xofs_edit != text->xofs_edit ||
1214 new_yofs_edit != text->yofs_edit) {
1215 text->xofs_edit = new_xofs_edit;
1216 text->yofs_edit = new_yofs_edit;
1217 return TRUE;
1220 return FALSE;
1223 /* Draw handler for the text item */
1224 static void
1225 e_text_draw (GnomeCanvasItem *item,
1226 cairo_t *cr,
1227 gint x,
1228 gint y,
1229 gint width,
1230 gint height)
1232 EText *text;
1233 gint xpos, ypos;
1234 GnomeCanvas *canvas;
1235 GtkWidget *widget;
1236 GdkRGBA rgba;
1237 gboolean backdrop;
1239 text = E_TEXT (item);
1240 canvas = GNOME_CANVAS_ITEM (text)->canvas;
1241 widget = GTK_WIDGET (canvas);
1242 backdrop = (gtk_widget_get_state_flags (widget) & GTK_STATE_FLAG_BACKDROP) != 0;
1244 cairo_save (cr);
1246 if (!text->rgba_set) {
1247 e_utils_get_theme_color (widget, backdrop ? "theme_unfocused_fg_color,theme_fg_color" : "theme_fg_color", E_UTILS_DEFAULT_THEME_FG_COLOR, &rgba);
1248 gdk_cairo_set_source_rgba (cr, &rgba);
1249 } else {
1250 cairo_set_source_rgba (
1252 ((text->rgba >> 24) & 0xff) / 255.0,
1253 ((text->rgba >> 16) & 0xff) / 255.0,
1254 ((text->rgba >> 8) & 0xff) / 255.0,
1255 ( text->rgba & 0xff) / 255.0);
1258 /* Insert preedit text only when im_context signals are connected &
1259 * text->preedit_len is not zero */
1260 if (text->im_context_signals_registered && text->preedit_len)
1261 insert_preedit_text (text);
1263 /* Need to reset the layout to cleanly clear the preedit buffer when
1264 * typing in CJK & using backspace on the preedit */
1265 if (!text->preedit_len)
1266 reset_layout (text);
1268 if (!pango_layout_get_text (text->layout)) {
1269 cairo_restore (cr);
1270 return;
1273 xpos = text->text_cx;
1274 ypos = text->text_cy;
1276 xpos = xpos - x + text->xofs;
1277 ypos = ypos - y + text->yofs;
1279 cairo_save (cr);
1281 if (text->clip) {
1282 cairo_rectangle (
1283 cr, xpos, ypos,
1284 text->clip_cwidth - text->xofs,
1285 text->clip_cheight - text->yofs);
1286 cairo_clip (cr);
1289 if (text->editing) {
1290 xpos -= text->xofs_edit;
1291 ypos -= text->yofs_edit;
1294 cairo_move_to (cr, xpos, ypos);
1295 pango_cairo_show_layout (cr, text->layout);
1297 if (text->editing) {
1298 if (text->selection_start != text->selection_end) {
1299 cairo_region_t *clip_region;
1300 gint indices[2];
1302 indices[0] = MIN (
1303 text->selection_start,
1304 text->selection_end);
1305 indices[1] = MAX (
1306 text->selection_start,
1307 text->selection_end);
1309 /* convert these into byte indices */
1310 indices[0] = g_utf8_offset_to_pointer (
1311 text->text, indices[0]) - text->text;
1312 indices[1] = g_utf8_offset_to_pointer (
1313 text->text, indices[1]) - text->text;
1315 clip_region = gdk_pango_layout_get_clip_region (
1316 text->layout, xpos, ypos, indices, 1);
1317 gdk_cairo_region (cr, clip_region);
1318 cairo_clip (cr);
1319 cairo_region_destroy (clip_region);
1321 e_utils_get_theme_color (widget, backdrop ? "theme_unfocused_base_color,theme_base_color" : "theme_base_color", E_UTILS_DEFAULT_THEME_BASE_COLOR, &rgba);
1323 gdk_cairo_set_source_rgba (cr, &rgba);
1324 cairo_paint (cr);
1326 e_utils_get_theme_color (widget, backdrop ? "theme_unfocused_text_color,theme_text_color,theme_fg_color" : "theme_text_color,theme_fg_color", E_UTILS_DEFAULT_THEME_TEXT_COLOR, &rgba);
1327 gdk_cairo_set_source_rgba (cr, &rgba);
1328 cairo_move_to (cr, xpos, ypos);
1329 pango_cairo_show_layout (cr, text->layout);
1330 } else {
1331 if (text->show_cursor) {
1332 PangoRectangle strong_pos, weak_pos;
1333 gchar *offs;
1335 offs = g_utf8_offset_to_pointer (
1336 text->text, text->selection_start);
1338 pango_layout_get_cursor_pos (
1339 text->layout, offs - text->text +
1340 text->preedit_len, &strong_pos,
1341 &weak_pos);
1342 draw_pango_rectangle (cr, xpos, ypos, strong_pos);
1343 if (strong_pos.x != weak_pos.x ||
1344 strong_pos.y != weak_pos.y ||
1345 strong_pos.width != weak_pos.width ||
1346 strong_pos.height != weak_pos.height)
1347 draw_pango_rectangle (cr, xpos, ypos, weak_pos);
1352 cairo_restore (cr);
1353 cairo_restore (cr);
1356 /* Point handler for the text item */
1357 static GnomeCanvasItem *
1358 e_text_point (GnomeCanvasItem *item,
1359 gdouble x,
1360 gdouble y,
1361 gint cx,
1362 gint cy)
1364 EText *text;
1365 gdouble clip_width;
1366 gdouble clip_height;
1368 text = E_TEXT (item);
1370 /* The idea is to build bounding rectangles for each of the lines of
1371 * text (clipped by the clipping rectangle, if it is activated) and see
1372 * whether the point is inside any of these. If it is, we are done.
1373 * Otherwise, calculate the distance to the nearest rectangle.
1376 if (text->clip_width < 0)
1377 clip_width = text->width;
1378 else
1379 clip_width = text->clip_width;
1381 if (text->clip_height < 0)
1382 clip_height = text->height;
1383 else
1384 clip_height = text->clip_height;
1386 if (cx < text->clip_cx ||
1387 cx > text->clip_cx + clip_width ||
1388 cy < text->clip_cy ||
1389 cy > text->clip_cy + clip_height)
1390 return NULL;
1392 if (text->fill_clip_rectangle || !text->text || !*text->text)
1393 return item;
1395 cx -= text->cx;
1397 if (pango_layout_xy_to_index (text->layout, cx, cy, NULL, NULL))
1398 return item;
1400 return NULL;
1403 /* Bounds handler for the text item */
1404 static void
1405 e_text_bounds (GnomeCanvasItem *item,
1406 gdouble *x1,
1407 gdouble *y1,
1408 gdouble *x2,
1409 gdouble *y2)
1411 EText *text;
1412 gdouble width, height;
1414 text = E_TEXT (item);
1416 *x1 = 0;
1417 *y1 = 0;
1419 width = text->width;
1420 height = text->height;
1422 if (text->clip) {
1423 if (text->clip_width >= 0)
1424 width = text->clip_width;
1425 if (text->clip_height >= 0)
1426 height = text->clip_height;
1429 *x2 = *x1 + width;
1430 *y2 = *y1 + height;
1433 static gint
1434 get_position_from_xy (EText *text,
1435 gint x,
1436 gint y)
1438 gint index;
1439 gint trailing;
1441 x -= text->xofs;
1442 y -= text->yofs;
1444 if (text->editing) {
1445 x += text->xofs_edit;
1446 y += text->yofs_edit;
1449 x -= text->cx;
1450 y -= text->cy;
1452 pango_layout_xy_to_index (
1453 text->layout, x * PANGO_SCALE,
1454 y * PANGO_SCALE, &index, &trailing);
1456 return g_utf8_pointer_to_offset (text->text, text->text + index + trailing);
1459 #define SCROLL_WAIT_TIME 30000
1461 static gboolean
1462 _blink_scroll_timeout (gpointer data)
1464 EText *text = E_TEXT (data);
1465 gulong current_time;
1466 gboolean scroll = FALSE;
1467 gboolean redraw = FALSE;
1469 g_timer_elapsed (text->timer, &current_time);
1471 if (text->scroll_start + SCROLL_WAIT_TIME > 1000000) {
1472 if (current_time > text->scroll_start - (1000000 - SCROLL_WAIT_TIME) &&
1473 current_time < text->scroll_start)
1474 scroll = TRUE;
1475 } else {
1476 if (current_time > text->scroll_start + SCROLL_WAIT_TIME ||
1477 current_time < text->scroll_start)
1478 scroll = TRUE;
1480 if (scroll && text->button_down && text->clip) {
1481 gint old_xofs_edit = text->xofs_edit;
1482 gint old_yofs_edit = text->yofs_edit;
1484 if (text->clip_cwidth >= 0 &&
1485 text->lastx - text->clip_cx > text->clip_cwidth &&
1486 text->xofs_edit < text->width - text->clip_cwidth) {
1487 text->xofs_edit += 4;
1488 if (text->xofs_edit > text->width - text->clip_cwidth + 1)
1489 text->xofs_edit = text->width - text->clip_cwidth + 1;
1491 if (text->lastx - text->clip_cx < 0 &&
1492 text->xofs_edit > 0) {
1493 text->xofs_edit -= 4;
1494 if (text->xofs_edit < 0)
1495 text->xofs_edit = 0;
1498 if (text->clip_cheight >= 0 &&
1499 text->lasty - text->clip_cy > text->clip_cheight &&
1500 text->yofs_edit < text->height - text->clip_cheight) {
1501 text->yofs_edit += 4;
1502 if (text->yofs_edit > text->height - text->clip_cheight + 1)
1503 text->yofs_edit = text->height - text->clip_cheight + 1;
1505 if (text->lasty - text->clip_cy < 0 &&
1506 text->yofs_edit > 0) {
1507 text->yofs_edit -= 4;
1508 if (text->yofs_edit < 0)
1509 text->yofs_edit = 0;
1512 if (old_xofs_edit != text->xofs_edit ||
1513 old_yofs_edit != text->yofs_edit) {
1514 ETextEventProcessorEvent e_tep_event;
1515 e_tep_event.type = GDK_MOTION_NOTIFY;
1516 e_tep_event.motion.state = text->last_state;
1517 e_tep_event.motion.time = 0;
1518 e_tep_event.motion.position =
1519 get_position_from_xy (
1520 text, text->lastx, text->lasty);
1521 _get_tep (text);
1522 e_text_event_processor_handle_event (
1523 text->tep,
1524 &e_tep_event);
1525 text->scroll_start = current_time;
1526 redraw = TRUE;
1530 if (!((current_time / 500000) % 2)) {
1531 if (!text->show_cursor)
1532 redraw = TRUE;
1533 text->show_cursor = TRUE;
1534 } else {
1535 if (text->show_cursor)
1536 redraw = TRUE;
1537 text->show_cursor = FALSE;
1539 if (redraw) {
1540 text->needs_redraw = 1;
1541 gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (text));
1543 return TRUE;
1546 static void
1547 start_editing (EText *text)
1549 if (text->editing)
1550 return;
1552 e_text_reset_im_context (text);
1554 g_free (text->revert);
1555 text->revert = g_strdup (text->text);
1557 text->editing = TRUE;
1558 if (text->pointer_in) {
1559 GdkWindow *window;
1561 window = gtk_widget_get_window (
1562 GTK_WIDGET (GNOME_CANVAS_ITEM (text)->canvas));
1564 if (text->default_cursor_shown) {
1565 gdk_window_set_cursor (window, text->i_cursor);
1566 text->default_cursor_shown = FALSE;
1569 text->select_by_word = FALSE;
1570 text->xofs_edit = 0;
1571 text->yofs_edit = 0;
1572 if (text->timeout_id == 0) {
1573 text->timeout_id = e_named_timeout_add (
1574 10, _blink_scroll_timeout, text);
1576 text->timer = g_timer_new ();
1577 g_timer_elapsed (text->timer, &(text->scroll_start));
1578 g_timer_start (text->timer);
1581 void
1582 e_text_stop_editing (EText *text)
1584 if (!text->editing)
1585 return;
1587 g_free (text->revert);
1588 text->revert = NULL;
1590 text->editing = FALSE;
1591 if (!text->default_cursor_shown) {
1592 GdkWindow *window;
1594 window = gtk_widget_get_window (
1595 GTK_WIDGET (GNOME_CANVAS_ITEM (text)->canvas));
1596 gdk_window_set_cursor (window, text->default_cursor);
1597 text->default_cursor_shown = TRUE;
1599 if (text->timer) {
1600 g_timer_stop (text->timer);
1601 g_timer_destroy (text->timer);
1602 text->timer = NULL;
1605 text->need_im_reset = TRUE;
1606 text->preedit_len = 0;
1607 text->preedit_pos = 0;
1610 void
1611 e_text_cancel_editing (EText *text)
1613 if (text->revert)
1614 e_text_model_set_text (text->model, text->revert);
1615 e_text_stop_editing (text);
1618 static gboolean
1619 _click (gpointer data)
1621 *(gint *)data = 0;
1622 return FALSE;
1625 static gint
1626 e_text_event (GnomeCanvasItem *item,
1627 GdkEvent *event)
1629 EText *text = E_TEXT (item);
1630 ETextEventProcessorEvent e_tep_event;
1631 GdkWindow *window;
1632 gint return_val = 0;
1634 if (!text->model)
1635 return 0;
1637 window = gtk_widget_get_window (GTK_WIDGET (item->canvas));
1639 e_tep_event.type = event->type;
1640 switch (event->type) {
1641 case GDK_FOCUS_CHANGE:
1642 if (text->editable) {
1643 GdkEventFocus *focus_event;
1644 focus_event = (GdkEventFocus *) event;
1645 if (focus_event->in) {
1646 if (text->im_context) {
1647 if (!text->im_context_signals_registered) {
1648 g_signal_connect (
1649 text->im_context, "commit",
1650 G_CALLBACK (e_text_commit_cb), text);
1651 g_signal_connect (
1652 text->im_context, "preedit_changed",
1653 G_CALLBACK (e_text_preedit_changed_cb), text);
1654 g_signal_connect (
1655 text->im_context, "retrieve_surrounding",
1656 G_CALLBACK (e_text_retrieve_surrounding_cb), text);
1657 g_signal_connect (
1658 text->im_context, "delete_surrounding",
1659 G_CALLBACK (e_text_delete_surrounding_cb), text);
1660 text->im_context_signals_registered = TRUE;
1662 gtk_im_context_focus_in (text->im_context);
1665 start_editing (text);
1667 /* So we'll redraw and the
1668 * cursor will be shown. */
1669 text->show_cursor = FALSE;
1670 } else {
1671 if (text->im_context) {
1672 gtk_im_context_focus_out (text->im_context);
1673 disconnect_im_context (text);
1674 text->need_im_reset = TRUE;
1677 e_text_stop_editing (text);
1678 if (text->timeout_id) {
1679 g_source_remove (text->timeout_id);
1680 text->timeout_id = 0;
1682 if (text->show_cursor) {
1683 text->show_cursor = FALSE;
1684 text->needs_redraw = 1;
1685 gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (text));
1688 if (text->line_wrap)
1689 text->needs_split_into_lines = 1;
1690 e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (text));
1692 return_val = 0;
1693 break;
1694 case GDK_KEY_PRESS:
1696 /* Handle S-F10 key binding here. */
1698 if (event->key.keyval == GDK_KEY_F10
1699 && (event->key.state & GDK_SHIFT_MASK)
1700 && text->handle_popup) {
1702 /* Simulate a GdkEventButton here, so that we can
1703 * call e_text_do_popup directly */
1705 GdkEvent *button_event;
1707 button_event = gdk_event_new (GDK_BUTTON_PRESS);
1708 button_event->button.time = event->key.time;
1709 button_event->button.button = 0;
1710 e_text_do_popup (text, button_event, 0);
1711 return 1;
1714 /* Fall Through */
1716 case GDK_KEY_RELEASE:
1718 if (text->editing) {
1719 GdkEventKey key;
1720 gint ret;
1722 if (text->im_context &&
1723 gtk_im_context_filter_keypress (
1724 text->im_context,
1725 (GdkEventKey *) event)) {
1726 text->need_im_reset = TRUE;
1727 return 1;
1730 key = event->key;
1731 e_tep_event.key.time = key.time;
1732 e_tep_event.key.state = key.state;
1733 e_tep_event.key.keyval = key.keyval;
1735 /* This is probably ugly hack, but we
1736 * have to handle UTF-8 input somehow. */
1737 e_tep_event.key.string = e_utf8_from_gtk_event_key (
1738 GTK_WIDGET (item->canvas),
1739 key.keyval, key.string);
1740 if (e_tep_event.key.string != NULL) {
1741 e_tep_event.key.length = strlen (e_tep_event.key.string);
1742 } else {
1743 e_tep_event.key.length = 0;
1746 _get_tep (text);
1747 ret = e_text_event_processor_handle_event (text->tep, &e_tep_event);
1749 if (event->type == GDK_KEY_PRESS)
1750 g_signal_emit (
1751 text, e_text_signals[E_TEXT_KEYPRESS], 0,
1752 e_tep_event.key.keyval, e_tep_event.key.state);
1754 if (e_tep_event.key.string)
1755 g_free ((gpointer) e_tep_event.key.string);
1757 return ret;
1759 break;
1760 case GDK_BUTTON_PRESS: /* Fall Through */
1761 case GDK_BUTTON_RELEASE:
1762 if ((!text->editing)
1763 && text->editable
1764 && (event->button.button == 1 ||
1765 event->button.button == 2)) {
1766 e_canvas_item_grab_focus (item, TRUE);
1767 start_editing (text);
1770 /* We follow convention and emit popup events on right-clicks. */
1771 if (event->type == GDK_BUTTON_PRESS && event->button.button == 3) {
1772 if (text->handle_popup) {
1773 e_text_do_popup (
1774 text, event,
1775 get_position_from_xy (
1776 text, event->button.x,
1777 event->button.y));
1778 return 1;
1780 else {
1781 break;
1785 /* Create our own double and triple click events,
1786 * as gnome-canvas doesn't forward them to us */
1787 if (event->type == GDK_BUTTON_PRESS) {
1788 if (text->dbl_timeout == 0 &&
1789 text->tpl_timeout == 0) {
1790 text->dbl_timeout = e_named_timeout_add (
1791 200, _click, &(text->dbl_timeout));
1792 } else {
1793 if (text->tpl_timeout == 0) {
1794 e_tep_event.type = GDK_2BUTTON_PRESS;
1795 text->tpl_timeout = e_named_timeout_add (
1796 200, _click, &(text->tpl_timeout));
1797 } else {
1798 e_tep_event.type = GDK_3BUTTON_PRESS;
1803 if (text->editing) {
1804 GdkEventButton button = event->button;
1805 e_tep_event.button.time = button.time;
1806 e_tep_event.button.state = button.state;
1807 e_tep_event.button.button = button.button;
1808 e_tep_event.button.position =
1809 get_position_from_xy (
1810 text, button.x, button.y);
1811 e_tep_event.button.device =
1812 gdk_event_get_device (event);
1813 _get_tep (text);
1814 return_val = e_text_event_processor_handle_event (
1815 text->tep, &e_tep_event);
1816 if (event->button.button == 1) {
1817 if (event->type == GDK_BUTTON_PRESS)
1818 text->button_down = TRUE;
1819 else
1820 text->button_down = FALSE;
1822 text->lastx = button.x;
1823 text->lasty = button.y;
1824 text->last_state = button.state;
1826 break;
1827 case GDK_MOTION_NOTIFY:
1828 if (text->editing) {
1829 GdkEventMotion motion = event->motion;
1830 e_tep_event.motion.time = motion.time;
1831 e_tep_event.motion.state = motion.state;
1832 e_tep_event.motion.position =
1833 get_position_from_xy (
1834 text, motion.x, motion.y);
1835 _get_tep (text);
1836 return_val = e_text_event_processor_handle_event (
1837 text->tep, &e_tep_event);
1838 text->lastx = motion.x;
1839 text->lasty = motion.y;
1840 text->last_state = motion.state;
1842 break;
1843 case GDK_ENTER_NOTIFY:
1844 text->pointer_in = TRUE;
1845 if (text->editing) {
1846 if (text->default_cursor_shown) {
1847 gdk_window_set_cursor (window, text->i_cursor);
1848 text->default_cursor_shown = FALSE;
1851 break;
1852 case GDK_LEAVE_NOTIFY:
1853 text->pointer_in = FALSE;
1854 if (text->editing) {
1855 if (!text->default_cursor_shown) {
1856 gdk_window_set_cursor (
1857 window, text->default_cursor);
1858 text->default_cursor_shown = TRUE;
1861 break;
1862 default:
1863 break;
1865 if (return_val)
1866 return return_val;
1867 if (GNOME_CANVAS_ITEM_CLASS (e_text_parent_class)->event)
1868 return GNOME_CANVAS_ITEM_CLASS (e_text_parent_class)->event (item, event);
1869 else
1870 return 0;
1873 void
1874 e_text_copy_clipboard (EText *text)
1876 gint selection_start_pos;
1877 gint selection_end_pos;
1879 selection_start_pos = MIN (text->selection_start, text->selection_end);
1880 selection_end_pos = MAX (text->selection_start, text->selection_end);
1882 /* convert sel_start/sel_end to byte indices */
1883 selection_start_pos = g_utf8_offset_to_pointer (
1884 text->text, selection_start_pos) - text->text;
1885 selection_end_pos = g_utf8_offset_to_pointer (
1886 text->text, selection_end_pos) - text->text;
1888 gtk_clipboard_set_text (
1889 gtk_widget_get_clipboard (
1890 GTK_WIDGET (GNOME_CANVAS_ITEM (text)->canvas),
1891 GDK_SELECTION_CLIPBOARD),
1892 text->text + selection_start_pos,
1893 selection_end_pos - selection_start_pos);
1896 void
1897 e_text_delete_selection (EText *text)
1899 gint sel_start, sel_end;
1901 sel_start = MIN (text->selection_start, text->selection_end);
1902 sel_end = MAX (text->selection_start, text->selection_end);
1904 if (sel_start != sel_end)
1905 e_text_model_delete (text->model, sel_start, sel_end - sel_start);
1906 text->need_im_reset = TRUE;
1909 void
1910 e_text_cut_clipboard (EText *text)
1912 e_text_copy_clipboard (text);
1913 e_text_delete_selection (text);
1916 void
1917 e_text_paste_clipboard (EText *text)
1919 ETextEventProcessorCommand command;
1921 command.action = E_TEP_PASTE;
1922 command.position = E_TEP_SELECTION;
1923 command.string = "";
1924 command.value = 0;
1925 e_text_command (text->tep, &command, text);
1928 void
1929 e_text_select_all (EText *text)
1931 ETextEventProcessorCommand command;
1933 command.action = E_TEP_SELECT;
1934 command.position = E_TEP_SELECT_ALL;
1935 command.string = "";
1936 command.value = 0;
1937 e_text_command (text->tep, &command, text);
1940 static void
1941 primary_get_cb (GtkClipboard *clipboard,
1942 GtkSelectionData *selection_data,
1943 guint info,
1944 gpointer data)
1946 EText *text = E_TEXT (data);
1947 gint sel_start, sel_end;
1949 sel_start = MIN (text->selection_start, text->selection_end);
1950 sel_end = MAX (text->selection_start, text->selection_end);
1952 /* convert sel_start/sel_end to byte indices */
1953 sel_start = g_utf8_offset_to_pointer (text->text, sel_start) - text->text;
1954 sel_end = g_utf8_offset_to_pointer (text->text, sel_end) - text->text;
1956 if (sel_start != sel_end) {
1957 gtk_selection_data_set_text (
1958 selection_data,
1959 text->text + sel_start,
1960 sel_end - sel_start);
1964 static void
1965 primary_clear_cb (GtkClipboard *clipboard,
1966 gpointer data)
1968 #ifdef notyet
1969 /* XXX */
1970 gtk_editable_select_region (
1971 GTK_EDITABLE (entry), entry->current_pos, entry->current_pos);
1972 #endif
1975 static void
1976 e_text_update_primary_selection (EText *text)
1978 static const GtkTargetEntry targets[] = {
1979 { (gchar *) "UTF8_STRING", 0, 0 },
1980 { (gchar *) "UTF-8", 0, 0 },
1981 { (gchar *) "STRING", 0, 0 },
1982 { (gchar *) "TEXT", 0, 0 },
1983 { (gchar *) "COMPOUND_TEXT", 0, 0 }
1985 GtkClipboard *clipboard;
1987 clipboard = gtk_widget_get_clipboard (
1988 GTK_WIDGET (GNOME_CANVAS_ITEM (text)->canvas),
1989 GDK_SELECTION_PRIMARY);
1991 if (text->selection_start != text->selection_end) {
1992 if (!gtk_clipboard_set_with_owner (
1993 clipboard, targets, G_N_ELEMENTS (targets),
1994 primary_get_cb, primary_clear_cb, G_OBJECT (text)))
1995 primary_clear_cb (clipboard, text);
1996 } else {
1997 if (gtk_clipboard_get_owner (clipboard) == G_OBJECT (text))
1998 gtk_clipboard_clear (clipboard);
2002 static void
2003 paste_received (GtkClipboard *clipboard,
2004 const gchar *text,
2005 gpointer data)
2007 EText *etext = E_TEXT (data);
2009 if (text && g_utf8_validate (text, strlen (text), NULL)) {
2010 if (etext->selection_end != etext->selection_start)
2011 e_text_delete_selection (etext);
2013 e_text_insert (etext, text);
2016 g_object_unref (etext);
2019 static void
2020 e_text_paste (EText *text,
2021 GdkAtom selection)
2023 g_object_ref (text);
2024 gtk_clipboard_request_text (
2025 gtk_widget_get_clipboard (
2026 GTK_WIDGET (GNOME_CANVAS_ITEM (text)->canvas),
2027 selection), paste_received, text);
2030 typedef struct {
2031 EText *text;
2032 GdkEvent *event;
2033 gint position;
2034 } PopupClosure;
2036 static void
2037 popup_targets_received (GtkClipboard *clipboard,
2038 GtkSelectionData *data,
2039 gpointer user_data)
2041 PopupClosure *closure = user_data;
2042 EText *text = closure->text;
2043 GdkEvent *event = closure->event;
2044 gint position = closure->position;
2045 GtkWidget *popup_menu = gtk_menu_new ();
2046 GtkWidget *menuitem, *submenu;
2047 guint event_button = 0;
2048 GdkRectangle rect;
2050 gdk_event_get_button (event, &event_button);
2052 g_free (closure);
2054 gtk_menu_attach_to_widget (
2055 GTK_MENU (popup_menu),
2056 GTK_WIDGET (GNOME_CANVAS_ITEM (text)->canvas),
2057 NULL);
2058 g_signal_connect (popup_menu, "deactivate", G_CALLBACK (gtk_menu_detach), NULL);
2060 /* cut menu item */
2061 menuitem = gtk_image_menu_item_new_with_mnemonic (_("Cu_t"));
2062 gtk_image_menu_item_set_image (
2063 GTK_IMAGE_MENU_ITEM (menuitem),
2064 gtk_image_new_from_icon_name ("edit-cut", GTK_ICON_SIZE_MENU));
2065 gtk_widget_show (menuitem);
2066 gtk_menu_shell_append (GTK_MENU_SHELL (popup_menu), menuitem);
2067 g_signal_connect_swapped (
2068 menuitem, "activate",
2069 G_CALLBACK (e_text_cut_clipboard), text);
2070 gtk_widget_set_sensitive (
2071 menuitem, text->editable &&
2072 (text->selection_start != text->selection_end));
2074 /* copy menu item */
2075 menuitem = gtk_image_menu_item_new_with_mnemonic (_("_Copy"));
2076 gtk_image_menu_item_set_image (
2077 GTK_IMAGE_MENU_ITEM (menuitem),
2078 gtk_image_new_from_icon_name ("edit-copy", GTK_ICON_SIZE_MENU));
2079 gtk_widget_show (menuitem);
2080 gtk_menu_shell_append (GTK_MENU_SHELL (popup_menu), menuitem);
2081 g_signal_connect_swapped (
2082 menuitem, "activate",
2083 G_CALLBACK (e_text_copy_clipboard), text);
2084 gtk_widget_set_sensitive (menuitem, text->selection_start != text->selection_end);
2086 /* paste menu item */
2087 menuitem = gtk_image_menu_item_new_with_mnemonic (_("_Paste"));
2088 gtk_image_menu_item_set_image (
2089 GTK_IMAGE_MENU_ITEM (menuitem),
2090 gtk_image_new_from_icon_name ("edit-paste", GTK_ICON_SIZE_MENU));
2091 gtk_widget_show (menuitem);
2092 gtk_menu_shell_append (GTK_MENU_SHELL (popup_menu), menuitem);
2093 g_signal_connect_swapped (
2094 menuitem, "activate",
2095 G_CALLBACK (e_text_paste_clipboard), text);
2096 gtk_widget_set_sensitive (
2097 menuitem, text->editable &&
2098 gtk_selection_data_targets_include_text (data));
2100 menuitem = gtk_menu_item_new_with_label (_("Select All"));
2101 gtk_widget_show (menuitem);
2102 gtk_menu_shell_append (GTK_MENU_SHELL (popup_menu), menuitem);
2103 g_signal_connect_swapped (
2104 menuitem, "activate",
2105 G_CALLBACK (e_text_select_all), text);
2106 gtk_widget_set_sensitive (menuitem, strlen (text->text) > 0);
2108 menuitem = gtk_separator_menu_item_new ();
2109 gtk_widget_show (menuitem);
2110 gtk_menu_shell_append (GTK_MENU_SHELL (popup_menu), menuitem);
2112 if (text->im_context && GTK_IS_IM_MULTICONTEXT (text->im_context)) {
2113 menuitem = gtk_menu_item_new_with_label (_("Input Methods"));
2114 gtk_widget_show (menuitem);
2115 submenu = gtk_menu_new ();
2116 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menuitem), submenu);
2118 gtk_menu_shell_append (GTK_MENU_SHELL (popup_menu), menuitem);
2120 gtk_im_multicontext_append_menuitems (
2121 GTK_IM_MULTICONTEXT (text->im_context),
2122 GTK_MENU_SHELL (submenu));
2125 g_signal_emit (
2126 text,
2127 e_text_signals[E_TEXT_POPULATE_POPUP],
2129 event,
2130 position,
2131 popup_menu);
2133 /* If invoked by S-F10 key binding, button will be 0. */
2134 if (event_button == 0 && text->item.canvas) {
2135 rect.x = text->item.x1;
2136 rect.y = text->item.y1;
2137 rect.width = text->width;
2138 rect.height = text->height;
2140 gtk_menu_popup_at_rect (GTK_MENU (popup_menu),
2141 gtk_widget_get_window (GTK_WIDGET (text->item.canvas)),
2142 &rect,
2143 GDK_GRAVITY_CENTER,
2144 GDK_GRAVITY_NORTH_WEST,
2145 event);
2146 } else
2147 gtk_menu_popup_at_pointer (GTK_MENU (popup_menu), event);
2149 g_object_unref (text);
2150 gdk_event_free (event);
2153 static void
2154 e_text_do_popup (EText *text,
2155 GdkEvent *button_event,
2156 gint position)
2158 PopupClosure *closure = g_new (PopupClosure, 1);
2160 closure->text = g_object_ref (text);
2161 closure->event = gdk_event_copy (button_event);
2162 closure->position = position;
2164 gtk_clipboard_request_contents (
2165 gtk_widget_get_clipboard (
2166 GTK_WIDGET (GNOME_CANVAS_ITEM (text)->canvas),
2167 GDK_SELECTION_CLIPBOARD),
2168 gdk_atom_intern ("TARGETS", FALSE),
2169 popup_targets_received,
2170 closure);
2173 static void
2174 e_text_reset_im_context (EText *text)
2176 if (text->need_im_reset && text->im_context) {
2177 text->need_im_reset = FALSE;
2178 gtk_im_context_reset (text->im_context);
2182 /* fixme: */
2184 static gint
2185 next_word (EText *text,
2186 gint start)
2188 gchar *p = g_utf8_offset_to_pointer (text->text, start);
2189 gint length;
2191 length = g_utf8_strlen (text->text, -1);
2193 if (start >= length) {
2194 return length;
2195 } else {
2196 p = g_utf8_next_char (p);
2197 start++;
2199 while (p && *p) {
2200 gunichar unival = g_utf8_get_char (p);
2201 if (g_unichar_isspace (unival)) {
2202 return start + 1;
2204 else {
2205 p = g_utf8_next_char (p);
2206 start++;
2211 return g_utf8_pointer_to_offset (text->text, p);
2214 static gint
2215 find_offset_into_line (EText *text,
2216 gint offset_into_text,
2217 gchar **start_of_line)
2219 gchar *p;
2221 p = g_utf8_offset_to_pointer (text->text, offset_into_text);
2223 if (p == text->text) {
2224 if (start_of_line)
2225 *start_of_line = (gchar *)text->text;
2226 return 0;
2228 else {
2229 p = g_utf8_find_prev_char (text->text, p);
2231 while (p && p > text->text) {
2232 if (*p == '\n') {
2233 if (start_of_line)
2234 *start_of_line = p+1;
2235 return offset_into_text -
2236 g_utf8_pointer_to_offset (
2237 text->text, p + 1);
2239 p = g_utf8_find_prev_char (text->text, p);
2242 if (start_of_line)
2243 *start_of_line = (gchar *)text->text;
2244 return offset_into_text;
2248 /* direction = TRUE (move forward), FALSE (move backward)
2249 * Any error shall return length (text->text) or 0 or
2250 * text->selection_end (as deemed fit) */
2251 static gint
2252 _get_updated_position (EText *text,
2253 gboolean direction)
2255 PangoLogAttr *log_attrs = NULL;
2256 gint n_attrs;
2257 gchar *p = NULL;
2258 gint new_pos = 0;
2259 gint length = 0;
2261 /* Basic sanity test, return whatever position we are currently at. */
2262 g_return_val_if_fail (text->layout != NULL, text->selection_end);
2264 length = g_utf8_strlen (text->text, -1);
2266 /* length checks to make sure we are not wandering
2267 * off into nonexistant memory... */
2268 if ((text->selection_end >= length) && (TRUE == direction)) /* forward */
2269 return length;
2270 /* checking for -ve value wont hurt! */
2271 if ((text->selection_end <= 0) && (FALSE == direction)) /* backward */
2272 return 0;
2274 /* check for validness of full text->text */
2275 if (!g_utf8_validate (text->text, -1, NULL))
2276 return text->selection_end;
2278 /* get layout's PangoLogAttr to facilitate moving when
2279 * moving across grapheme cluster as in indic langs */
2280 pango_layout_get_log_attrs (text->layout, &log_attrs, &n_attrs);
2282 /* Fetch the current gchar index in the line & keep moving
2283 * forward until we can display cursor */
2284 p = g_utf8_offset_to_pointer (text->text, text->selection_end);
2286 new_pos = text->selection_end;
2287 while (1)
2289 /* check before moving forward/backwards
2290 * if we have more chars to move or not */
2291 if (TRUE == direction)
2292 p = g_utf8_next_char (p);
2293 else
2294 p = g_utf8_prev_char (p);
2296 /* validate the new string & return with original position if check fails */
2297 if (!g_utf8_validate (p, -1, NULL))
2298 break; /* will return old value of new_pos */
2300 new_pos = g_utf8_pointer_to_offset (text->text, p);
2302 /* if is_cursor_position is set, cursor can appear in front of character.
2303 * i.e. this is a grapheme boundary AND make some sanity checks */
2304 if ((new_pos >=0) && (new_pos < n_attrs) &&
2305 (log_attrs[new_pos].is_cursor_position))
2306 break;
2307 else if ((new_pos < 0) || (new_pos >= n_attrs))
2309 new_pos = text->selection_end;
2310 break;
2314 if (log_attrs)
2315 g_free (log_attrs);
2317 return new_pos;
2320 static gint
2321 _get_position (EText *text,
2322 ETextEventProcessorCommand *command)
2324 gint length, obj_num;
2325 gunichar unival;
2326 gchar *p = NULL;
2327 gint new_pos = 0;
2329 switch (command->position) {
2331 case E_TEP_VALUE:
2332 new_pos = command->value;
2333 break;
2335 case E_TEP_SELECTION:
2336 new_pos = text->selection_end;
2337 break;
2339 case E_TEP_START_OF_BUFFER:
2340 new_pos = 0;
2341 break;
2343 case E_TEP_END_OF_BUFFER:
2344 new_pos = strlen (text->text);
2345 break;
2347 case E_TEP_START_OF_LINE:
2349 if (text->selection_end >= 1) {
2351 p = g_utf8_offset_to_pointer (text->text, text->selection_end);
2352 if (p != text->text) {
2353 p = g_utf8_find_prev_char (text->text, p);
2354 while (p && p > text->text) {
2355 if (*p == '\n') {
2356 new_pos = g_utf8_pointer_to_offset (text->text, p) + 1;
2357 break;
2359 p = g_utf8_find_prev_char (text->text, p);
2364 break;
2366 case E_TEP_END_OF_LINE:
2367 new_pos = -1;
2368 length = g_utf8_strlen (text->text, -1);
2370 if (text->selection_end >= length) {
2371 new_pos = length;
2372 } else {
2374 p = g_utf8_offset_to_pointer (text->text, text->selection_end);
2376 while (p && *p) {
2377 if (*p == '\n') {
2378 new_pos = g_utf8_pointer_to_offset (text->text, p);
2379 p = NULL;
2380 } else
2381 p = g_utf8_next_char (p);
2385 if (new_pos == -1)
2386 new_pos = g_utf8_pointer_to_offset (text->text, p);
2388 break;
2390 case E_TEP_FORWARD_CHARACTER:
2391 length = g_utf8_strlen (text->text, -1);
2393 if (text->selection_end >= length)
2394 new_pos = length;
2395 else
2396 /* get updated position to display cursor */
2397 new_pos = _get_updated_position (text, TRUE);
2399 break;
2401 case E_TEP_BACKWARD_CHARACTER:
2402 new_pos = 0;
2403 if (text->selection_end >= 1)
2404 /* get updated position to display cursor */
2405 new_pos = _get_updated_position (text, FALSE);
2407 break;
2409 case E_TEP_FORWARD_WORD:
2410 new_pos = next_word (text, text->selection_end);
2411 break;
2413 case E_TEP_BACKWARD_WORD:
2414 new_pos = 0;
2415 if (text->selection_end >= 1) {
2416 gint pos = text->selection_end;
2418 p = g_utf8_find_prev_char (
2419 text->text, g_utf8_offset_to_pointer (
2420 text->text, text->selection_end));
2421 pos--;
2423 if (p != text->text) {
2424 p = g_utf8_find_prev_char (text->text, p);
2425 pos--;
2427 while (p && p > text->text) {
2428 unival = g_utf8_get_char (p);
2429 if (g_unichar_isspace (unival)) {
2430 new_pos = pos + 1;
2431 p = NULL;
2433 else {
2434 p = g_utf8_find_prev_char (text->text, p);
2435 pos--;
2441 break;
2443 case E_TEP_FORWARD_LINE: {
2444 gint offset_into_line;
2446 offset_into_line = find_offset_into_line (text, text->selection_end, NULL);
2447 if (offset_into_line == -1)
2448 return text->selection_end;
2450 /* now we search forward til we hit a \n, and then
2451 * offset_into_line more characters */
2452 p = g_utf8_offset_to_pointer (text->text, text->selection_end);
2453 while (p && *p) {
2454 if (*p == '\n')
2455 break;
2456 p = g_utf8_next_char (p);
2458 if (p && *p == '\n') {
2459 /* now we loop forward offset_into_line
2460 * characters, or until we hit \n or \0 */
2462 p = g_utf8_next_char (p);
2463 while (offset_into_line > 0 && p && *p != '\n' && *p != '\0') {
2464 p = g_utf8_next_char (p);
2465 offset_into_line--;
2469 /* at this point, p points to the new location,
2470 * convert it to an offset and we're done */
2471 new_pos = g_utf8_pointer_to_offset (text->text, p);
2472 break;
2474 case E_TEP_BACKWARD_LINE: {
2475 gint offset_into_line;
2477 offset_into_line = find_offset_into_line (
2478 text, text->selection_end, &p);
2480 if (offset_into_line == -1)
2481 return text->selection_end;
2483 /* p points to the first character on our line. if we
2484 * have a \n before it, skip it and scan til we hit
2485 * the next one */
2486 if (p != text->text) {
2487 p = g_utf8_find_prev_char (text->text, p);
2488 if (*p == '\n') {
2489 p = g_utf8_find_prev_char (text->text, p);
2490 while (p > text->text) {
2491 if (*p == '\n') {
2492 p++;
2493 break;
2495 p = g_utf8_find_prev_char (text->text, p);
2500 /* at this point 'p' points to the start of the
2501 * previous line, move forward 'offset_into_line'
2502 * times. */
2504 while (offset_into_line > 0 && p && *p != '\n' && *p != '\0') {
2505 p = g_utf8_next_char (p);
2506 offset_into_line--;
2509 /* at this point, p points to the new location,
2510 * convert it to an offset and we're done */
2511 new_pos = g_utf8_pointer_to_offset (text->text, p);
2512 break;
2514 case E_TEP_SELECT_WORD:
2515 /* This is a silly hack to cause double-clicking on an object
2516 * to activate that object.
2517 * (Normally, double click == select word, which is why this is here.) */
2519 obj_num = e_text_model_get_object_at_offset (
2520 text->model, text->selection_start);
2521 if (obj_num != -1) {
2522 e_text_model_activate_nth_object (text->model, obj_num);
2523 new_pos = text->selection_start;
2524 break;
2527 if (text->selection_end < 1) {
2528 new_pos = 0;
2529 break;
2532 p = g_utf8_offset_to_pointer (text->text, text->selection_end);
2534 p = g_utf8_find_prev_char (text->text, p);
2536 while (p && p > text->text) {
2537 unival = g_utf8_get_char (p);
2538 if (g_unichar_isspace (unival)) {
2539 p = g_utf8_next_char (p);
2540 break;
2542 p = g_utf8_find_prev_char (text->text, p);
2545 if (!p)
2546 text->selection_start = 0;
2547 else
2548 text->selection_start = g_utf8_pointer_to_offset (text->text, p);
2550 text->selection_start =
2551 e_text_model_validate_position (
2552 text->model, text->selection_start);
2554 length = g_utf8_strlen (text->text, -1);
2555 if (text->selection_end >= length) {
2556 new_pos = length;
2557 break;
2560 p = g_utf8_offset_to_pointer (text->text, text->selection_end);
2561 while (p && *p) {
2562 unival = g_utf8_get_char (p);
2563 if (g_unichar_isspace (unival)) {
2564 new_pos = g_utf8_pointer_to_offset (text->text, p);
2565 break;
2566 } else
2567 p = g_utf8_next_char (p);
2570 if (!new_pos)
2571 new_pos = g_utf8_strlen (text->text, -1);
2573 return new_pos;
2575 case E_TEP_SELECT_ALL:
2576 text->selection_start = 0;
2577 new_pos = g_utf8_strlen (text->text, -1);
2578 break;
2580 case E_TEP_FORWARD_PARAGRAPH:
2581 case E_TEP_BACKWARD_PARAGRAPH:
2583 case E_TEP_FORWARD_PAGE:
2584 case E_TEP_BACKWARD_PAGE:
2585 new_pos = text->selection_end;
2586 break;
2588 default:
2589 new_pos = text->selection_end;
2590 break;
2593 new_pos = e_text_model_validate_position (text->model, new_pos);
2595 return new_pos;
2598 static void
2599 e_text_insert (EText *text,
2600 const gchar *string)
2602 gint len = strlen (string);
2604 if (len > 0) {
2605 gint utf8len = 0;
2607 if (!text->allow_newlines) {
2608 const gchar *i;
2609 gchar *new_string = g_malloc (len + 1);
2610 gchar *j = new_string;
2612 for (i = string; *i; i = g_utf8_next_char (i)) {
2613 if (*i != '\n') {
2614 gunichar c;
2615 gint charlen;
2617 c = g_utf8_get_char (i);
2618 charlen = g_unichar_to_utf8 (c, j);
2619 j += charlen;
2620 utf8len++;
2623 *j = 0;
2624 e_text_model_insert_length (
2625 text->model, text->selection_start,
2626 new_string, utf8len);
2627 g_free (new_string);
2629 else {
2630 utf8len = g_utf8_strlen (string, -1);
2631 e_text_model_insert_length (
2632 text->model, text->selection_start,
2633 string, utf8len);
2638 static void
2639 capitalize (EText *text,
2640 gint start,
2641 gint end,
2642 ETextEventProcessorCaps type)
2644 gboolean first = TRUE;
2645 const gchar *p = g_utf8_offset_to_pointer (text->text, start);
2646 const gchar *text_end = g_utf8_offset_to_pointer (text->text, end);
2647 gint utf8len = text_end - p;
2649 if (utf8len > 0) {
2650 gchar *new_text = g_new0 (char, utf8len * 6);
2651 gchar *output = new_text;
2653 while (p && *p && p < text_end) {
2654 gunichar unival = g_utf8_get_char (p);
2655 gunichar newval = unival;
2657 switch (type) {
2658 case E_TEP_CAPS_UPPER:
2659 newval = g_unichar_toupper (unival);
2660 break;
2661 case E_TEP_CAPS_LOWER:
2662 newval = g_unichar_tolower (unival);
2663 break;
2664 case E_TEP_CAPS_TITLE:
2665 if (g_unichar_isalpha (unival)) {
2666 if (first)
2667 newval = g_unichar_totitle (unival);
2668 else
2669 newval = g_unichar_tolower (unival);
2670 first = FALSE;
2671 } else {
2672 first = TRUE;
2674 break;
2676 g_unichar_to_utf8 (newval, output);
2677 output = g_utf8_next_char (output);
2679 p = g_utf8_next_char (p);
2681 *output = 0;
2683 e_text_model_delete (text->model, start, utf8len);
2684 e_text_model_insert_length (text->model, start, new_text, utf8len);
2685 g_free (new_text);
2689 static void
2690 e_text_command (ETextEventProcessor *tep,
2691 ETextEventProcessorCommand *command,
2692 gpointer data)
2694 EText *text = E_TEXT (data);
2695 gboolean scroll = TRUE;
2696 gboolean use_start = TRUE;
2698 switch (command->action) {
2699 case E_TEP_MOVE:
2700 text->selection_start = _get_position (text, command);
2701 text->selection_end = text->selection_start;
2702 if (text->timer) {
2703 g_timer_reset (text->timer);
2706 text->need_im_reset = TRUE;
2707 use_start = TRUE;
2708 break;
2709 case E_TEP_SELECT:
2710 text->selection_start =
2711 e_text_model_validate_position (
2712 text->model, text->selection_start); /* paranoia */
2713 text->selection_end = _get_position (text, command);
2715 e_text_update_primary_selection (text);
2717 text->need_im_reset = TRUE;
2718 use_start = FALSE;
2720 break;
2721 case E_TEP_DELETE:
2722 if (text->selection_end == text->selection_start) {
2723 text->selection_end = _get_position (text, command);
2725 e_text_delete_selection (text);
2726 if (text->timer) {
2727 g_timer_reset (text->timer);
2730 text->need_im_reset = TRUE;
2731 use_start = FALSE;
2733 break;
2735 case E_TEP_INSERT:
2736 if (g_utf8_validate (command->string, command->value, NULL)) {
2737 if (text->selection_end != text->selection_start) {
2738 e_text_delete_selection (text);
2740 e_text_insert (text, command->string);
2741 if (text->timer) {
2742 g_timer_reset (text->timer);
2744 text->need_im_reset = TRUE;
2746 break;
2747 case E_TEP_COPY:
2748 e_text_copy_clipboard (text);
2750 if (text->timer) {
2751 g_timer_reset (text->timer);
2753 scroll = FALSE;
2754 break;
2755 case E_TEP_PASTE:
2756 e_text_paste (text, GDK_NONE);
2757 if (text->timer) {
2758 g_timer_reset (text->timer);
2760 text->need_im_reset = TRUE;
2761 break;
2762 case E_TEP_GET_SELECTION:
2763 e_text_paste (text, GDK_SELECTION_PRIMARY);
2764 break;
2765 case E_TEP_ACTIVATE:
2766 g_signal_emit (text, e_text_signals[E_TEXT_ACTIVATE], 0);
2767 if (text->timer) {
2768 g_timer_reset (text->timer);
2770 break;
2771 case E_TEP_SET_SELECT_BY_WORD:
2772 text->select_by_word = command->value;
2773 break;
2774 case E_TEP_GRAB:
2775 e_canvas_item_grab (
2776 E_CANVAS (GNOME_CANVAS_ITEM (text)->canvas),
2777 GNOME_CANVAS_ITEM (text),
2778 GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK,
2779 text->i_cursor,
2780 command->device,
2781 command->time,
2782 NULL,
2783 NULL);
2784 scroll = FALSE;
2785 break;
2786 case E_TEP_UNGRAB:
2787 e_canvas_item_ungrab (
2788 E_CANVAS (GNOME_CANVAS_ITEM (text)->canvas),
2789 GNOME_CANVAS_ITEM (text),
2790 command->time);
2791 scroll = FALSE;
2792 break;
2793 case E_TEP_CAPS:
2794 if (text->selection_start == text->selection_end) {
2795 capitalize (
2796 text, text->selection_start,
2797 next_word (text, text->selection_start),
2798 command->value);
2799 } else {
2800 gint selection_start = MIN (
2801 text->selection_start, text->selection_end);
2802 gint selection_end = MAX (
2803 text->selection_start, text->selection_end);
2804 capitalize (
2805 text, selection_start,
2806 selection_end, command->value);
2808 break;
2809 case E_TEP_NOP:
2810 scroll = FALSE;
2811 break;
2814 e_text_reset_im_context (text);
2816 /* it's possible to get here without ever having been realized
2817 * by our canvas (if the e-text started completely obscured.)
2818 * so let's create our layout object if we don't already have
2819 * one. */
2820 if (!text->layout)
2821 create_layout (text);
2823 /* We move cursor only if scroll is TRUE */
2824 if (scroll && !text->button_down) {
2825 /* XXX do we really need the @trailing logic here? if
2826 * we don't we can scrap the loop and just use
2827 * pango_layout_index_to_pos */
2828 PangoLayoutLine *cur_line = NULL;
2829 gint selection_index;
2830 PangoLayoutIter *iter = pango_layout_get_iter (text->layout);
2832 /* check if we are using selection_start or selection_end for moving? */
2833 selection_index = use_start ? text->selection_start : text->selection_end;
2835 /* convert to a byte index */
2836 selection_index = g_utf8_offset_to_pointer (
2837 text->text, selection_index) - text->text;
2839 do {
2840 PangoLayoutLine *line = pango_layout_iter_get_line (iter);
2842 if (selection_index >= line->start_index &&
2843 selection_index <= line->start_index + line->length) {
2844 /* found the line with the start of the selection */
2845 cur_line = line;
2846 break;
2849 } while (pango_layout_iter_next_line (iter));
2851 if (cur_line) {
2852 gint xpos, ypos;
2853 gdouble clip_width, clip_height;
2854 /* gboolean trailing = FALSE; */
2855 PangoRectangle pango_pos;
2857 if (selection_index > 0 && selection_index ==
2858 cur_line->start_index + cur_line->length) {
2859 selection_index--;
2860 /* trailing = TRUE; */
2863 pango_layout_index_to_pos (text->layout, selection_index, &pango_pos);
2865 pango_pos.x = PANGO_PIXELS (pango_pos.x);
2866 pango_pos.y = PANGO_PIXELS (pango_pos.y);
2867 pango_pos.width = (pango_pos.width + PANGO_SCALE / 2) / PANGO_SCALE;
2868 pango_pos.height = (pango_pos.height + PANGO_SCALE / 2) / PANGO_SCALE;
2870 /* scroll for X */
2871 xpos = pango_pos.x; /* + (trailing ? 0 : pango_pos.width);*/
2873 if (xpos + 2 < text->xofs_edit) {
2874 text->xofs_edit = xpos;
2877 clip_width = text->clip_width;
2879 if (xpos + pango_pos.width - clip_width > text->xofs_edit) {
2880 text->xofs_edit = xpos + pango_pos.width - clip_width;
2883 /* scroll for Y */
2884 if (pango_pos.y + 2 < text->yofs_edit) {
2885 ypos = pango_pos.y;
2886 text->yofs_edit = ypos;
2888 else {
2889 ypos = pango_pos.y + pango_pos.height;
2892 if (text->clip_height < 0)
2893 clip_height = text->height;
2894 else
2895 clip_height = text->clip_height;
2897 if (ypos - clip_height > text->yofs_edit) {
2898 text->yofs_edit = ypos - clip_height;
2903 pango_layout_iter_free (iter);
2906 text->needs_redraw = 1;
2907 gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (text));
2910 /* Class initialization function for the text item */
2911 static void
2912 e_text_class_init (ETextClass *class)
2914 GObjectClass *gobject_class;
2915 GnomeCanvasItemClass *item_class;
2917 gobject_class = (GObjectClass *) class;
2918 item_class = (GnomeCanvasItemClass *) class;
2920 gobject_class->dispose = e_text_dispose;
2921 gobject_class->set_property = e_text_set_property;
2922 gobject_class->get_property = e_text_get_property;
2924 item_class->update = e_text_update;
2925 item_class->realize = e_text_realize;
2926 item_class->unrealize = e_text_unrealize;
2927 item_class->draw = e_text_draw;
2928 item_class->point = e_text_point;
2929 item_class->bounds = e_text_bounds;
2930 item_class->event = e_text_event;
2932 class->changed = NULL;
2933 class->activate = NULL;
2935 e_text_signals[E_TEXT_CHANGED] = g_signal_new (
2936 "changed",
2937 G_OBJECT_CLASS_TYPE (gobject_class),
2938 G_SIGNAL_RUN_LAST,
2939 G_STRUCT_OFFSET (ETextClass, changed),
2940 NULL, NULL,
2941 g_cclosure_marshal_VOID__VOID,
2942 G_TYPE_NONE, 0);
2944 e_text_signals[E_TEXT_ACTIVATE] = g_signal_new (
2945 "activate",
2946 G_OBJECT_CLASS_TYPE (gobject_class),
2947 G_SIGNAL_RUN_LAST,
2948 G_STRUCT_OFFSET (ETextClass, activate),
2949 NULL, NULL,
2950 g_cclosure_marshal_VOID__VOID,
2951 G_TYPE_NONE, 0);
2953 e_text_signals[E_TEXT_KEYPRESS] = g_signal_new (
2954 "keypress",
2955 G_OBJECT_CLASS_TYPE (gobject_class),
2956 G_SIGNAL_RUN_LAST,
2957 G_STRUCT_OFFSET (ETextClass, keypress),
2958 NULL, NULL,
2959 e_marshal_VOID__INT_INT,
2960 G_TYPE_NONE, 2,
2961 G_TYPE_UINT,
2962 G_TYPE_UINT);
2964 e_text_signals[E_TEXT_POPULATE_POPUP] = g_signal_new (
2965 "populate_popup",
2966 G_OBJECT_CLASS_TYPE (gobject_class),
2967 G_SIGNAL_RUN_LAST,
2968 G_STRUCT_OFFSET (ETextClass, populate_popup),
2969 NULL, NULL,
2970 e_marshal_VOID__POINTER_INT_OBJECT,
2971 G_TYPE_NONE, 3,
2972 G_TYPE_POINTER,
2973 G_TYPE_INT,
2974 GTK_TYPE_MENU);
2976 g_object_class_install_property (
2977 gobject_class,
2978 PROP_MODEL,
2979 g_param_spec_object (
2980 "model",
2981 "Model",
2982 "Model",
2983 E_TYPE_TEXT_MODEL,
2984 G_PARAM_READWRITE));
2986 g_object_class_install_property (
2987 gobject_class,
2988 PROP_EVENT_PROCESSOR,
2989 g_param_spec_object (
2990 "event_processor",
2991 "Event Processor",
2992 "Event Processor",
2993 E_TYPE_TEXT_EVENT_PROCESSOR,
2994 G_PARAM_READWRITE));
2996 g_object_class_install_property (
2997 gobject_class,
2998 PROP_TEXT,
2999 g_param_spec_string (
3000 "text",
3001 "Text",
3002 "Text",
3003 NULL,
3004 G_PARAM_READWRITE));
3006 g_object_class_install_property (
3007 gobject_class,
3008 PROP_BOLD,
3009 g_param_spec_boolean (
3010 "bold",
3011 "Bold",
3012 "Bold",
3013 FALSE,
3014 G_PARAM_READWRITE));
3016 g_object_class_install_property (
3017 gobject_class,
3018 PROP_STRIKEOUT,
3019 g_param_spec_boolean (
3020 "strikeout",
3021 "Strikeout",
3022 "Strikeout",
3023 FALSE,
3024 G_PARAM_READWRITE));
3026 g_object_class_install_property (
3027 gobject_class,
3028 PROP_ITALIC,
3029 g_param_spec_boolean (
3030 "italic",
3031 "Italic",
3032 "Italic",
3033 FALSE,
3034 G_PARAM_READWRITE));
3036 g_object_class_install_property (
3037 gobject_class,
3038 PROP_JUSTIFICATION,
3039 g_param_spec_enum (
3040 "justification",
3041 "Justification",
3042 "Justification",
3043 GTK_TYPE_JUSTIFICATION,
3044 GTK_JUSTIFY_LEFT,
3045 G_PARAM_READWRITE));
3047 g_object_class_install_property (
3048 gobject_class,
3049 PROP_CLIP_WIDTH,
3050 g_param_spec_double (
3051 "clip_width",
3052 "Clip Width",
3053 "Clip Width",
3054 0.0, G_MAXDOUBLE, 0.0,
3055 G_PARAM_READWRITE));
3057 g_object_class_install_property (
3058 gobject_class,
3059 PROP_CLIP_HEIGHT,
3060 g_param_spec_double (
3061 "clip_height",
3062 "Clip Height",
3063 "Clip Height",
3064 0.0, G_MAXDOUBLE, 0.0,
3065 G_PARAM_READWRITE));
3067 g_object_class_install_property (
3068 gobject_class,
3069 PROP_CLIP,
3070 g_param_spec_boolean (
3071 "clip",
3072 "Clip",
3073 "Clip",
3074 FALSE,
3075 G_PARAM_READWRITE));
3077 g_object_class_install_property (
3078 gobject_class,
3079 PROP_FILL_CLIP_RECTANGLE,
3080 g_param_spec_boolean (
3081 "fill_clip_rectangle",
3082 "Fill clip rectangle",
3083 "Fill clip rectangle",
3084 FALSE,
3085 G_PARAM_READWRITE));
3087 g_object_class_install_property (
3088 gobject_class,
3089 PROP_X_OFFSET,
3090 g_param_spec_double (
3091 "x_offset",
3092 "X Offset",
3093 "X Offset",
3094 0.0, G_MAXDOUBLE, 0.0,
3095 G_PARAM_READWRITE));
3097 g_object_class_install_property (
3098 gobject_class,
3099 PROP_Y_OFFSET,
3100 g_param_spec_double (
3101 "y_offset",
3102 "Y Offset",
3103 "Y Offset",
3104 0.0, G_MAXDOUBLE, 0.0,
3105 G_PARAM_READWRITE));
3107 g_object_class_install_property (
3108 gobject_class,
3109 PROP_FILL_COLOR,
3110 g_param_spec_string (
3111 "fill_color",
3112 "Fill color",
3113 "Fill color",
3114 NULL,
3115 G_PARAM_WRITABLE));
3117 g_object_class_install_property (
3118 gobject_class,
3119 PROP_FILL_COLOR_GDK,
3120 g_param_spec_boxed (
3121 "fill_color_gdk",
3122 "GDK fill color",
3123 "GDK fill color",
3124 GDK_TYPE_COLOR,
3125 G_PARAM_WRITABLE));
3127 g_object_class_install_property (
3128 gobject_class,
3129 PROP_FILL_COLOR_RGBA,
3130 g_param_spec_uint (
3131 "fill_color_rgba",
3132 "GDK fill color",
3133 "GDK fill color",
3134 0, G_MAXUINT, 0,
3135 G_PARAM_READWRITE));
3137 g_object_class_install_property (
3138 gobject_class,
3139 PROP_TEXT_WIDTH,
3140 g_param_spec_double (
3141 "text_width",
3142 "Text width",
3143 "Text width",
3144 0.0, G_MAXDOUBLE, 0.0,
3145 G_PARAM_READABLE));
3147 g_object_class_install_property (
3148 gobject_class,
3149 PROP_TEXT_HEIGHT,
3150 g_param_spec_double (
3151 "text_height",
3152 "Text height",
3153 "Text height",
3154 0.0, G_MAXDOUBLE, 0.0,
3155 G_PARAM_READABLE));
3157 g_object_class_install_property (
3158 gobject_class,
3159 PROP_EDITABLE,
3160 g_param_spec_boolean (
3161 "editable",
3162 "Editable",
3163 "Editable",
3164 FALSE,
3165 G_PARAM_READWRITE));
3167 g_object_class_install_property (
3168 gobject_class,
3169 PROP_USE_ELLIPSIS,
3170 g_param_spec_boolean (
3171 "use_ellipsis",
3172 "Use ellipsis",
3173 "Use ellipsis",
3174 FALSE,
3175 G_PARAM_READWRITE));
3177 g_object_class_install_property (
3178 gobject_class,
3179 PROP_ELLIPSIS,
3180 g_param_spec_string (
3181 "ellipsis",
3182 "Ellipsis",
3183 "Ellipsis",
3184 NULL,
3185 G_PARAM_READWRITE));
3187 g_object_class_install_property (
3188 gobject_class,
3189 PROP_LINE_WRAP,
3190 g_param_spec_boolean (
3191 "line_wrap",
3192 "Line wrap",
3193 "Line wrap",
3194 FALSE,
3195 G_PARAM_READWRITE));
3197 g_object_class_install_property (
3198 gobject_class,
3199 PROP_BREAK_CHARACTERS,
3200 g_param_spec_string (
3201 "break_characters",
3202 "Break characters",
3203 "Break characters",
3204 NULL,
3205 G_PARAM_READWRITE));
3207 g_object_class_install_property (
3208 gobject_class, PROP_MAX_LINES,
3209 g_param_spec_int (
3210 "max_lines",
3211 "Max lines",
3212 "Max lines",
3213 0, G_MAXINT, 0,
3214 G_PARAM_READWRITE));
3216 g_object_class_install_property (
3217 gobject_class,
3218 PROP_WIDTH,
3219 g_param_spec_double (
3220 "width",
3221 "Width",
3222 "Width",
3223 0.0, G_MAXDOUBLE, 0.0,
3224 G_PARAM_READWRITE));
3226 g_object_class_install_property (
3227 gobject_class,
3228 PROP_HEIGHT,
3229 g_param_spec_double (
3230 "height",
3231 "Height",
3232 "Height",
3233 0.0, G_MAXDOUBLE, 0.0,
3234 G_PARAM_READWRITE));
3236 g_object_class_install_property (
3237 gobject_class,
3238 PROP_ALLOW_NEWLINES,
3239 g_param_spec_boolean (
3240 "allow_newlines",
3241 "Allow newlines",
3242 "Allow newlines",
3243 FALSE,
3244 G_PARAM_READWRITE));
3246 g_object_class_install_property (
3247 gobject_class,
3248 PROP_CURSOR_POS,
3249 g_param_spec_int (
3250 "cursor_pos",
3251 "Cursor position",
3252 "Cursor position",
3253 0, G_MAXINT, 0,
3254 G_PARAM_READWRITE));
3256 g_object_class_install_property (
3257 gobject_class,
3258 PROP_IM_CONTEXT,
3259 g_param_spec_object (
3260 "im_context",
3261 "IM Context",
3262 "IM Context",
3263 GTK_TYPE_IM_CONTEXT,
3264 G_PARAM_READWRITE));
3266 g_object_class_install_property (
3267 gobject_class,
3268 PROP_HANDLE_POPUP,
3269 g_param_spec_boolean (
3270 "handle_popup",
3271 "Handle Popup",
3272 "Handle Popup",
3273 FALSE,
3274 G_PARAM_READWRITE));
3276 if (!clipboard_atom)
3277 clipboard_atom = gdk_atom_intern ("CLIPBOARD", FALSE);
3279 gal_a11y_e_text_init ();
3282 /* Object initialization function for the text item */
3283 static void
3284 e_text_init (EText *text)
3286 text->model = e_text_model_new ();
3287 text->text = e_text_model_get_text (text->model);
3288 text->preedit_len = 0;
3289 text->preedit_pos = 0;
3290 text->layout = NULL;
3292 text->revert = NULL;
3294 text->model_changed_signal_id = g_signal_connect (
3295 text->model, "changed",
3296 G_CALLBACK (e_text_text_model_changed), text);
3298 text->model_repos_signal_id = g_signal_connect (
3299 text->model, "reposition",
3300 G_CALLBACK (e_text_text_model_reposition), text);
3302 text->justification = GTK_JUSTIFY_LEFT;
3303 text->clip_width = -1.0;
3304 text->clip_height = -1.0;
3305 text->xofs = 0.0;
3306 text->yofs = 0.0;
3308 text->ellipsis = NULL;
3309 text->use_ellipsis = FALSE;
3310 text->ellipsis_width = 0;
3312 text->editable = FALSE;
3313 text->editing = FALSE;
3314 text->xofs_edit = 0;
3315 text->yofs_edit = 0;
3317 text->selection_start = 0;
3318 text->selection_end = 0;
3319 text->select_by_word = FALSE;
3321 text->timeout_id = 0;
3322 text->timer = NULL;
3324 text->lastx = 0;
3325 text->lasty = 0;
3326 text->last_state = 0;
3328 text->scroll_start = 0;
3329 text->show_cursor = TRUE;
3330 text->button_down = FALSE;
3332 text->tep = NULL;
3333 text->tep_command_id = 0;
3335 text->pointer_in = FALSE;
3336 text->default_cursor_shown = TRUE;
3337 text->line_wrap = FALSE;
3338 text->break_characters = NULL;
3339 text->max_lines = -1;
3340 text->dbl_timeout = 0;
3341 text->tpl_timeout = 0;
3343 text->bold = FALSE;
3344 text->strikeout = FALSE;
3345 text->italic = FALSE;
3347 text->allow_newlines = TRUE;
3349 text->last_type_request = -1;
3350 text->last_time_request = 0;
3351 text->queued_requests = NULL;
3353 text->im_context = NULL;
3354 text->need_im_reset = FALSE;
3355 text->im_context_signals_registered = FALSE;
3357 text->handle_popup = FALSE;
3358 text->rgba_set = FALSE;
3360 e_canvas_item_set_reflow_callback (GNOME_CANVAS_ITEM (text), e_text_reflow);
3363 /* IM Context Callbacks */
3364 static void
3365 e_text_commit_cb (GtkIMContext *context,
3366 const gchar *str,
3367 EText *text)
3369 if (g_utf8_validate (str, strlen (str), NULL)) {
3370 if (text->selection_end != text->selection_start)
3371 e_text_delete_selection (text);
3372 e_text_insert (text, str);
3373 g_signal_emit (text, e_text_signals[E_TEXT_KEYPRESS], 0, 0, 0);
3377 static void
3378 e_text_preedit_changed_cb (GtkIMContext *context,
3379 EText *etext)
3381 gchar *preedit_string = NULL;
3382 gint cursor_pos;
3384 gtk_im_context_get_preedit_string (
3385 context, &preedit_string,
3386 NULL, &cursor_pos);
3388 cursor_pos = CLAMP (cursor_pos, 0, g_utf8_strlen (preedit_string, -1));
3389 etext->preedit_len = strlen (preedit_string);
3390 etext->preedit_pos = g_utf8_offset_to_pointer (
3391 preedit_string, cursor_pos) - preedit_string;
3392 g_free (preedit_string);
3394 g_signal_emit (etext, e_text_signals[E_TEXT_KEYPRESS], 0, 0, 0);
3397 static gboolean
3398 e_text_retrieve_surrounding_cb (GtkIMContext *context,
3399 EText *text)
3401 gtk_im_context_set_surrounding (
3402 context, text->text, strlen (text->text),
3403 g_utf8_offset_to_pointer (text->text, MIN (
3404 text->selection_start, text->selection_end)) - text->text);
3406 return TRUE;
3409 static gboolean
3410 e_text_delete_surrounding_cb (GtkIMContext *context,
3411 gint offset,
3412 gint n_chars,
3413 EText *text)
3415 e_text_model_delete (
3416 text->model,
3417 MIN (text->selection_start, text->selection_end) + offset,
3418 n_chars);
3420 return TRUE;