1 /* CALLY - The Clutter Accessibility Implementation Library
2 *
3 * Copyright (C) 2009 Igalia, S.L.
4 *
5 * Author: Alejandro Piñeiro Iglesias <apinheiro@igalia.com>
6 *
7 * Some parts are based on GailLabel, GailEntry, GailTextView from GAIL
8 * GAIL - The GNOME Accessibility Implementation Library
9 * Copyright 2001, 2002, 2003 Sun Microsystems Inc.
10 *
11 * Implementation of atk_text_get_text_[before/at/after]_offset
12 * copied from gtkpango.c, part of GTK+ project
13 * Copyright (c) 2010 Red Hat, Inc.
14 *
15 * This library is free software; you can redistribute it and/or
16 * modify it under the terms of the GNU Lesser General Public
17 * License as published by the Free Software Foundation; either
18 * version 2 of the License, or (at your option) any later version.
19 *
20 * This library is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
23 * Lesser General Public License for more details.
24 *
25 * You should have received a copy of the GNU Lesser General Public
26 * License along with this library; if not, write to the
27 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
28 * Boston, MA 02111-1307, USA.
29 */
30
31 /**
32 * SECTION:cally-text
33 * @short_description: Implementation of the ATK interfaces for a #ClutterText
34 * @see_also: #ClutterText
35 *
36 * #CallyText implements the required ATK interfaces of
37 * #ClutterText, #AtkText and #AtkEditableText
38 *
39 *
40 */
41
42 #include "clutter-build-config.h"
43
44 #include "cally-text.h"
45 #include "cally-actor-private.h"
46
47 #include "clutter-color.h"
48 #include "clutter-main.h"
49 #include "clutter-text.h"
50
51 static void cally_text_finalize (GObject *obj);
52
53 /* AtkObject */
54 static void cally_text_real_initialize (AtkObject *obj,
55 gpointer data);
56 static AtkStateSet* cally_text_ref_state_set (AtkObject *obj);
57
58 /* atkaction */
59
60 static void _cally_text_activate_action (CallyActor *cally_actor);
61 static void _check_activate_action (CallyText *cally_text,
62 ClutterText *clutter_text);
63
64 /* AtkText */
65 static void cally_text_text_interface_init (AtkTextIface *iface);
66 static gchar* cally_text_get_text (AtkText *text,
67 gint start_offset,
68 gint end_offset);
69 static gunichar cally_text_get_character_at_offset (AtkText *text,
70 gint offset);
71 static gchar* cally_text_get_text_before_offset (AtkText *text,
72 gint offset,
73 AtkTextBoundary boundary_type,
74 gint *start_offset,
75 gint *end_offset);
76 static gchar* cally_text_get_text_at_offset (AtkText *text,
77 gint offset,
78 AtkTextBoundary boundary_type,
79 gint *start_offset,
80 gint *end_offset);
81 static gchar* cally_text_get_text_after_offset (AtkText *text,
82 gint offset,
83 AtkTextBoundary boundary_type,
84 gint *start_offset,
85 gint *end_offset);
86 static gint cally_text_get_caret_offset (AtkText *text);
87 static gboolean cally_text_set_caret_offset (AtkText *text,
88 gint offset);
89 static gint cally_text_get_character_count (AtkText *text);
90 static gint cally_text_get_n_selections (AtkText *text);
91 static gchar* cally_text_get_selection (AtkText *text,
92 gint selection_num,
93 gint *start_offset,
94 gint *end_offset);
95 static gboolean cally_text_add_selection (AtkText *text,
96 gint start_offset,
97 gint end_offset);
98 static gboolean cally_text_remove_selection (AtkText *text,
99 gint selection_num);
100 static gboolean cally_text_set_selection (AtkText *text,
101 gint selection_num,
102 gint start_offset,
103 gint end_offset);
104 static AtkAttributeSet* cally_text_get_run_attributes (AtkText *text,
105 gint offset,
106 gint *start_offset,
107 gint *end_offset);
108 static AtkAttributeSet* cally_text_get_default_attributes (AtkText *text);
109 static void cally_text_get_character_extents (AtkText *text,
110 gint offset,
111 gint *x,
112 gint *y,
113 gint *width,
114 gint *height,
115 AtkCoordType coords);
116 static gint cally_text_get_offset_at_point (AtkText *text,
117 gint x,
118 gint y,
119 AtkCoordType coords);
120
121 static void _cally_text_get_selection_bounds (ClutterText *clutter_text,
122 gint *start_offset,
123 gint *end_offset);
124 static void _cally_text_insert_text_cb (ClutterText *clutter_text,
125 gchar *new_text,
126 gint new_text_length,
127 gint *position,
128 gpointer data);
129 static void _cally_text_delete_text_cb (ClutterText *clutter_text,
130 gint start_pos,
131 gint end_pos,
132 gpointer data);
133 static gboolean _idle_notify_insert (gpointer data);
134 static void _notify_insert (CallyText *cally_text);
135 static void _notify_delete (CallyText *cally_text);
136
137 /* AtkEditableText */
138 static void cally_text_editable_text_interface_init (AtkEditableTextIface *iface);
139 static void cally_text_set_text_contents (AtkEditableText *text,
140 const gchar *string);
141 static void cally_text_insert_text (AtkEditableText *text,
142 const gchar *string,
143 gint length,
144 gint *position);
145 static void cally_text_delete_text (AtkEditableText *text,
146 gint start_pos,
147 gint end_pos);
148
149 /* CallyActor */
150 static void cally_text_notify_clutter (GObject *obj,
151 GParamSpec *pspec);
152
153 static gboolean _check_for_selection_change (CallyText *cally_text,
154 ClutterText *clutter_text);
155
156 /* Misc functions */
157 static AtkAttributeSet* _cally_misc_add_attribute (AtkAttributeSet *attrib_set,
158 AtkTextAttribute attr,
159 gchar *value);
160
161 static AtkAttributeSet* _cally_misc_layout_get_run_attributes (AtkAttributeSet *attrib_set,
162 ClutterText *clutter_text,
163 gint offset,
164 gint *start_offset,
165 gint *end_offset);
166
167 static AtkAttributeSet* _cally_misc_layout_get_default_attributes (AtkAttributeSet *attrib_set,
168 ClutterText *text);
169
170 static int _cally_misc_get_index_at_point (ClutterText *clutter_text,
171 gint x,
172 gint y,
173 AtkCoordType coords);
174
175 struct _CallyTextPrivate
176 {
177 /* Cached ClutterText values*/
178 gint cursor_position;
179 gint selection_bound;
180
181 /* text_changed::insert stuff */
182 const gchar *signal_name_insert;
183 gint position_insert;
184 gint length_insert;
185 guint insert_idle_handler;
186
187 /* text_changed::delete stuff */
188 const gchar *signal_name_delete;
189 gint position_delete;
190 gint length_delete;
191
192 /* action */
193 guint activate_action_id;
194 };
195
196 G_DEFINE_TYPE_WITH_CODE (CallyText,
197 cally_text,
198 CALLY_TYPE_ACTOR,
199 G_ADD_PRIVATE (CallyText)
200 G_IMPLEMENT_INTERFACE (ATK_TYPE_TEXT,
201 cally_text_text_interface_init)
202 G_IMPLEMENT_INTERFACE (ATK_TYPE_EDITABLE_TEXT,
203 cally_text_editable_text_interface_init));
204
205 static void
cally_text_class_init(CallyTextClass * klass)206 cally_text_class_init (CallyTextClass *klass)
207 {
208 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
209 AtkObjectClass *class = ATK_OBJECT_CLASS (klass);
210 CallyActorClass *cally_class = CALLY_ACTOR_CLASS (klass);
211
212 gobject_class->finalize = cally_text_finalize;
213
214 class->initialize = cally_text_real_initialize;
215 class->ref_state_set = cally_text_ref_state_set;
216
217 cally_class->notify_clutter = cally_text_notify_clutter;
218 }
219
220 static void
cally_text_init(CallyText * cally_text)221 cally_text_init (CallyText *cally_text)
222 {
223 CallyTextPrivate *priv = cally_text_get_instance_private (cally_text);
224
225 cally_text->priv = priv;
226
227 priv->cursor_position = 0;
228 priv->selection_bound = 0;
229
230 priv->signal_name_insert = NULL;
231 priv->position_insert = -1;
232 priv->length_insert = -1;
233 priv->insert_idle_handler = 0;
234
235 priv->signal_name_delete = NULL;
236 priv->position_delete = -1;
237 priv->length_delete = -1;
238
239 priv->activate_action_id = 0;
240 }
241
242 static void
cally_text_finalize(GObject * obj)243 cally_text_finalize (GObject *obj)
244 {
245 CallyText *cally_text = CALLY_TEXT (obj);
246
247 /* g_object_unref (cally_text->priv->textutil); */
248 /* cally_text->priv->textutil = NULL; */
249
250 g_clear_handle_id (&cally_text->priv->insert_idle_handler, g_source_remove);
251
252 G_OBJECT_CLASS (cally_text_parent_class)->finalize (obj);
253 }
254
255 /**
256 * cally_text_new:
257 * @actor: a #ClutterActor
258 *
259 * Creates a new #CallyText for the given @actor. @actor must be a
260 * #ClutterText.
261 *
262 * Return value: the newly created #AtkObject
263 *
264 * Since: 1.4
265 */
266 AtkObject*
cally_text_new(ClutterActor * actor)267 cally_text_new (ClutterActor *actor)
268 {
269 GObject *object = NULL;
270 AtkObject *accessible = NULL;
271
272 g_return_val_if_fail (CLUTTER_IS_TEXT (actor), NULL);
273
274 object = g_object_new (CALLY_TYPE_TEXT, NULL);
275
276 accessible = ATK_OBJECT (object);
277 atk_object_initialize (accessible, actor);
278
279 return accessible;
280 }
281
282 /* atkobject.h */
283
284 static void
cally_text_real_initialize(AtkObject * obj,gpointer data)285 cally_text_real_initialize(AtkObject *obj,
286 gpointer data)
287 {
288 ClutterText *clutter_text = NULL;
289 CallyText *cally_text = NULL;
290
291 ATK_OBJECT_CLASS (cally_text_parent_class)->initialize (obj, data);
292
293 g_return_if_fail (CLUTTER_TEXT (data));
294
295 cally_text = CALLY_TEXT (obj);
296 clutter_text = CLUTTER_TEXT (data);
297
298 cally_text->priv->cursor_position = clutter_text_get_cursor_position (clutter_text);
299 cally_text->priv->selection_bound = clutter_text_get_selection_bound (clutter_text);
300
301 g_signal_connect (clutter_text, "insert-text",
302 G_CALLBACK (_cally_text_insert_text_cb),
303 cally_text);
304 g_signal_connect (clutter_text, "delete-text",
305 G_CALLBACK (_cally_text_delete_text_cb),
306 cally_text);
307
308 _check_activate_action (cally_text, clutter_text);
309
310 if (clutter_text_get_password_char (clutter_text) != 0)
311 atk_object_set_role (obj, ATK_ROLE_PASSWORD_TEXT);
312 else
313 atk_object_set_role (obj, ATK_ROLE_TEXT);
314 }
315
316 static AtkStateSet*
cally_text_ref_state_set(AtkObject * obj)317 cally_text_ref_state_set (AtkObject *obj)
318 {
319 AtkStateSet *result = NULL;
320 ClutterActor *actor = NULL;
321
322 result = ATK_OBJECT_CLASS (cally_text_parent_class)->ref_state_set (obj);
323
324 actor = CALLY_GET_CLUTTER_ACTOR (obj);
325
326 if (actor == NULL)
327 return result;
328
329 if (clutter_text_get_editable (CLUTTER_TEXT (actor)))
330 atk_state_set_add_state (result, ATK_STATE_EDITABLE);
331
332 if (clutter_text_get_selectable (CLUTTER_TEXT (actor)))
333 atk_state_set_add_state (result, ATK_STATE_SELECTABLE_TEXT);
334
335 return result;
336 }
337
338 /***** pango stuff ****
339 *
340 * FIXME: all this pango related code used to implement
341 * atk_text_get_text_[before/at/after]_offset was copied from GTK, and
342 * should be on a common library (like pango itself).
343 *
344 *********************/
345
346 /*
347 * _gtk_pango_move_chars:
348 * @layout: a #PangoLayout
349 * @offset: a character offset in @layout
350 * @count: the number of characters to move from @offset
351 *
352 * Returns the position that is @count characters from the
353 * given @offset. @count may be positive or negative.
354 *
355 * For the purpose of this function, characters are defined
356 * by what Pango considers cursor positions.
357 *
358 * Returns: the new position
359 */
360 static gint
_gtk_pango_move_chars(PangoLayout * layout,gint offset,gint count)361 _gtk_pango_move_chars (PangoLayout *layout,
362 gint offset,
363 gint count)
364 {
365 const PangoLogAttr *attrs;
366 gint n_attrs;
367
368 attrs = pango_layout_get_log_attrs_readonly (layout, &n_attrs);
369
370 while (count > 0 && offset < n_attrs - 1)
371 {
372 do
373 offset++;
374 while (offset < n_attrs - 1 && !attrs[offset].is_cursor_position);
375
376 count--;
377 }
378 while (count < 0 && offset > 0)
379 {
380 do
381 offset--;
382 while (offset > 0 && !attrs[offset].is_cursor_position);
383
384 count++;
385 }
386
387 return offset;
388 }
389
390 /*
391 * _gtk_pango_move_words:
392 * @layout: a #PangoLayout
393 * @offset: a character offset in @layout
394 * @count: the number of words to move from @offset
395 *
396 * Returns the position that is @count words from the
397 * given @offset. @count may be positive or negative.
398 *
399 * If @count is positive, the returned position will
400 * be a word end, otherwise it will be a word start.
401 * See the Pango documentation for details on how
402 * word starts and ends are defined.
403 *
404 * Returns: the new position
405 */
406 static gint
_gtk_pango_move_words(PangoLayout * layout,gint offset,gint count)407 _gtk_pango_move_words (PangoLayout *layout,
408 gint offset,
409 gint count)
410 {
411 const PangoLogAttr *attrs;
412 gint n_attrs;
413
414 attrs = pango_layout_get_log_attrs_readonly (layout, &n_attrs);
415
416 while (count > 0 && offset < n_attrs - 1)
417 {
418 do
419 offset++;
420 while (offset < n_attrs - 1 && !attrs[offset].is_word_end);
421
422 count--;
423 }
424 while (count < 0 && offset > 0)
425 {
426 do
427 offset--;
428 while (offset > 0 && !attrs[offset].is_word_start);
429
430 count++;
431 }
432
433 return offset;
434 }
435
436 /*
437 * _gtk_pango_move_sentences:
438 * @layout: a #PangoLayout
439 * @offset: a character offset in @layout
440 * @count: the number of sentences to move from @offset
441 *
442 * Returns the position that is @count sentences from the
443 * given @offset. @count may be positive or negative.
444 *
445 * If @count is positive, the returned position will
446 * be a sentence end, otherwise it will be a sentence start.
447 * See the Pango documentation for details on how
448 * sentence starts and ends are defined.
449 *
450 * Returns: the new position
451 */
452 static gint
_gtk_pango_move_sentences(PangoLayout * layout,gint offset,gint count)453 _gtk_pango_move_sentences (PangoLayout *layout,
454 gint offset,
455 gint count)
456 {
457 const PangoLogAttr *attrs;
458 gint n_attrs;
459
460 attrs = pango_layout_get_log_attrs_readonly (layout, &n_attrs);
461
462 while (count > 0 && offset < n_attrs - 1)
463 {
464 do
465 offset++;
466 while (offset < n_attrs - 1 && !attrs[offset].is_sentence_end);
467
468 count--;
469 }
470 while (count < 0 && offset > 0)
471 {
472 do
473 offset--;
474 while (offset > 0 && !attrs[offset].is_sentence_start);
475
476 count++;
477 }
478
479 return offset;
480 }
481
482 /*
483 * _gtk_pango_is_inside_word:
484 * @layout: a #PangoLayout
485 * @offset: a character offset in @layout
486 *
487 * Returns whether the given position is inside
488 * a word.
489 *
490 * Returns: %TRUE if @offset is inside a word
491 */
492 static gboolean
_gtk_pango_is_inside_word(PangoLayout * layout,gint offset)493 _gtk_pango_is_inside_word (PangoLayout *layout,
494 gint offset)
495 {
496 const PangoLogAttr *attrs;
497 gint n_attrs;
498
499 attrs = pango_layout_get_log_attrs_readonly (layout, &n_attrs);
500
501 while (offset >= 0 &&
502 !(attrs[offset].is_word_start || attrs[offset].is_word_end))
503 offset--;
504
505 if (offset >= 0)
506 return attrs[offset].is_word_start;
507
508 return FALSE;
509 }
510
511 /*
512 * _gtk_pango_is_inside_sentence:
513 * @layout: a #PangoLayout
514 * @offset: a character offset in @layout
515 *
516 * Returns whether the given position is inside
517 * a sentence.
518 *
519 * Returns: %TRUE if @offset is inside a sentence
520 */
521 static gboolean
_gtk_pango_is_inside_sentence(PangoLayout * layout,gint offset)522 _gtk_pango_is_inside_sentence (PangoLayout *layout,
523 gint offset)
524 {
525 const PangoLogAttr *attrs;
526 gint n_attrs;
527
528 attrs = pango_layout_get_log_attrs_readonly (layout, &n_attrs);
529
530 while (offset >= 0 &&
531 !(attrs[offset].is_sentence_start || attrs[offset].is_sentence_end))
532 offset--;
533
534 if (offset >= 0)
535 return attrs[offset].is_sentence_start;
536
537 return FALSE;
538 }
539
540 static void
pango_layout_get_line_before(PangoLayout * layout,AtkTextBoundary boundary_type,gint offset,gint * start_offset,gint * end_offset)541 pango_layout_get_line_before (PangoLayout *layout,
542 AtkTextBoundary boundary_type,
543 gint offset,
544 gint *start_offset,
545 gint *end_offset)
546 {
547 PangoLayoutIter *iter;
548 PangoLayoutLine *line, *prev_line = NULL, *prev_prev_line = NULL;
549 gint index, start_index, end_index;
550 const gchar *text;
551 gboolean found = FALSE;
552
553 text = pango_layout_get_text (layout);
554 index = g_utf8_offset_to_pointer (text, offset) - text;
555 iter = pango_layout_get_iter (layout);
556 do
557 {
558 line = pango_layout_iter_get_line (iter);
559 start_index = line->start_index;
560 end_index = start_index + line->length;
561
562 if (index >= start_index && index <= end_index)
563 {
564 /* Found line for offset */
565 if (prev_line)
566 {
567 switch (boundary_type)
568 {
569 case ATK_TEXT_BOUNDARY_LINE_START:
570 end_index = start_index;
571 start_index = prev_line->start_index;
572 break;
573 case ATK_TEXT_BOUNDARY_LINE_END:
574 if (prev_prev_line)
575 start_index = prev_prev_line->start_index + prev_prev_line->length;
576 else
577 start_index = 0;
578 end_index = prev_line->start_index + prev_line->length;
579 break;
580 default:
581 g_assert_not_reached();
582 }
583 }
584 else
585 start_index = end_index = 0;
586
587 found = TRUE;
588 break;
589 }
590
591 prev_prev_line = prev_line;
592 prev_line = line;
593 }
594 while (pango_layout_iter_next_line (iter));
595
596 if (!found)
597 {
598 start_index = prev_line->start_index + prev_line->length;
599 end_index = start_index;
600 }
601 pango_layout_iter_free (iter);
602
603 *start_offset = g_utf8_pointer_to_offset (text, text + start_index);
604 *end_offset = g_utf8_pointer_to_offset (text, text + end_index);
605 }
606
607 static void
pango_layout_get_line_at(PangoLayout * layout,AtkTextBoundary boundary_type,gint offset,gint * start_offset,gint * end_offset)608 pango_layout_get_line_at (PangoLayout *layout,
609 AtkTextBoundary boundary_type,
610 gint offset,
611 gint *start_offset,
612 gint *end_offset)
613 {
614 PangoLayoutIter *iter;
615 PangoLayoutLine *line, *prev_line = NULL;
616 gint index, start_index, end_index;
617 const gchar *text;
618 gboolean found = FALSE;
619
620 text = pango_layout_get_text (layout);
621 index = g_utf8_offset_to_pointer (text, offset) - text;
622 iter = pango_layout_get_iter (layout);
623 do
624 {
625 line = pango_layout_iter_get_line (iter);
626 start_index = line->start_index;
627 end_index = start_index + line->length;
628
629 if (index >= start_index && index <= end_index)
630 {
631 /* Found line for offset */
632 switch (boundary_type)
633 {
634 case ATK_TEXT_BOUNDARY_LINE_START:
635 if (pango_layout_iter_next_line (iter))
636 end_index = pango_layout_iter_get_line (iter)->start_index;
637 break;
638 case ATK_TEXT_BOUNDARY_LINE_END:
639 if (prev_line)
640 start_index = prev_line->start_index + prev_line->length;
641 break;
642 default:
643 g_assert_not_reached();
644 }
645
646 found = TRUE;
647 break;
648 }
649
650 prev_line = line;
651 }
652 while (pango_layout_iter_next_line (iter));
653
654 if (!found)
655 {
656 start_index = prev_line->start_index + prev_line->length;
657 end_index = start_index;
658 }
659 pango_layout_iter_free (iter);
660
661 *start_offset = g_utf8_pointer_to_offset (text, text + start_index);
662 *end_offset = g_utf8_pointer_to_offset (text, text + end_index);
663 }
664
665 static void
pango_layout_get_line_after(PangoLayout * layout,AtkTextBoundary boundary_type,gint offset,gint * start_offset,gint * end_offset)666 pango_layout_get_line_after (PangoLayout *layout,
667 AtkTextBoundary boundary_type,
668 gint offset,
669 gint *start_offset,
670 gint *end_offset)
671 {
672 PangoLayoutIter *iter;
673 PangoLayoutLine *line, *prev_line = NULL;
674 gint index, start_index, end_index;
675 const gchar *text;
676 gboolean found = FALSE;
677
678 text = pango_layout_get_text (layout);
679 index = g_utf8_offset_to_pointer (text, offset) - text;
680 iter = pango_layout_get_iter (layout);
681 do
682 {
683 line = pango_layout_iter_get_line (iter);
684 start_index = line->start_index;
685 end_index = start_index + line->length;
686
687 if (index >= start_index && index <= end_index)
688 {
689 /* Found line for offset */
690 if (pango_layout_iter_next_line (iter))
691 {
692 line = pango_layout_iter_get_line (iter);
693 switch (boundary_type)
694 {
695 case ATK_TEXT_BOUNDARY_LINE_START:
696 start_index = line->start_index;
697 if (pango_layout_iter_next_line (iter))
698 end_index = pango_layout_iter_get_line (iter)->start_index;
699 else
700 end_index = start_index + line->length;
701 break;
702 case ATK_TEXT_BOUNDARY_LINE_END:
703 start_index = end_index;
704 end_index = line->start_index + line->length;
705 break;
706 default:
707 g_assert_not_reached();
708 }
709 }
710 else
711 start_index = end_index;
712
713 found = TRUE;
714 break;
715 }
716
717 prev_line = line;
718 }
719 while (pango_layout_iter_next_line (iter));
720
721 if (!found)
722 {
723 start_index = prev_line->start_index + prev_line->length;
724 end_index = start_index;
725 }
726 pango_layout_iter_free (iter);
727
728 *start_offset = g_utf8_pointer_to_offset (text, text + start_index);
729 *end_offset = g_utf8_pointer_to_offset (text, text + end_index);
730 }
731
732 /*
733 * _gtk_pango_get_text_at:
734 * @layout: a #PangoLayout
735 * @boundary_type: a #AtkTextBoundary
736 * @offset: a character offset in @layout
737 * @start_offset: return location for the start of the returned text
738 * @end_offset: return location for the end of the return text
739 *
740 * Gets a slice of the text from @layout at @offset.
741 *
742 * The @boundary_type determines the size of the returned slice of
743 * text. For the exact semantics of this function, see
744 * atk_text_get_text_after_offset().
745 *
746 * Returns: a newly allocated string containing a slice of text
747 * from layout. Free with g_free().
748 */
749 static gchar *
_gtk_pango_get_text_at(PangoLayout * layout,AtkTextBoundary boundary_type,gint offset,gint * start_offset,gint * end_offset)750 _gtk_pango_get_text_at (PangoLayout *layout,
751 AtkTextBoundary boundary_type,
752 gint offset,
753 gint *start_offset,
754 gint *end_offset)
755 {
756 const gchar *text;
757 gint start, end;
758 const PangoLogAttr *attrs;
759 gint n_attrs;
760
761 text = pango_layout_get_text (layout);
762
763 if (text[0] == 0)
764 {
765 *start_offset = 0;
766 *end_offset = 0;
767 return g_strdup ("");
768 }
769
770 attrs = pango_layout_get_log_attrs_readonly (layout, &n_attrs);
771
772 start = offset;
773 end = start;
774
775 switch (boundary_type)
776 {
777 case ATK_TEXT_BOUNDARY_CHAR:
778 end = _gtk_pango_move_chars (layout, end, 1);
779 break;
780
781 case ATK_TEXT_BOUNDARY_WORD_START:
782 if (!attrs[start].is_word_start)
783 start = _gtk_pango_move_words (layout, start, -1);
784 if (_gtk_pango_is_inside_word (layout, end))
785 end = _gtk_pango_move_words (layout, end, 1);
786 while (!attrs[end].is_word_start && end < n_attrs - 1)
787 end = _gtk_pango_move_chars (layout, end, 1);
788 break;
789
790 case ATK_TEXT_BOUNDARY_WORD_END:
791 if (_gtk_pango_is_inside_word (layout, start) &&
792 !attrs[start].is_word_start)
793 start = _gtk_pango_move_words (layout, start, -1);
794 while (!attrs[start].is_word_end && start > 0)
795 start = _gtk_pango_move_chars (layout, start, -1);
796 end = _gtk_pango_move_words (layout, end, 1);
797 break;
798
799 case ATK_TEXT_BOUNDARY_SENTENCE_START:
800 if (!attrs[start].is_sentence_start)
801 start = _gtk_pango_move_sentences (layout, start, -1);
802 if (_gtk_pango_is_inside_sentence (layout, end))
803 end = _gtk_pango_move_sentences (layout, end, 1);
804 while (!attrs[end].is_sentence_start && end < n_attrs - 1)
805 end = _gtk_pango_move_chars (layout, end, 1);
806 break;
807
808 case ATK_TEXT_BOUNDARY_SENTENCE_END:
809 if (_gtk_pango_is_inside_sentence (layout, start) &&
810 !attrs[start].is_sentence_start)
811 start = _gtk_pango_move_sentences (layout, start, -1);
812 while (!attrs[start].is_sentence_end && start > 0)
813 start = _gtk_pango_move_chars (layout, start, -1);
814 end = _gtk_pango_move_sentences (layout, end, 1);
815 break;
816
817 case ATK_TEXT_BOUNDARY_LINE_START:
818 case ATK_TEXT_BOUNDARY_LINE_END:
819 pango_layout_get_line_at (layout, boundary_type, offset, &start, &end);
820 break;
821 }
822
823 *start_offset = start;
824 *end_offset = end;
825
826 g_assert (start <= end);
827
828 return g_utf8_substring (text, start, end);
829 }
830
831 /*
832 * _gtk_pango_get_text_before:
833 * @layout: a #PangoLayout
834 * @boundary_type: a #AtkTextBoundary
835 * @offset: a character offset in @layout
836 * @start_offset: return location for the start of the returned text
837 * @end_offset: return location for the end of the return text
838 *
839 * Gets a slice of the text from @layout before @offset.
840 *
841 * The @boundary_type determines the size of the returned slice of
842 * text. For the exact semantics of this function, see
843 * atk_text_get_text_before_offset().
844 *
845 * Returns: a newly allocated string containing a slice of text
846 * from layout. Free with g_free().
847 */
848 static gchar *
_gtk_pango_get_text_before(PangoLayout * layout,AtkTextBoundary boundary_type,gint offset,gint * start_offset,gint * end_offset)849 _gtk_pango_get_text_before (PangoLayout *layout,
850 AtkTextBoundary boundary_type,
851 gint offset,
852 gint *start_offset,
853 gint *end_offset)
854 {
855 const gchar *text;
856 gint start, end;
857 const PangoLogAttr *attrs;
858 gint n_attrs;
859
860 text = pango_layout_get_text (layout);
861
862 if (text[0] == 0)
863 {
864 *start_offset = 0;
865 *end_offset = 0;
866 return g_strdup ("");
867 }
868
869 attrs = pango_layout_get_log_attrs_readonly (layout, &n_attrs);
870
871 start = offset;
872 end = start;
873
874 switch (boundary_type)
875 {
876 case ATK_TEXT_BOUNDARY_CHAR:
877 start = _gtk_pango_move_chars (layout, start, -1);
878 break;
879
880 case ATK_TEXT_BOUNDARY_WORD_START:
881 if (!attrs[start].is_word_start)
882 start = _gtk_pango_move_words (layout, start, -1);
883 end = start;
884 start = _gtk_pango_move_words (layout, start, -1);
885 break;
886
887 case ATK_TEXT_BOUNDARY_WORD_END:
888 if (_gtk_pango_is_inside_word (layout, start) &&
889 !attrs[start].is_word_start)
890 start = _gtk_pango_move_words (layout, start, -1);
891 while (!attrs[start].is_word_end && start > 0)
892 start = _gtk_pango_move_chars (layout, start, -1);
893 end = start;
894 start = _gtk_pango_move_words (layout, start, -1);
895 while (!attrs[start].is_word_end && start > 0)
896 start = _gtk_pango_move_chars (layout, start, -1);
897 break;
898
899 case ATK_TEXT_BOUNDARY_SENTENCE_START:
900 if (!attrs[start].is_sentence_start)
901 start = _gtk_pango_move_sentences (layout, start, -1);
902 end = start;
903 start = _gtk_pango_move_sentences (layout, start, -1);
904 break;
905
906 case ATK_TEXT_BOUNDARY_SENTENCE_END:
907 if (_gtk_pango_is_inside_sentence (layout, start) &&
908 !attrs[start].is_sentence_start)
909 start = _gtk_pango_move_sentences (layout, start, -1);
910 while (!attrs[start].is_sentence_end && start > 0)
911 start = _gtk_pango_move_chars (layout, start, -1);
912 end = start;
913 start = _gtk_pango_move_sentences (layout, start, -1);
914 while (!attrs[start].is_sentence_end && start > 0)
915 start = _gtk_pango_move_chars (layout, start, -1);
916 break;
917
918 case ATK_TEXT_BOUNDARY_LINE_START:
919 case ATK_TEXT_BOUNDARY_LINE_END:
920 pango_layout_get_line_before (layout, boundary_type, offset, &start, &end);
921 break;
922 }
923
924 *start_offset = start;
925 *end_offset = end;
926
927 g_assert (start <= end);
928
929 return g_utf8_substring (text, start, end);
930 }
931
932 /*
933 * _gtk_pango_get_text_after:
934 * @layout: a #PangoLayout
935 * @boundary_type: a #AtkTextBoundary
936 * @offset: a character offset in @layout
937 * @start_offset: return location for the start of the returned text
938 * @end_offset: return location for the end of the return text
939 *
940 * Gets a slice of the text from @layout after @offset.
941 *
942 * The @boundary_type determines the size of the returned slice of
943 * text. For the exact semantics of this function, see
944 * atk_text_get_text_after_offset().
945 *
946 * Returns: a newly allocated string containing a slice of text
947 * from layout. Free with g_free().
948 */
949 static gchar *
_gtk_pango_get_text_after(PangoLayout * layout,AtkTextBoundary boundary_type,gint offset,gint * start_offset,gint * end_offset)950 _gtk_pango_get_text_after (PangoLayout *layout,
951 AtkTextBoundary boundary_type,
952 gint offset,
953 gint *start_offset,
954 gint *end_offset)
955 {
956 const gchar *text;
957 gint start, end;
958 const PangoLogAttr *attrs;
959 gint n_attrs;
960
961 text = pango_layout_get_text (layout);
962
963 if (text[0] == 0)
964 {
965 *start_offset = 0;
966 *end_offset = 0;
967 return g_strdup ("");
968 }
969
970 attrs = pango_layout_get_log_attrs_readonly (layout, &n_attrs);
971
972 start = offset;
973 end = start;
974
975 switch (boundary_type)
976 {
977 case ATK_TEXT_BOUNDARY_CHAR:
978 start = _gtk_pango_move_chars (layout, start, 1);
979 end = start;
980 end = _gtk_pango_move_chars (layout, end, 1);
981 break;
982
983 case ATK_TEXT_BOUNDARY_WORD_START:
984 if (_gtk_pango_is_inside_word (layout, end))
985 end = _gtk_pango_move_words (layout, end, 1);
986 while (!attrs[end].is_word_start && end < n_attrs - 1)
987 end = _gtk_pango_move_chars (layout, end, 1);
988 start = end;
989 if (end < n_attrs - 1)
990 {
991 end = _gtk_pango_move_words (layout, end, 1);
992 while (!attrs[end].is_word_start && end < n_attrs - 1)
993 end = _gtk_pango_move_chars (layout, end, 1);
994 }
995 break;
996
997 case ATK_TEXT_BOUNDARY_WORD_END:
998 end = _gtk_pango_move_words (layout, end, 1);
999 start = end;
1000 if (end < n_attrs - 1)
1001 end = _gtk_pango_move_words (layout, end, 1);
1002 break;
1003
1004 case ATK_TEXT_BOUNDARY_SENTENCE_START:
1005 if (_gtk_pango_is_inside_sentence (layout, end))
1006 end = _gtk_pango_move_sentences (layout, end, 1);
1007 while (!attrs[end].is_sentence_start && end < n_attrs - 1)
1008 end = _gtk_pango_move_chars (layout, end, 1);
1009 start = end;
1010 if (end < n_attrs - 1)
1011 {
1012 end = _gtk_pango_move_sentences (layout, end, 1);
1013 while (!attrs[end].is_sentence_start && end < n_attrs - 1)
1014 end = _gtk_pango_move_chars (layout, end, 1);
1015 }
1016 break;
1017
1018 case ATK_TEXT_BOUNDARY_SENTENCE_END:
1019 end = _gtk_pango_move_sentences (layout, end, 1);
1020 start = end;
1021 if (end < n_attrs - 1)
1022 end = _gtk_pango_move_sentences (layout, end, 1);
1023 break;
1024
1025 case ATK_TEXT_BOUNDARY_LINE_START:
1026 case ATK_TEXT_BOUNDARY_LINE_END:
1027 pango_layout_get_line_after (layout, boundary_type, offset, &start, &end);
1028 break;
1029 }
1030
1031 *start_offset = start;
1032 *end_offset = end;
1033
1034 g_assert (start <= end);
1035
1036 return g_utf8_substring (text, start, end);
1037 }
1038
1039 /***** atktext.h ******/
1040
1041 static void
cally_text_text_interface_init(AtkTextIface * iface)1042 cally_text_text_interface_init (AtkTextIface *iface)
1043 {
1044 g_return_if_fail (iface != NULL);
1045
1046 iface->get_text = cally_text_get_text;
1047 iface->get_character_at_offset = cally_text_get_character_at_offset;
1048 iface->get_text_before_offset = cally_text_get_text_before_offset;
1049 iface->get_text_at_offset = cally_text_get_text_at_offset;
1050 iface->get_text_after_offset = cally_text_get_text_after_offset;
1051 iface->get_character_count = cally_text_get_character_count;
1052 iface->get_caret_offset = cally_text_get_caret_offset;
1053 iface->set_caret_offset = cally_text_set_caret_offset;
1054 iface->get_n_selections = cally_text_get_n_selections;
1055 iface->get_selection = cally_text_get_selection;
1056 iface->add_selection = cally_text_add_selection;
1057 iface->remove_selection = cally_text_remove_selection;
1058 iface->set_selection = cally_text_set_selection;
1059 iface->get_run_attributes = cally_text_get_run_attributes;
1060 iface->get_default_attributes = cally_text_get_default_attributes;
1061 iface->get_character_extents = cally_text_get_character_extents;
1062 iface->get_offset_at_point = cally_text_get_offset_at_point;
1063
1064 }
1065
1066 static gchar*
cally_text_get_text(AtkText * text,gint start_offset,gint end_offset)1067 cally_text_get_text (AtkText *text,
1068 gint start_offset,
1069 gint end_offset)
1070 {
1071 ClutterActor *actor = NULL;
1072 PangoLayout *layout = NULL;
1073 const gchar *string = NULL;
1074 gint character_count = 0;
1075
1076 actor = CALLY_GET_CLUTTER_ACTOR (text);
1077 if (actor == NULL) /* Object is defunct */
1078 return NULL;
1079
1080 /* we use the pango layout instead of clutter_text_get_chars because
1081 it take into account password-char */
1082
1083 layout = clutter_text_get_layout (CLUTTER_TEXT (actor));
1084 string = pango_layout_get_text (layout);
1085 character_count = pango_layout_get_character_count (layout);
1086
1087 if (end_offset == -1 || end_offset > character_count)
1088 end_offset = character_count;
1089
1090 if (string[0] == 0)
1091 return g_strdup("");
1092 else
1093 return g_utf8_substring (string, start_offset, end_offset);
1094 }
1095
1096 static gunichar
cally_text_get_character_at_offset(AtkText * text,gint offset)1097 cally_text_get_character_at_offset (AtkText *text,
1098 gint offset)
1099 {
1100 ClutterActor *actor = NULL;
1101 const gchar *string = NULL;
1102 gchar *index = NULL;
1103 gunichar unichar;
1104 PangoLayout *layout = NULL;
1105
1106 actor = CALLY_GET_CLUTTER_ACTOR (text);
1107 if (actor == NULL) /* State is defunct */
1108 return '\0';
1109
1110 /* we use the pango layout instead of clutter_text_get_chars because
1111 it take into account password-char */
1112
1113 layout = clutter_text_get_layout (CLUTTER_TEXT (actor));
1114 string = pango_layout_get_text (layout);
1115
1116 if (offset >= g_utf8_strlen (string, -1))
1117 {
1118 unichar = '\0';
1119 }
1120 else
1121 {
1122 index = g_utf8_offset_to_pointer (string, offset);
1123
1124 unichar = g_utf8_get_char (index);
1125 }
1126
1127 return unichar;
1128 }
1129
1130 static gchar*
cally_text_get_text_before_offset(AtkText * text,gint offset,AtkTextBoundary boundary_type,gint * start_offset,gint * end_offset)1131 cally_text_get_text_before_offset (AtkText *text,
1132 gint offset,
1133 AtkTextBoundary boundary_type,
1134 gint *start_offset,
1135 gint *end_offset)
1136 {
1137 ClutterActor *actor = NULL;
1138
1139 actor = CALLY_GET_CLUTTER_ACTOR (text);
1140 if (actor == NULL) /* State is defunct */
1141 return NULL;
1142
1143 return _gtk_pango_get_text_before (clutter_text_get_layout (CLUTTER_TEXT (actor)),
1144 boundary_type, offset,
1145 start_offset, end_offset);
1146 }
1147
1148 static gchar*
cally_text_get_text_at_offset(AtkText * text,gint offset,AtkTextBoundary boundary_type,gint * start_offset,gint * end_offset)1149 cally_text_get_text_at_offset (AtkText *text,
1150 gint offset,
1151 AtkTextBoundary boundary_type,
1152 gint *start_offset,
1153 gint *end_offset)
1154 {
1155 ClutterActor *actor = NULL;
1156
1157 actor = CALLY_GET_CLUTTER_ACTOR (text);
1158 if (actor == NULL) /* State is defunct */
1159 return NULL;
1160
1161 return _gtk_pango_get_text_at (clutter_text_get_layout (CLUTTER_TEXT (actor)),
1162 boundary_type, offset,
1163 start_offset, end_offset);
1164 }
1165
1166 static gchar*
cally_text_get_text_after_offset(AtkText * text,gint offset,AtkTextBoundary boundary_type,gint * start_offset,gint * end_offset)1167 cally_text_get_text_after_offset (AtkText *text,
1168 gint offset,
1169 AtkTextBoundary boundary_type,
1170 gint *start_offset,
1171 gint *end_offset)
1172 {
1173 ClutterActor *actor = NULL;
1174
1175 actor = CALLY_GET_CLUTTER_ACTOR (text);
1176 if (actor == NULL) /* State is defunct */
1177 return NULL;
1178
1179 return _gtk_pango_get_text_after (clutter_text_get_layout (CLUTTER_TEXT (actor)),
1180 boundary_type, offset,
1181 start_offset, end_offset);
1182 }
1183
1184 static gint
cally_text_get_caret_offset(AtkText * text)1185 cally_text_get_caret_offset (AtkText *text)
1186 {
1187 ClutterActor *actor = NULL;
1188
1189 actor = CALLY_GET_CLUTTER_ACTOR (text);
1190 if (actor == NULL) /* State is defunct */
1191 return -1;
1192
1193 return clutter_text_get_cursor_position (CLUTTER_TEXT (actor));
1194 }
1195
1196 static gboolean
cally_text_set_caret_offset(AtkText * text,gint offset)1197 cally_text_set_caret_offset (AtkText *text,
1198 gint offset)
1199 {
1200 ClutterActor *actor = NULL;
1201
1202 actor = CALLY_GET_CLUTTER_ACTOR (text);
1203 if (actor == NULL) /* State is defunct */
1204 return FALSE;
1205
1206 clutter_text_set_cursor_position (CLUTTER_TEXT (actor), offset);
1207
1208 /* like in gailentry, we suppose that this always works, as clutter text
1209 doesn't return anything */
1210 return TRUE;
1211 }
1212
1213 static gint
cally_text_get_character_count(AtkText * text)1214 cally_text_get_character_count (AtkText *text)
1215 {
1216 ClutterActor *actor = NULL;
1217 ClutterText *clutter_text = NULL;
1218
1219 actor = CALLY_GET_CLUTTER_ACTOR (text);
1220 if (actor == NULL) /* State is defunct */
1221 return 0;
1222
1223 clutter_text = CLUTTER_TEXT (actor);
1224 return g_utf8_strlen (clutter_text_get_text (clutter_text), -1);
1225 }
1226
1227 static gint
cally_text_get_n_selections(AtkText * text)1228 cally_text_get_n_selections (AtkText *text)
1229 {
1230 ClutterActor *actor = NULL;
1231 gint selection_bound = -1;
1232 gint cursor_position = -1;
1233
1234 actor = CALLY_GET_CLUTTER_ACTOR (text);
1235 if (actor == NULL) /* State is defunct */
1236 return 0;
1237
1238 if (!clutter_text_get_selectable (CLUTTER_TEXT (actor)))
1239 return 0;
1240
1241 selection_bound = clutter_text_get_selection_bound (CLUTTER_TEXT (actor));
1242 cursor_position = clutter_text_get_cursor_position (CLUTTER_TEXT (actor));
1243
1244 if (selection_bound == cursor_position)
1245 return 0;
1246 else
1247 return 1;
1248 }
1249
1250 static gchar*
cally_text_get_selection(AtkText * text,gint selection_num,gint * start_offset,gint * end_offset)1251 cally_text_get_selection (AtkText *text,
1252 gint selection_num,
1253 gint *start_offset,
1254 gint *end_offset)
1255 {
1256 ClutterActor *actor = NULL;
1257
1258 actor = CALLY_GET_CLUTTER_ACTOR (text);
1259 if (actor == NULL) /* State is defunct */
1260 return NULL;
1261
1262 /* As in gailentry, only let the user get the selection if one is set, and if
1263 * the selection_num is 0.
1264 */
1265 if (selection_num != 0)
1266 return NULL;
1267
1268 _cally_text_get_selection_bounds (CLUTTER_TEXT (actor), start_offset, end_offset);
1269
1270 if (*start_offset != *end_offset)
1271 return clutter_text_get_selection (CLUTTER_TEXT (actor));
1272 else
1273 return NULL;
1274 }
1275
1276 /* ClutterText only allows one selection. So this method will set the selection
1277 if no selection exists, but as in gailentry, it will not change the current
1278 selection */
1279 static gboolean
cally_text_add_selection(AtkText * text,gint start_offset,gint end_offset)1280 cally_text_add_selection (AtkText *text,
1281 gint start_offset,
1282 gint end_offset)
1283 {
1284 ClutterActor *actor;
1285 gint select_start, select_end;
1286
1287 actor = CALLY_GET_CLUTTER_ACTOR (text);
1288 if (actor == NULL) /* State is defunct */
1289 return FALSE;
1290
1291 _cally_text_get_selection_bounds (CLUTTER_TEXT (actor),
1292 &select_start, &select_end);
1293
1294 /* Like in gailentry, if there is already a selection, then don't allow another
1295 * to be added, since ClutterText only supports one selected region.
1296 */
1297 if (select_start == select_end)
1298 {
1299 clutter_text_set_selection (CLUTTER_TEXT (actor),
1300 start_offset, end_offset);
1301
1302 return TRUE;
1303 }
1304 else
1305 return FALSE;
1306 }
1307
1308
1309 static gboolean
cally_text_remove_selection(AtkText * text,gint selection_num)1310 cally_text_remove_selection (AtkText *text,
1311 gint selection_num)
1312 {
1313 ClutterActor *actor = NULL;
1314 gint caret_pos = -1;
1315 gint select_start = -1;
1316 gint select_end = -1;
1317
1318 actor = CALLY_GET_CLUTTER_ACTOR (text);
1319 if (actor == NULL) /* State is defunct */
1320 return FALSE;
1321
1322 /* only one selection is allowed */
1323 if (selection_num != 0)
1324 return FALSE;
1325
1326 _cally_text_get_selection_bounds (CLUTTER_TEXT (actor),
1327 &select_start, &select_end);
1328
1329 if (select_start != select_end)
1330 {
1331 /* Setting the start & end of the selected region to the caret position
1332 * turns off the selection.
1333 */
1334 caret_pos = clutter_text_get_cursor_position (CLUTTER_TEXT (actor));
1335 clutter_text_set_selection (CLUTTER_TEXT (actor),
1336 caret_pos, caret_pos);
1337 return TRUE;
1338 }
1339 else
1340 return FALSE;
1341 }
1342
1343 static gboolean
cally_text_set_selection(AtkText * text,gint selection_num,gint start_offset,gint end_offset)1344 cally_text_set_selection (AtkText *text,
1345 gint selection_num,
1346 gint start_offset,
1347 gint end_offset)
1348 {
1349 ClutterActor *actor = NULL;
1350 gint select_start = -1;
1351 gint select_end = -1;
1352
1353 actor = CALLY_GET_CLUTTER_ACTOR (text);
1354 if (actor == NULL) /* State is defunct */
1355 return FALSE;
1356
1357 /* Like in gailentry, only let the user move the selection if one is set,
1358 * and if the selection_num is 0
1359 */
1360 if (selection_num != 0)
1361 return FALSE;
1362
1363 _cally_text_get_selection_bounds (CLUTTER_TEXT (actor),
1364 &select_start, &select_end);
1365
1366 if (select_start != select_end)
1367 {
1368 clutter_text_set_selection (CLUTTER_TEXT (actor),
1369 start_offset, end_offset);
1370 return TRUE;
1371 }
1372 else
1373 return FALSE;
1374 }
1375
1376 static AtkAttributeSet*
cally_text_get_run_attributes(AtkText * text,gint offset,gint * start_offset,gint * end_offset)1377 cally_text_get_run_attributes (AtkText *text,
1378 gint offset,
1379 gint *start_offset,
1380 gint *end_offset)
1381 {
1382 ClutterActor *actor = NULL;
1383 ClutterText *clutter_text = NULL;
1384 AtkAttributeSet *at_set = NULL;
1385
1386 actor = CALLY_GET_CLUTTER_ACTOR (text);
1387 if (actor == NULL) /* State is defunct */
1388 return NULL;
1389
1390 /* Clutter don't have any reference to the direction*/
1391
1392 clutter_text = CLUTTER_TEXT (actor);
1393
1394 at_set = _cally_misc_layout_get_run_attributes (at_set,
1395 clutter_text,
1396 offset,
1397 start_offset,
1398 end_offset);
1399
1400 return at_set;
1401 }
1402
1403 static AtkAttributeSet*
cally_text_get_default_attributes(AtkText * text)1404 cally_text_get_default_attributes (AtkText *text)
1405 {
1406 ClutterActor *actor = NULL;
1407 ClutterText *clutter_text = NULL;
1408 AtkAttributeSet *at_set = NULL;
1409
1410 actor = CALLY_GET_CLUTTER_ACTOR (text);
1411 if (actor == NULL) /* State is defunct */
1412 return NULL;
1413
1414 clutter_text = CLUTTER_TEXT (actor);
1415
1416 at_set = _cally_misc_layout_get_default_attributes (at_set, clutter_text);
1417
1418 return at_set;
1419 }
1420
cally_text_get_character_extents(AtkText * text,gint offset,gint * xp,gint * yp,gint * widthp,gint * heightp,AtkCoordType coords)1421 static void cally_text_get_character_extents (AtkText *text,
1422 gint offset,
1423 gint *xp,
1424 gint *yp,
1425 gint *widthp,
1426 gint *heightp,
1427 AtkCoordType coords)
1428 {
1429 ClutterActor *actor = NULL;
1430 ClutterText *clutter_text = NULL;
1431 gint x = 0, y = 0, width = 0, height = 0;
1432 gint index, x_window, y_window, x_toplevel, y_toplevel;
1433 gint x_layout, y_layout;
1434 PangoLayout *layout;
1435 PangoRectangle extents;
1436 const gchar *text_value;
1437 graphene_point3d_t verts[4];
1438
1439 actor = CALLY_GET_CLUTTER_ACTOR (text);
1440 if (actor == NULL) /* State is defunct */
1441 goto done;
1442
1443 clutter_text = CLUTTER_TEXT (actor);
1444
1445 text_value = clutter_text_get_text (clutter_text);
1446 index = g_utf8_offset_to_pointer (text_value, offset) - text_value;
1447
1448 layout = clutter_text_get_layout (clutter_text);
1449 pango_layout_index_to_pos (layout, index, &extents);
1450
1451 /* handle RTL text layout */
1452 if (extents.width < 0)
1453 {
1454 extents.x += extents.width;
1455 extents.width = -extents.width;
1456 }
1457
1458 clutter_actor_get_abs_allocation_vertices (actor, verts);
1459 x_window = verts[0].x;
1460 y_window = verts[0].y;
1461
1462 clutter_text_get_layout_offsets (clutter_text, &x_layout, &y_layout);
1463
1464 x = (extents.x / PANGO_SCALE) + x_layout + x_window;
1465 y = (extents.y / PANGO_SCALE) + y_layout + y_window;
1466 width = extents.width / PANGO_SCALE;
1467 height = extents.height / PANGO_SCALE;
1468
1469 if (coords == ATK_XY_SCREEN)
1470 {
1471 _cally_actor_get_top_level_origin (actor, &x_toplevel, &y_toplevel);
1472 x += x_toplevel;
1473 y += y_toplevel;
1474 }
1475
1476 done:
1477 if (widthp)
1478 *widthp = width;
1479
1480 if (heightp)
1481 *heightp = height;
1482
1483 if (xp)
1484 *xp = x;
1485
1486 if (yp)
1487 *yp = y;
1488 }
1489
1490 static gint
cally_text_get_offset_at_point(AtkText * text,gint x,gint y,AtkCoordType coords)1491 cally_text_get_offset_at_point (AtkText *text,
1492 gint x,
1493 gint y,
1494 AtkCoordType coords)
1495 {
1496 ClutterActor *actor = NULL;
1497 ClutterText *clutter_text = NULL;
1498 const gchar *text_value;
1499 gint index;
1500
1501 actor = CALLY_GET_CLUTTER_ACTOR (text);
1502 if (actor == NULL) /* State is defunct */
1503 return -1;
1504
1505 clutter_text = CLUTTER_TEXT (actor);
1506
1507 index = _cally_misc_get_index_at_point (clutter_text, x, y, coords);
1508 text_value = clutter_text_get_text (clutter_text);
1509 if (index == -1)
1510 return g_utf8_strlen (text_value, -1);
1511 else
1512 return g_utf8_pointer_to_offset (text_value, text_value + index);
1513 }
1514
1515
1516 /******** Auxiliary private methods ******/
1517
1518 /* ClutterText only maintains the current cursor position and a extra selection
1519 bound, but this could be before or after the cursor. This method returns
1520 the start and end positions in a proper order (so start<=end). This is
1521 similar to the function gtk_editable_get_selection_bounds */
1522 static void
_cally_text_get_selection_bounds(ClutterText * clutter_text,gint * start_offset,gint * end_offset)1523 _cally_text_get_selection_bounds (ClutterText *clutter_text,
1524 gint *start_offset,
1525 gint *end_offset)
1526 {
1527 gint pos = -1;
1528 gint selection_bound = -1;
1529
1530 pos = clutter_text_get_cursor_position (clutter_text);
1531 selection_bound = clutter_text_get_selection_bound (clutter_text);
1532
1533 if (pos < selection_bound)
1534 {
1535 *start_offset = pos;
1536 *end_offset = selection_bound;
1537 }
1538 else
1539 {
1540 *start_offset = selection_bound;
1541 *end_offset = pos;
1542 }
1543 }
1544
1545 static void
_cally_text_delete_text_cb(ClutterText * clutter_text,gint start_pos,gint end_pos,gpointer data)1546 _cally_text_delete_text_cb (ClutterText *clutter_text,
1547 gint start_pos,
1548 gint end_pos,
1549 gpointer data)
1550 {
1551 CallyText *cally_text = NULL;
1552
1553 g_return_if_fail (CALLY_IS_TEXT (data));
1554
1555 /* Ignore zero length deletions */
1556 if (end_pos - start_pos == 0)
1557 return;
1558
1559 cally_text = CALLY_TEXT (data);
1560
1561 if (!cally_text->priv->signal_name_delete)
1562 {
1563 cally_text->priv->signal_name_delete = "text_changed::delete";
1564 cally_text->priv->position_delete = start_pos;
1565 cally_text->priv->length_delete = end_pos - start_pos;
1566 }
1567
1568 _notify_delete (cally_text);
1569 }
1570
1571 static void
_cally_text_insert_text_cb(ClutterText * clutter_text,gchar * new_text,gint new_text_length,gint * position,gpointer data)1572 _cally_text_insert_text_cb (ClutterText *clutter_text,
1573 gchar *new_text,
1574 gint new_text_length,
1575 gint *position,
1576 gpointer data)
1577 {
1578 CallyText *cally_text = NULL;
1579
1580 g_return_if_fail (CALLY_IS_TEXT (data));
1581
1582 cally_text = CALLY_TEXT (data);
1583
1584 if (!cally_text->priv->signal_name_insert)
1585 {
1586 cally_text->priv->signal_name_insert = "text_changed::insert";
1587 cally_text->priv->position_insert = *position;
1588 cally_text->priv->length_insert = g_utf8_strlen (new_text, new_text_length);
1589 }
1590
1591 /*
1592 * The signal will be emitted when the cursor position is updated,
1593 * or in an idle handler if it not updated.
1594 */
1595 if (cally_text->priv->insert_idle_handler == 0)
1596 cally_text->priv->insert_idle_handler = clutter_threads_add_idle (_idle_notify_insert,
1597 cally_text);
1598 }
1599
1600 /***** atkeditabletext.h ******/
1601
1602 static void
cally_text_editable_text_interface_init(AtkEditableTextIface * iface)1603 cally_text_editable_text_interface_init (AtkEditableTextIface *iface)
1604 {
1605 g_return_if_fail (iface != NULL);
1606
1607 iface->set_text_contents = cally_text_set_text_contents;
1608 iface->insert_text = cally_text_insert_text;
1609 iface->delete_text = cally_text_delete_text;
1610
1611 iface->set_run_attributes = NULL;
1612 iface->copy_text = NULL;
1613 iface->cut_text = NULL;
1614 iface->paste_text = NULL;
1615 }
1616
1617 static void
cally_text_set_text_contents(AtkEditableText * text,const gchar * string)1618 cally_text_set_text_contents (AtkEditableText *text,
1619 const gchar *string)
1620 {
1621 ClutterActor *actor = NULL;
1622
1623 actor = CALLY_GET_CLUTTER_ACTOR (text);
1624 if (actor == NULL)
1625 return;
1626
1627 if (!clutter_text_get_editable (CLUTTER_TEXT (actor)))
1628 return;
1629
1630 clutter_text_set_text (CLUTTER_TEXT (actor),
1631 string);
1632 }
1633
1634
1635 static void
cally_text_insert_text(AtkEditableText * text,const gchar * string,gint length,gint * position)1636 cally_text_insert_text (AtkEditableText *text,
1637 const gchar *string,
1638 gint length,
1639 gint *position)
1640 {
1641 ClutterActor *actor = NULL;
1642
1643 actor = CALLY_GET_CLUTTER_ACTOR (text);
1644 if (actor == NULL)
1645 return;
1646
1647 if (!clutter_text_get_editable (CLUTTER_TEXT (actor)))
1648 return;
1649
1650 if (length < 0)
1651 length = g_utf8_strlen (string, -1);
1652
1653 clutter_text_insert_text (CLUTTER_TEXT (actor),
1654 string, *position);
1655
1656 /* we suppose that the text insertion will be successful,
1657 clutter-text doesn't warn about it. A option would be search for
1658 the text, but it seems not really required */
1659 *position += length;
1660 }
1661
cally_text_delete_text(AtkEditableText * text,gint start_pos,gint end_pos)1662 static void cally_text_delete_text (AtkEditableText *text,
1663 gint start_pos,
1664 gint end_pos)
1665 {
1666 ClutterActor *actor = NULL;
1667
1668 actor = CALLY_GET_CLUTTER_ACTOR (text);
1669 if (actor == NULL)
1670 return;
1671
1672 if (!clutter_text_get_editable (CLUTTER_TEXT (actor)))
1673 return;
1674
1675 clutter_text_delete_text (CLUTTER_TEXT (actor),
1676 start_pos, end_pos);
1677 }
1678
1679 /* CallyActor */
1680 static void
cally_text_notify_clutter(GObject * obj,GParamSpec * pspec)1681 cally_text_notify_clutter (GObject *obj,
1682 GParamSpec *pspec)
1683 {
1684 ClutterText *clutter_text = NULL;
1685 CallyText *cally_text = NULL;
1686 AtkObject *atk_obj = NULL;
1687
1688 clutter_text = CLUTTER_TEXT (obj);
1689 atk_obj = clutter_actor_get_accessible (CLUTTER_ACTOR (obj));
1690 cally_text = CALLY_TEXT (atk_obj);
1691
1692 if (g_strcmp0 (pspec->name, "position") == 0)
1693 {
1694 /* the selection can change also for the cursor position */
1695 if (_check_for_selection_change (cally_text, clutter_text))
1696 g_signal_emit_by_name (atk_obj, "text_selection_changed");
1697
1698 g_signal_emit_by_name (atk_obj, "text_caret_moved",
1699 clutter_text_get_cursor_position (clutter_text));
1700 }
1701 else if (g_strcmp0 (pspec->name, "selection-bound") == 0)
1702 {
1703 if (_check_for_selection_change (cally_text, clutter_text))
1704 g_signal_emit_by_name (atk_obj, "text_selection_changed");
1705 }
1706 else if (g_strcmp0 (pspec->name, "editable") == 0)
1707 {
1708 atk_object_notify_state_change (atk_obj, ATK_STATE_EDITABLE,
1709 clutter_text_get_editable (clutter_text));
1710 }
1711 else if (g_strcmp0 (pspec->name, "activatable") == 0)
1712 {
1713 _check_activate_action (cally_text, clutter_text);
1714 }
1715 else if (g_strcmp0 (pspec->name, "password-char") == 0)
1716 {
1717 if (clutter_text_get_password_char (clutter_text) != 0)
1718 atk_object_set_role (atk_obj, ATK_ROLE_PASSWORD_TEXT);
1719 else
1720 atk_object_set_role (atk_obj, ATK_ROLE_TEXT);
1721 }
1722 else
1723 {
1724 CALLY_ACTOR_CLASS (cally_text_parent_class)->notify_clutter (obj, pspec);
1725 }
1726 }
1727
1728 static gboolean
_check_for_selection_change(CallyText * cally_text,ClutterText * clutter_text)1729 _check_for_selection_change (CallyText *cally_text,
1730 ClutterText *clutter_text)
1731 {
1732 gboolean ret_val = FALSE;
1733 gint clutter_pos = -1;
1734 gint clutter_bound = -1;
1735
1736 clutter_pos = clutter_text_get_cursor_position (clutter_text);
1737 clutter_bound = clutter_text_get_selection_bound (clutter_text);
1738
1739 if (clutter_pos != clutter_bound)
1740 {
1741 if (clutter_pos != cally_text->priv->cursor_position ||
1742 clutter_bound != cally_text->priv->selection_bound)
1743 /*
1744 * This check is here as this function can be called for
1745 * notification of selection_bound and current_pos. The
1746 * values of current_pos and selection_bound may be the same
1747 * for both notifications and we only want to generate one
1748 * text_selection_changed signal.
1749 */
1750 ret_val = TRUE;
1751 }
1752 else
1753 {
1754 /* We had a selection */
1755 ret_val = (cally_text->priv->cursor_position != cally_text->priv->selection_bound);
1756 }
1757
1758 cally_text->priv->cursor_position = clutter_pos;
1759 cally_text->priv->selection_bound = clutter_bound;
1760
1761 return ret_val;
1762 }
1763
1764 static gboolean
_idle_notify_insert(gpointer data)1765 _idle_notify_insert (gpointer data)
1766 {
1767 CallyText *cally_text = NULL;
1768
1769 cally_text = CALLY_TEXT (data);
1770 cally_text->priv->insert_idle_handler = 0;
1771
1772 _notify_insert (cally_text);
1773
1774 return FALSE;
1775 }
1776
1777 static void
_notify_insert(CallyText * cally_text)1778 _notify_insert (CallyText *cally_text)
1779 {
1780 if (cally_text->priv->signal_name_insert)
1781 {
1782 g_signal_emit_by_name (cally_text,
1783 cally_text->priv->signal_name_insert,
1784 cally_text->priv->position_insert,
1785 cally_text->priv->length_insert);
1786 cally_text->priv->signal_name_insert = NULL;
1787 }
1788 }
1789
1790 static void
_notify_delete(CallyText * cally_text)1791 _notify_delete (CallyText *cally_text)
1792 {
1793 if (cally_text->priv->signal_name_delete)
1794 {
1795 g_signal_emit_by_name (cally_text,
1796 cally_text->priv->signal_name_delete,
1797 cally_text->priv->position_delete,
1798 cally_text->priv->length_delete);
1799 cally_text->priv->signal_name_delete = NULL;
1800 }
1801 }
1802 /* atkaction */
1803
1804 static void
_cally_text_activate_action(CallyActor * cally_actor)1805 _cally_text_activate_action (CallyActor *cally_actor)
1806 {
1807 ClutterActor *actor = NULL;
1808
1809 actor = CALLY_GET_CLUTTER_ACTOR (cally_actor);
1810
1811 clutter_text_activate (CLUTTER_TEXT (actor));
1812 }
1813
1814 static void
_check_activate_action(CallyText * cally_text,ClutterText * clutter_text)1815 _check_activate_action (CallyText *cally_text,
1816 ClutterText *clutter_text)
1817 {
1818
1819 if (clutter_text_get_activatable (clutter_text))
1820 {
1821 if (cally_text->priv->activate_action_id != 0)
1822 return;
1823
1824 cally_text->priv->activate_action_id = cally_actor_add_action (CALLY_ACTOR (cally_text),
1825 "activate", NULL, NULL,
1826 _cally_text_activate_action);
1827 }
1828 else
1829 {
1830 if (cally_text->priv->activate_action_id == 0)
1831 return;
1832
1833 if (cally_actor_remove_action (CALLY_ACTOR (cally_text),
1834 cally_text->priv->activate_action_id))
1835 {
1836 cally_text->priv->activate_action_id = 0;
1837 }
1838 }
1839 }
1840
1841 /* GailTextUtil/GailMisc reimplementation methods */
1842
1843 /**
1844 * _cally_misc_add_attribute:
1845 *
1846 * Reimplementation of gail_misc_layout_get_run_attributes (check this
1847 * function for more documentation).
1848 *
1849 * Returns: A pointer to the new #AtkAttributeSet.
1850 **/
1851 static AtkAttributeSet*
_cally_misc_add_attribute(AtkAttributeSet * attrib_set,AtkTextAttribute attr,gchar * value)1852 _cally_misc_add_attribute (AtkAttributeSet *attrib_set,
1853 AtkTextAttribute attr,
1854 gchar *value)
1855 {
1856 AtkAttributeSet *return_set;
1857 AtkAttribute *at = g_malloc (sizeof (AtkAttribute));
1858 at->name = g_strdup (atk_text_attribute_get_name (attr));
1859 at->value = value;
1860 return_set = g_slist_prepend(attrib_set, at);
1861 return return_set;
1862 }
1863
1864
1865 static gint
_cally_atk_attribute_lookup_func(gconstpointer data,gconstpointer user_data)1866 _cally_atk_attribute_lookup_func (gconstpointer data,
1867 gconstpointer user_data)
1868 {
1869 AtkTextAttribute attr = (AtkTextAttribute) GPOINTER_TO_INT (user_data);
1870 AtkAttribute *at = (AtkAttribute *) data;
1871 if (!g_strcmp0 (at->name, atk_text_attribute_get_name (attr)))
1872 return 0;
1873 return -1;
1874 }
1875
1876 static gboolean
_cally_misc_find_atk_attribute(AtkAttributeSet * attrib_set,AtkTextAttribute attr)1877 _cally_misc_find_atk_attribute (AtkAttributeSet *attrib_set,
1878 AtkTextAttribute attr)
1879 {
1880 GSList* result = g_slist_find_custom ((GSList*) attrib_set,
1881 (gconstpointer) attr,
1882 _cally_atk_attribute_lookup_func);
1883 return (result != NULL);
1884 }
1885
1886 /**
1887 * _cally_misc_layout_atk_attributes_from_pango:
1888 *
1889 * Store the pango attributes as their ATK equivalent in an existing
1890 * #AtkAttributeSet.
1891 *
1892 * Returns: A pointer to the updated #AtkAttributeSet.
1893 **/
1894 static AtkAttributeSet*
_cally_misc_layout_atk_attributes_from_pango(AtkAttributeSet * attrib_set,PangoAttrIterator * iter)1895 _cally_misc_layout_atk_attributes_from_pango (AtkAttributeSet *attrib_set,
1896 PangoAttrIterator *iter)
1897 {
1898 PangoAttrString *pango_string;
1899 PangoAttrInt *pango_int;
1900 PangoAttrColor *pango_color;
1901 PangoAttrLanguage *pango_lang;
1902 PangoAttrFloat *pango_float;
1903 gchar *value = NULL;
1904
1905 if ((pango_string = (PangoAttrString*) pango_attr_iterator_get (iter,
1906 PANGO_ATTR_FAMILY)) != NULL)
1907 {
1908 value = g_strdup_printf("%s", pango_string->value);
1909 attrib_set = _cally_misc_add_attribute (attrib_set,
1910 ATK_TEXT_ATTR_FAMILY_NAME,
1911 value);
1912 }
1913 if ((pango_int = (PangoAttrInt*) pango_attr_iterator_get (iter,
1914 PANGO_ATTR_STYLE)) != NULL)
1915 {
1916 attrib_set = _cally_misc_add_attribute (attrib_set,
1917 ATK_TEXT_ATTR_STYLE,
1918 g_strdup (atk_text_attribute_get_value (ATK_TEXT_ATTR_STYLE, pango_int->value)));
1919 }
1920 if ((pango_int = (PangoAttrInt*) pango_attr_iterator_get (iter,
1921 PANGO_ATTR_WEIGHT)) != NULL)
1922 {
1923 value = g_strdup_printf("%i", pango_int->value);
1924 attrib_set = _cally_misc_add_attribute (attrib_set,
1925 ATK_TEXT_ATTR_WEIGHT,
1926 value);
1927 }
1928 if ((pango_int = (PangoAttrInt*) pango_attr_iterator_get (iter,
1929 PANGO_ATTR_VARIANT)) != NULL)
1930 {
1931 attrib_set = _cally_misc_add_attribute (attrib_set,
1932 ATK_TEXT_ATTR_VARIANT,
1933 g_strdup (atk_text_attribute_get_value (ATK_TEXT_ATTR_VARIANT, pango_int->value)));
1934 }
1935 if ((pango_int = (PangoAttrInt*) pango_attr_iterator_get (iter,
1936 PANGO_ATTR_STRETCH)) != NULL)
1937 {
1938 attrib_set = _cally_misc_add_attribute (attrib_set,
1939 ATK_TEXT_ATTR_STRETCH,
1940 g_strdup (atk_text_attribute_get_value (ATK_TEXT_ATTR_STRETCH, pango_int->value)));
1941 }
1942 if ((pango_int = (PangoAttrInt*) pango_attr_iterator_get (iter,
1943 PANGO_ATTR_SIZE)) != NULL)
1944 {
1945 value = g_strdup_printf("%i", pango_int->value / PANGO_SCALE);
1946 attrib_set = _cally_misc_add_attribute (attrib_set,
1947 ATK_TEXT_ATTR_SIZE,
1948 value);
1949 }
1950 if ((pango_int = (PangoAttrInt*) pango_attr_iterator_get (iter,
1951 PANGO_ATTR_UNDERLINE)) != NULL)
1952 {
1953 attrib_set = _cally_misc_add_attribute (attrib_set,
1954 ATK_TEXT_ATTR_UNDERLINE,
1955 g_strdup (atk_text_attribute_get_value (ATK_TEXT_ATTR_UNDERLINE, pango_int->value)));
1956 }
1957 if ((pango_int = (PangoAttrInt*) pango_attr_iterator_get (iter,
1958 PANGO_ATTR_STRIKETHROUGH)) != NULL)
1959 {
1960 attrib_set = _cally_misc_add_attribute (attrib_set,
1961 ATK_TEXT_ATTR_STRIKETHROUGH,
1962 g_strdup (atk_text_attribute_get_value (ATK_TEXT_ATTR_STRIKETHROUGH, pango_int->value)));
1963 }
1964 if ((pango_int = (PangoAttrInt*) pango_attr_iterator_get (iter,
1965 PANGO_ATTR_RISE)) != NULL)
1966 {
1967 value = g_strdup_printf("%i", pango_int->value);
1968 attrib_set = _cally_misc_add_attribute (attrib_set,
1969 ATK_TEXT_ATTR_RISE,
1970 value);
1971 }
1972 if ((pango_lang = (PangoAttrLanguage*) pango_attr_iterator_get (iter,
1973 PANGO_ATTR_LANGUAGE)) != NULL)
1974 {
1975 value = g_strdup( pango_language_to_string( pango_lang->value));
1976 attrib_set = _cally_misc_add_attribute (attrib_set,
1977 ATK_TEXT_ATTR_LANGUAGE,
1978 value);
1979 }
1980 if ((pango_float = (PangoAttrFloat*) pango_attr_iterator_get (iter,
1981 PANGO_ATTR_SCALE)) != NULL)
1982 {
1983 value = g_strdup_printf("%g", pango_float->value);
1984 attrib_set = _cally_misc_add_attribute (attrib_set,
1985 ATK_TEXT_ATTR_SCALE,
1986 value);
1987 }
1988 if ((pango_color = (PangoAttrColor*) pango_attr_iterator_get (iter,
1989 PANGO_ATTR_FOREGROUND)) != NULL)
1990 {
1991 value = g_strdup_printf ("%u,%u,%u",
1992 pango_color->color.red,
1993 pango_color->color.green,
1994 pango_color->color.blue);
1995 attrib_set = _cally_misc_add_attribute (attrib_set,
1996 ATK_TEXT_ATTR_FG_COLOR,
1997 value);
1998 }
1999 if ((pango_color = (PangoAttrColor*) pango_attr_iterator_get (iter,
2000 PANGO_ATTR_BACKGROUND)) != NULL)
2001 {
2002 value = g_strdup_printf ("%u,%u,%u",
2003 pango_color->color.red,
2004 pango_color->color.green,
2005 pango_color->color.blue);
2006 attrib_set = _cally_misc_add_attribute (attrib_set,
2007 ATK_TEXT_ATTR_BG_COLOR,
2008 value);
2009 }
2010
2011 return attrib_set;
2012 }
2013
2014 static AtkAttributeSet*
_cally_misc_add_actor_color_to_attribute_set(AtkAttributeSet * attrib_set,ClutterText * clutter_text)2015 _cally_misc_add_actor_color_to_attribute_set (AtkAttributeSet *attrib_set,
2016 ClutterText *clutter_text)
2017 {
2018 ClutterColor color;
2019 gchar *value;
2020
2021 clutter_text_get_color (clutter_text, &color);
2022 value = g_strdup_printf ("%u,%u,%u",
2023 (guint) (color.red * 65535 / 255),
2024 (guint) (color.green * 65535 / 255),
2025 (guint) (color.blue * 65535 / 255));
2026 attrib_set = _cally_misc_add_attribute (attrib_set,
2027 ATK_TEXT_ATTR_FG_COLOR,
2028 value);
2029 return attrib_set;
2030 }
2031
2032
2033 /**
2034 * _cally_misc_layout_get_run_attributes:
2035 *
2036 * Reimplementation of gail_misc_layout_get_run_attributes (check this
2037 * function for more documentation).
2038 *
2039 * Returns: A pointer to the #AtkAttributeSet.
2040 **/
2041 static AtkAttributeSet*
_cally_misc_layout_get_run_attributes(AtkAttributeSet * attrib_set,ClutterText * clutter_text,gint offset,gint * start_offset,gint * end_offset)2042 _cally_misc_layout_get_run_attributes (AtkAttributeSet *attrib_set,
2043 ClutterText *clutter_text,
2044 gint offset,
2045 gint *start_offset,
2046 gint *end_offset)
2047 {
2048 PangoAttrIterator *iter;
2049 PangoAttrList *attr;
2050 gint index, start_index, end_index;
2051 gboolean is_next = TRUE;
2052 glong len;
2053 PangoLayout *layout = clutter_text_get_layout (clutter_text);
2054 gchar *text = (gchar*) clutter_text_get_text (clutter_text);
2055
2056 len = g_utf8_strlen (text, -1);
2057 /* Grab the attributes of the PangoLayout, if any */
2058 if ((attr = pango_layout_get_attributes (layout)) == NULL)
2059 {
2060 *start_offset = 0;
2061 *end_offset = len;
2062 _cally_misc_add_actor_color_to_attribute_set (attrib_set, clutter_text);
2063 }
2064 else
2065 {
2066 iter = pango_attr_list_get_iterator (attr);
2067 /* Get invariant range offsets */
2068 /* If offset out of range, set offset in range */
2069 if (offset > len)
2070 offset = len;
2071 else if (offset < 0)
2072 offset = 0;
2073
2074 index = g_utf8_offset_to_pointer (text, offset) - text;
2075 pango_attr_iterator_range (iter, &start_index, &end_index);
2076 while (is_next)
2077 {
2078 if (index >= start_index && index < end_index)
2079 {
2080 *start_offset = g_utf8_pointer_to_offset (text,
2081 text + start_index);
2082 if (end_index == G_MAXINT)
2083 /* Last iterator */
2084 end_index = len;
2085
2086 *end_offset = g_utf8_pointer_to_offset (text,
2087 text + end_index);
2088 break;
2089 }
2090 is_next = pango_attr_iterator_next (iter);
2091 pango_attr_iterator_range (iter, &start_index, &end_index);
2092 }
2093
2094 /* Get attributes */
2095 attrib_set = _cally_misc_layout_atk_attributes_from_pango (attrib_set, iter);
2096 pango_attr_iterator_destroy (iter);
2097 }
2098
2099 if (!_cally_misc_find_atk_attribute (attrib_set, ATK_TEXT_ATTR_FG_COLOR))
2100 attrib_set = _cally_misc_add_actor_color_to_attribute_set (attrib_set, clutter_text);
2101
2102 return attrib_set;
2103 }
2104
2105
2106 /**
2107 * _cally_misc_layout_get_default_attributes:
2108 *
2109 * Reimplementation of gail_misc_layout_get_default_attributes (check this
2110 * function for more documentation).
2111 *
2112 * Returns: A pointer to the #AtkAttributeSet.
2113 **/
2114 static AtkAttributeSet*
_cally_misc_layout_get_default_attributes(AtkAttributeSet * attrib_set,ClutterText * clutter_text)2115 _cally_misc_layout_get_default_attributes (AtkAttributeSet *attrib_set,
2116 ClutterText *clutter_text)
2117 {
2118 PangoLayout *layout;
2119 PangoContext *context;
2120 PangoLanguage* language;
2121 PangoFontDescription* font;
2122 PangoWrapMode mode;
2123 gchar *value = NULL;
2124 gint int_value;
2125 ClutterTextDirection text_direction;
2126 PangoAttrIterator *iter;
2127 PangoAttrList *attr;
2128
2129 text_direction = clutter_actor_get_text_direction (CLUTTER_ACTOR (clutter_text));
2130 switch (text_direction)
2131 {
2132 case CLUTTER_TEXT_DIRECTION_DEFAULT:
2133 value = g_strdup ("none");
2134 break;
2135
2136 case CLUTTER_TEXT_DIRECTION_LTR:
2137 value = g_strdup ("ltr");
2138 break;
2139
2140 case CLUTTER_TEXT_DIRECTION_RTL:
2141 value = g_strdup ("rtl");
2142 break;
2143
2144 default:
2145 value = g_strdup ("none");
2146 break;
2147 }
2148 attrib_set = _cally_misc_add_attribute (attrib_set,
2149 ATK_TEXT_ATTR_DIRECTION,
2150 value);
2151
2152 layout = clutter_text_get_layout (clutter_text);
2153 context = pango_layout_get_context (layout);
2154 if (context)
2155 {
2156 if ((language = pango_context_get_language (context)))
2157 {
2158 value = g_strdup (pango_language_to_string (language));
2159 attrib_set = _cally_misc_add_attribute (attrib_set,
2160 ATK_TEXT_ATTR_LANGUAGE, value);
2161 }
2162
2163 if ((font = pango_context_get_font_description (context)))
2164 {
2165 value = g_strdup (atk_text_attribute_get_value (ATK_TEXT_ATTR_STYLE,
2166 pango_font_description_get_style (font)));
2167 attrib_set = _cally_misc_add_attribute (attrib_set,
2168 ATK_TEXT_ATTR_STYLE,
2169 value);
2170
2171 value = g_strdup (atk_text_attribute_get_value (ATK_TEXT_ATTR_VARIANT,
2172 pango_font_description_get_variant (font)));
2173 attrib_set = _cally_misc_add_attribute (attrib_set,
2174 ATK_TEXT_ATTR_VARIANT, value);
2175
2176 value = g_strdup (atk_text_attribute_get_value (ATK_TEXT_ATTR_STRETCH,
2177 pango_font_description_get_stretch (font)));
2178 attrib_set = _cally_misc_add_attribute (attrib_set,
2179 ATK_TEXT_ATTR_STRETCH, value);
2180
2181 value = g_strdup (pango_font_description_get_family (font));
2182 attrib_set = _cally_misc_add_attribute (attrib_set,
2183 ATK_TEXT_ATTR_FAMILY_NAME, value);
2184 value = g_strdup_printf ("%d", pango_font_description_get_weight (font));
2185 attrib_set = _cally_misc_add_attribute (attrib_set,
2186 ATK_TEXT_ATTR_WEIGHT, value);
2187
2188 value = g_strdup_printf ("%i", pango_font_description_get_size (font) / PANGO_SCALE);
2189 attrib_set = _cally_misc_add_attribute (attrib_set,
2190 ATK_TEXT_ATTR_SIZE, value);
2191
2192 }
2193
2194 }
2195
2196 if (pango_layout_get_justify (layout))
2197 int_value = 3;
2198 else
2199 {
2200 PangoAlignment align;
2201
2202 align = pango_layout_get_alignment (layout);
2203 if (align == PANGO_ALIGN_LEFT)
2204 int_value = 0;
2205 else if (align == PANGO_ALIGN_CENTER)
2206 int_value = 2;
2207 else /* if (align == PANGO_ALIGN_RIGHT) */
2208 int_value = 1;
2209 }
2210 value = g_strdup (atk_text_attribute_get_value (ATK_TEXT_ATTR_JUSTIFICATION,
2211 int_value));
2212 attrib_set = _cally_misc_add_attribute (attrib_set,
2213 ATK_TEXT_ATTR_JUSTIFICATION,
2214 value);
2215
2216 mode = pango_layout_get_wrap (layout);
2217 if (mode == PANGO_WRAP_WORD)
2218 int_value = 2;
2219 else /* if (mode == PANGO_WRAP_CHAR) */
2220 int_value = 1;
2221 value = g_strdup (atk_text_attribute_get_value (ATK_TEXT_ATTR_WRAP_MODE,
2222 int_value));
2223 attrib_set = _cally_misc_add_attribute (attrib_set,
2224 ATK_TEXT_ATTR_WRAP_MODE, value);
2225
2226 if ((attr = clutter_text_get_attributes (clutter_text)))
2227 {
2228 iter = pango_attr_list_get_iterator (attr);
2229 /* Get attributes */
2230 attrib_set = _cally_misc_layout_atk_attributes_from_pango (attrib_set, iter);
2231 pango_attr_iterator_destroy (iter);
2232 }
2233
2234
2235 if (!_cally_misc_find_atk_attribute (attrib_set, ATK_TEXT_ATTR_FG_COLOR))
2236 attrib_set = _cally_misc_add_actor_color_to_attribute_set (attrib_set,
2237 clutter_text);
2238
2239 value = g_strdup (atk_text_attribute_get_value (ATK_TEXT_ATTR_FG_STIPPLE, 0));
2240 attrib_set = _cally_misc_add_attribute (attrib_set,
2241 ATK_TEXT_ATTR_FG_STIPPLE,
2242 value);
2243
2244 value = g_strdup (atk_text_attribute_get_value (ATK_TEXT_ATTR_BG_STIPPLE, 0));
2245 attrib_set = _cally_misc_add_attribute (attrib_set,
2246 ATK_TEXT_ATTR_BG_STIPPLE,
2247 value);
2248 attrib_set = _cally_misc_add_attribute (attrib_set,
2249 ATK_TEXT_ATTR_BG_FULL_HEIGHT,
2250 g_strdup_printf ("%i", 0));
2251 attrib_set = _cally_misc_add_attribute (attrib_set,
2252 ATK_TEXT_ATTR_PIXELS_INSIDE_WRAP,
2253 g_strdup_printf ("%i", 0));
2254 attrib_set = _cally_misc_add_attribute (attrib_set,
2255 ATK_TEXT_ATTR_PIXELS_BELOW_LINES,
2256 g_strdup_printf ("%i", 0));
2257 attrib_set = _cally_misc_add_attribute (attrib_set,
2258 ATK_TEXT_ATTR_PIXELS_ABOVE_LINES,
2259 g_strdup_printf ("%i", 0));
2260 value = g_strdup (atk_text_attribute_get_value (ATK_TEXT_ATTR_EDITABLE,
2261 clutter_text_get_editable (clutter_text)));
2262 attrib_set = _cally_misc_add_attribute (attrib_set,
2263 ATK_TEXT_ATTR_EDITABLE,
2264 value);
2265
2266 value = g_strdup (atk_text_attribute_get_value (ATK_TEXT_ATTR_INVISIBLE,
2267 !clutter_actor_is_visible (CLUTTER_ACTOR (clutter_text))));
2268 attrib_set = _cally_misc_add_attribute (attrib_set,
2269 ATK_TEXT_ATTR_INVISIBLE, value);
2270
2271 value = g_strdup_printf ("%i", pango_layout_get_indent (layout));
2272 attrib_set = _cally_misc_add_attribute (attrib_set,
2273 ATK_TEXT_ATTR_INDENT, value);
2274 attrib_set = _cally_misc_add_attribute (attrib_set,
2275 ATK_TEXT_ATTR_RIGHT_MARGIN,
2276 g_strdup_printf ("%i", 0));
2277 attrib_set = _cally_misc_add_attribute (attrib_set,
2278 ATK_TEXT_ATTR_LEFT_MARGIN,
2279 g_strdup_printf ("%i", 0));
2280
2281 return attrib_set;
2282 }
2283
2284 static int
_cally_misc_get_index_at_point(ClutterText * clutter_text,gint x,gint y,AtkCoordType coords)2285 _cally_misc_get_index_at_point (ClutterText *clutter_text,
2286 gint x,
2287 gint y,
2288 AtkCoordType coords)
2289 {
2290 gint index, x_window, y_window, x_toplevel, y_toplevel;
2291 gint x_temp, y_temp;
2292 gboolean ret;
2293 graphene_point3d_t verts[4];
2294 PangoLayout *layout;
2295 gint x_layout, y_layout;
2296
2297 clutter_text_get_layout_offsets (clutter_text, &x_layout, &y_layout);
2298
2299 clutter_actor_get_abs_allocation_vertices (CLUTTER_ACTOR (clutter_text), verts);
2300 x_window = verts[0].x;
2301 y_window = verts[0].y;
2302
2303 x_temp = x - x_layout - x_window;
2304 y_temp = y - y_layout - y_window;
2305
2306 if (coords == ATK_XY_SCREEN)
2307 {
2308 _cally_actor_get_top_level_origin (CLUTTER_ACTOR (clutter_text), &x_toplevel,
2309 &y_toplevel);
2310 x_temp -= x_toplevel;
2311 y_temp -= y_toplevel;
2312 }
2313
2314 layout = clutter_text_get_layout (clutter_text);
2315 ret = pango_layout_xy_to_index (layout,
2316 x_temp * PANGO_SCALE,
2317 y_temp * PANGO_SCALE,
2318 &index, NULL);
2319
2320 if (!ret)
2321 {
2322 if (x_temp < 0 || y_temp < 0)
2323 index = 0;
2324 else
2325 index = -1;
2326 }
2327 return index;
2328 }
2329