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