Merge "custom eail widget implementation" into tizen
[platform/core/uifw/eail.git] / eail / eail_utils.c
1 /*
2  * Copyright (c) 2013 Samsung Electronics Co., Ltd.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public License
15  * along with this library; see the file COPYING.LIB.  If not, write to
16  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19
20 /**
21  * @file eail_utils.c
22  * @brief Implementation of Utility Functions
23  */
24
25 /* enabling beta API support for Eo parts*/
26 #define EFL_BETA_API_SUPPORT
27
28 #include <atk/atk.h>
29 #include <Elementary.h>
30 #include <Eina.h>
31 #include <Evas.h>
32
33 /* internal elm_widget api for listening EDJE parts */
34
35 #define ELM_INTERNAL_API_ARGESFSDFEFC
36 #include <elm_widget.h>
37
38 #include "eail_utils.h"
39 #include "eail_factory.h"
40 #include "eail_dynamic_content.h"
41 #include "eail_priv.h"
42
43 /**
44  * @param string base string to get substring from
45  * @param start_offset beginning offset
46  * @param end_offset end offset
47  *
48  * @returns newly allocated substring
49  */
50 gchar *
51 eail_get_substring(const gchar *string,
52                    gint         start_offset,
53                    gint         end_offset)
54 {
55    gchar *substring = NULL;
56    gint len = 0;
57    gint sub_len = 0;
58
59    if (!string) return NULL;
60
61    len = g_utf8_strlen(string, -1);
62
63    if ((start_offset <  0) ||
64       (start_offset > len - 1) ||
65       (end_offset   <  -1))
66     return NULL;
67    else if (end_offset == -1 ||
68            end_offset >= len - 1)
69      sub_len = len - start_offset + 1;
70    else
71      sub_len = end_offset - start_offset;
72
73    substring = g_malloc0(sub_len + 1);
74
75    return g_utf8_strncpy(substring, &string[start_offset], sub_len);
76 }
77
78 /**
79  * @param widget Evas_Object instance for getting state set
80  * @param state_set current state set taken from object's parent
81  *
82  * @returns AtkStateSet representing the given Evas_Object
83  */
84 AtkStateSet *
85 eail_evas_obj_ref_state_set(Evas_Object *widget, AtkStateSet *state_set)
86 {
87    if (!widget) {
88        atk_state_set_add_state(state_set, ATK_STATE_DEFUNCT);
89        return state_set;
90    }
91
92    if (!elm_object_disabled_get(widget)) {
93        atk_state_set_add_state(state_set, ATK_STATE_SENSITIVE);
94        atk_state_set_add_state(state_set, ATK_STATE_ENABLED);
95    }
96
97    if (evas_object_visible_get(widget)) {
98        int x, y, width, height;
99        int vp_x, vp_y, vp_width, vp_height;
100
101        atk_state_set_add_state(state_set, ATK_STATE_VISIBLE);
102
103        evas_object_geometry_get(widget, &x, &y, &width, &height);
104        evas_output_viewport_get(evas_object_evas_get(widget),
105                                 &vp_x, &vp_y, &vp_width, &vp_height);
106
107        if ((x + width) >= vp_x && (y + height) >= vp_y &&
108            (vp_x + vp_width) >= x && (vp_y + vp_height) >= y) {
109            atk_state_set_add_state(state_set, ATK_STATE_SHOWING);
110        }
111    }
112
113    if (elm_object_focus_allow_get(widget)) {
114        atk_state_set_add_state(state_set, ATK_STATE_FOCUSABLE);
115
116        if (elm_object_focus_get(widget)) {
117            atk_state_set_add_state(state_set, ATK_STATE_FOCUSED);
118        }
119    }
120
121    return state_set;
122 }
123
124 /**
125  * @param widget Evas_Object instance
126  * @return TRUE if grabbing focus was successfull, FALSE otherwise
127  */
128 gboolean
129 eail_evas_obj_grab_focus(Evas_Object *widget)
130 {
131    Ecore_Evas *ee = ecore_evas_ecore_evas_get(evas_object_evas_get(widget));
132
133    if (!widget || !elm_object_focus_allow_get(widget)) {
134          return FALSE;
135    }
136
137    ecore_evas_activate(ee);
138    elm_object_focus_set(widget, EINA_TRUE);
139
140    return TRUE;
141 }
142
143 /**
144  * @param widget Evas_Object instance to press on
145  * @param x x coordinate
146  * @param y y coordinate
147  */
148 void
149 eail_mouse_press_on_coords(Evas_Object *widget, int x, int y)
150 {
151    Evas* evas = NULL;
152    evas =  evas_object_evas_get(widget);
153
154    evas_event_feed_mouse_move(evas, x, y, 0, NULL);
155    evas_event_feed_mouse_down(evas, 1, EVAS_BUTTON_NONE, 0, NULL);
156 }
157
158 /**
159  * @param widget Evas_Object instance to press on
160  * @param x x coordinate
161  * @param y y coordinate
162  */
163 void
164 eail_mouse_release_on_coords(Evas_Object *widget, int x, int y)
165 {
166    Evas* evas = NULL;
167    evas =  evas_object_evas_get(widget);
168
169    evas_event_feed_mouse_move(evas, x, y, 0, NULL);
170    evas_event_feed_mouse_up(evas, 1, EVAS_BUTTON_NONE, 0, NULL);
171 }
172
173 /**
174  * @param widget Evas_Object instance to click on
175  * @param x x coordinate
176  * @param y y coordinate
177  */
178 void
179 eail_mouse_click_on_coords(Evas_Object *widget, int x, int y)
180 {
181    Evas* evas = NULL;
182    evas =  evas_object_evas_get(widget);
183
184    evas_event_feed_mouse_move(evas, x, y, 0, NULL);
185    evas_event_feed_mouse_down(evas, 1, EVAS_BUTTON_NONE, 0, NULL);
186    evas_event_feed_mouse_up(evas, 1, EVAS_BUTTON_NONE, 0, NULL);
187 }
188
189 /**
190  * @param widget Evas_Object instance to get values for
191  * @param [out] x x coordinate
192  * @param [out] y Y coordinate
193  */
194 void
195 eail_get_coords_widget_center(Evas_Object *widget, int *x, int *y)
196 {
197    int w, h;
198    evas_object_geometry_get(widget, x, y, &w, &h);
199
200    *x = *x + (w / 2);
201    *y = *y + (h / 2);
202 }
203
204 /**
205  * @param item Elm_Object_Item* instance
206  *
207  * @returns Eina_List * representing the list of raw Evas_Object*
208  * (not every returned Evas_Object is a widget - widget-only content
209  * is returned by eail_get_edje_parts_for_item function)
210  */
211 Eina_List *
212 eail_get_raw_evas_obj_list_from_item(Elm_Object_Item *item)
213 {
214    Evas_Object *edje = NULL;
215
216    edje = VIEW(item);
217    if (!edje)
218      {
219         DBG("Edje object for item not found. Returning empty list");\
220         return NULL;
221      }
222
223     return evas_object_smart_members_get(edje);
224 }
225
226 /**
227  * @param item Elm_Object_Item* instance
228  *
229  * @returns Evas_Object* representing the edje object related to item
230  */
231 Evas_Object *
232 eail_get_edje_obj_from_item(Elm_Object_Item *item)
233 {
234    Evas_Object *edje = NULL;
235
236    edje = VIEW(item);
237    if (!edje)
238       DBG("Edje object for item not found.");
239
240    return edje;
241 }
242
243 /**
244  *
245  * The returned list has to be freed when no longer needed but DO NOT
246  * FREE CONTENT STRINGS.
247  *
248  * @param item Elm_Object_Item * to get strings from
249  * @returns Eina_List representing the list of string that represent text
250  * content of item
251  */
252 Eina_List *
253 eail_item_get_content_strings(Elm_Object_Item *item)
254 {
255    Eina_List *strings_list = NULL;
256    Eina_List *edje_parts = NULL;
257    Evas_Object *part = NULL;
258    Eina_List *l;
259
260
261    edje_parts = eail_get_raw_evas_obj_list_from_item(item);
262    EINA_LIST_FOREACH(edje_parts, l, part)
263      {
264       const gchar *type_name = evas_object_type_get(part);
265
266       if (0 == strcmp(type_name, "text"))
267         {
268            const gchar *text = evas_object_text_text_get(part);
269
270            if (text)
271              strings_list = eina_list_append(strings_list, text);
272         }
273      }
274
275    return strings_list;
276 }
277
278 /**
279  * It does filtering inside and returs only parts that can be used later in
280  * eail factory (only widgets will be returned).
281  *
282  * @param item Elm_Object_Item instance to get objects from
283  *
284  * @returns an Eina_List filled with Evas_Object* objects representing content
285  * found inside item
286  */
287 Eina_List *
288 eail_get_edje_parts_for_item(Elm_Object_Item *item)
289 {
290    Eina_List *edje_parts = NULL;
291    Eina_List *usable_parts = NULL;
292    int i = 0;
293
294    edje_parts = eail_get_raw_evas_obj_list_from_item(item);
295    for (i = 0; i < eina_list_count(edje_parts); ++i)
296      {
297         Evas_Object *obj = eina_list_nth(edje_parts, i);
298
299         /* adding only parts that can be used by eail_factory later */
300         if (elm_object_widget_check(obj))
301           usable_parts = eina_list_append(usable_parts, obj);
302      }
303
304    eina_list_free(edje_parts);
305
306    return usable_parts;
307 }
308
309 /**
310  *
311  * @param widget Evas_Object instance
312  * @param type type of scroll action
313  *
314  * @returns TRUE if scroll action was successful, FALSE otherwise
315  */
316 gboolean
317 eail_handle_scroll(Evas_Object *widget,
318                    enum EAIL_SCROLL_TYPE type)
319 {
320    int x, y, w, h;
321
322    elm_scroller_region_get(widget, &x, &y, &w, &h);
323
324    if (EAIL_SCROLL_TYPE_UP == type)
325       y = y - h;
326    else if (EAIL_SCROLL_TYPE_DOWN == type)
327       y = y + h;
328    else if (EAIL_SCROLL_TYPE_RIGHT == type)
329       x = x + w;
330    else if (EAIL_SCROLL_TYPE_LEFT == type)
331       x = x - w;
332
333    elm_scroller_region_bring_in(widget, x, y, w, h);
334
335    return TRUE;
336 }
337
338 /**
339  *
340  * @param widget Evas_Object* instance
341  * @param data additional action data
342  * @returns TRUE if scroll action was successful, FALSE otherwise
343  */
344 gboolean
345 eail_action_scroll_up(Evas_Object *widget,
346                       void *data)
347 {
348    return eail_handle_scroll(widget, EAIL_SCROLL_TYPE_UP);
349 }
350
351 /**
352  *
353  * @param widget Evas_Object* instance
354  * @param data additional action data
355  *
356  * @returns TRUE if scroll action was successful, FALSE otherwise
357  */
358 gboolean
359 eail_action_scroll_down(Evas_Object *widget,
360                         void *data)
361 {
362    return eail_handle_scroll(widget, EAIL_SCROLL_TYPE_DOWN);
363 }
364
365 /**
366  *
367  * @param widget Evas_Object* instance
368  * @param data additional action data
369  *
370  * @returns TRUE if scroll action was successful, FALSE otherwise
371  */
372 gboolean
373 eail_action_scroll_left(Evas_Object *widget,
374                         void *data)
375 {
376    return eail_handle_scroll(widget, EAIL_SCROLL_TYPE_LEFT);
377 }
378
379 /**
380  *
381  * @param widget Evas_Object* instance
382  * @param data additional action data
383  *
384  * @returns TRUE if scroll action was successful, FALSE otherwise
385  */
386 gboolean
387 eail_action_scroll_right(Evas_Object *widget,
388                          void *data)
389 {
390    return eail_handle_scroll(widget, EAIL_SCROLL_TYPE_RIGHT);
391 }
392
393 /**
394  * @param atk_obj AtkObject instance that emits the signal
395  * @param signal_name name of signal
396  * @param object_type GType of object
397  */
398 void
399 eail_emit_atk_signal(AtkObject *atk_obj, const gchar *signal_name,
400                      GType object_type)
401 {
402    guint signal = g_signal_lookup (signal_name,object_type);
403    if (!signal)
404      {
405         ERR("No signal with name %s was found", signal_name);
406         return;
407      }
408
409    DBG("Raising %s signal", signal_name);
410    g_signal_emit (atk_obj, signal, 0);
411 }
412
413 /**
414  * @param added boolean used for marking if child is added. TRUE if child was
415  * added, FALSE when child was removed
416  * @param atk_obj AtkObject instance that emits the signal
417  * @param child_number index number of changed child
418  */
419 void
420 eail_emit_children_changed(gboolean added, AtkObject *atk_obj, gint child_number)
421 {
422
423    DBG("Emitting child-changed for index %d. ADDED: %d", child_number, added);
424
425    if (added)
426      g_signal_emit_by_name
427                  (atk_obj, "children_changed::add", child_number, NULL, NULL);
428    else
429      g_signal_emit_by_name
430                (atk_obj, "children_changed::remove", child_number, NULL, NULL);
431
432 }
433
434 /**
435  * @param added boolean used for marking if child is added. TRUE if child was
436  * added, FALSE when child was removed
437  * @param atk_obj an AtkObject that emits the signal
438  * @param changed_obj pointer to object that has been added/removed
439  */
440 void
441 eail_emit_children_changed_obj(gboolean added,
442                                AtkObject *atk_obj,
443                                AtkObject *changed_obj)
444 {
445
446    DBG("Emitting child-changed for obj. ADDED: %d", added);
447
448    if (added)
449      g_signal_emit_by_name
450                  (atk_obj, "children_changed::add", NULL, changed_obj, NULL);
451    else
452      g_signal_emit_by_name
453                (atk_obj, "children_changed::remove", NULL, changed_obj, NULL);
454
455 }
456 /**
457  * @brief Handles 'selected' state changes for item
458  *
459  * @param item Elm_Object instance
460  * @param selected value of 'selected' state
461  * @param parent AtkObject that holds item inside
462  * @param role role of item
463  */
464 static void
465 _eail_handle_selected_for_item(Elm_Object_Item *item,
466                                gboolean selected,
467                                AtkObject *parent,
468                                AtkRole role)
469 {
470    AtkObject *atk_item_obj = NULL;
471    if (!item && !parent)
472      {
473         ERR("Wrong parameters passed. Ignoring event");
474         return;
475      }
476
477    atk_item_obj = eail_factory_get_item_atk_obj(item, role, parent);
478    if (!atk_item_obj)
479      {
480         DBG("Couldn't find factory obj for item. Ignoring 'selected' event");
481         return;
482      }
483
484    atk_object_notify_state_change(atk_item_obj, ATK_STATE_SELECTED, selected);
485    g_signal_emit_by_name (parent, "selection_changed");
486 }
487
488 /**
489  * @brief Function that calls apropriate handling for selected event
490  *
491  * @param data passed to callback
492  * @param obj Evas_Object that raised event
493  * @param event_info additional event info
494  * @param selected selected state
495  */
496 static void
497 _eail_list_item_do_handle_selected_event(void *data,
498                                          Evas_Object *obj,
499                                          void *event_info,
500                                          gboolean selected)
501 {
502    Elm_Object_Item *obj_item = (Elm_Object_Item*)event_info;
503    if (!obj_item)
504      {
505         DBG("obj_item not found in event_info. Ignoring event..");
506         return;
507      }
508
509    eail_notify_child_focus_changes();
510    _eail_handle_selected_for_item
511                      (obj_item, selected, ATK_OBJECT(data), ATK_ROLE_LIST_ITEM);
512 }
513
514 /**
515  *
516  * @param data passed to callback
517  * @param obj Evas_Object that raised event
518  * @param event_info additional event info
519  */
520 void
521 eail_list_item_handle_selected_event(void *data,
522                                       Evas_Object *obj,
523                                       void *event_info)
524 {
525    _eail_list_item_do_handle_selected_event(data, obj, event_info, TRUE);
526 }
527
528 /**
529  *
530  * @param data passed to callback
531  * @param obj Evas_Object that raised event
532  * @param event_info additional event info
533  */
534 void
535 eail_list_item_handle_unselected_event(void *data,
536                                        Evas_Object *obj,
537                                        void *event_info)
538 {
539    _eail_list_item_do_handle_selected_event(data, obj, event_info, FALSE);
540 }
541
542 /**
543  * @brief Notifies objects with given role about possible hierarchy changes
544  *
545  * @param data an AtkRole
546  *
547  * @returns always FALSE
548  */
549 static gboolean
550 _notifiy_content_holders_by_type(gpointer data)
551 {
552    Eina_List *objs = NULL, *l = NULL;
553    AtkObject *atk_obj = NULL;
554    AtkRole role = ATK_ROLE_INVALID;
555
556    role = (AtkRole)data;
557
558    if (ATK_ROLE_APPLICATION == role)
559      {
560          atk_obj = atk_get_root();
561          if (atk_obj && EAIL_IS_DYNAMIC_CONTENT(atk_obj))
562             eail_dynamic_content_update_hierarchy(EAIL_DYNAMIC_CONTENT(atk_obj));
563
564          return FALSE;
565      }
566    else
567      {
568         objs = eail_factory_find_objects_with_role(role);
569         EINA_LIST_FOREACH(objs, l, atk_obj)
570           {
571             if (EAIL_IS_DYNAMIC_CONTENT(atk_obj))
572               {
573                    eail_dynamic_content_update_hierarchy
574                                                (EAIL_DYNAMIC_CONTENT(atk_obj));
575               }
576           }
577         eina_list_free(objs);
578      }
579
580    return FALSE;
581 }
582
583 /**
584  * @brief Sends notification about possible hierarchy changes to objects
585  * with dynamic content interface
586  *
587  * @param delay value in miliseconds, objects will be notified after this
588  * timeout
589  */
590 static void
591 _eail_notify_focus_listeners_delayed(guint delay)
592 {
593    g_timeout_add
594       (delay, _notifiy_content_holders_by_type, (gpointer)ATK_ROLE_APPLICATION);
595    g_timeout_add
596       (delay, _notifiy_content_holders_by_type, (gpointer)ATK_ROLE_WINDOW);
597    g_timeout_add
598       (delay, _notifiy_content_holders_by_type, (gpointer)ATK_ROLE_FILLER);
599 }
600
601 void
602 eail_notify_child_focus_changes(void)
603 {
604    _eail_notify_focus_listeners_delayed(0);
605    /* sending delayed notifications to objects after hierarchy has
606     * been fully established*/
607    _eail_notify_focus_listeners_delayed(1600);
608 }
609
610 /**
611  * @brief Returns the position that is obtained by adding count to the
612  * given offset
613  *
614  * Count may be positive or negative.
615  *
616  * @param textblock Evas textblock
617  * @param offset character offset
618  * @param count number of characters to move from offset
619  * @returns integer representing the new position
620  */
621 static gint
622 _eail_move_chars(const Evas_Object *textblock,
623                  gint offset,
624                  gint count)
625 {
626    Evas_Textblock_Cursor *cur;
627    gint dir;
628    gboolean res;
629
630    dir = count > 0 ? 1 : -1;
631    cur = evas_object_textblock_cursor_new(textblock);
632    evas_textblock_cursor_pos_set(cur, offset);
633
634    while(count)
635      {
636         res = dir > 0 ? evas_textblock_cursor_char_next(cur) :
637             evas_textblock_cursor_char_prev(cur);
638         if (!res) break;
639
640         count -= dir;
641      }
642
643    offset = evas_textblock_cursor_pos_get(cur);
644    evas_textblock_cursor_free(cur);
645
646    return offset;
647 }
648
649 /**
650  * @brief Gets the utf8 character at offset
651  *
652  * @param textblock Evas textblock
653  * @param offset character offset
654  * @returns char representing the utf8 character
655  */
656 static gunichar
657 _eail_get_unichar_at_offset(const Evas_Object *textblock,
658                             int offset)
659 {
660    Evas_Textblock_Cursor *cur;
661    gchar *s;
662    gunichar c;
663
664    cur = evas_object_textblock_cursor_new(textblock);
665    evas_textblock_cursor_pos_set(cur, offset);
666    s = evas_textblock_cursor_content_get(cur);
667    c = g_utf8_get_char(s);
668
669    evas_textblock_cursor_free(cur);
670    g_free(s);
671
672    return c;
673 }
674
675 /**
676  * @brief Checks whether the character at offset in textblock is a word's start
677  *
678  * @param textblock Evas textblock
679  * @param offset character offset
680  * @returns TRUE on success, FALSE otherwise
681  */
682 static gboolean
683 _eail_is_word_start(const Evas_Object *textblock,
684                     gint offset)
685 {
686    /* first character in a word */
687    Evas_Textblock_Cursor *cur = NULL;
688    cur = evas_object_textblock_cursor_new(textblock);
689    evas_textblock_cursor_pos_set(cur, offset);
690    evas_textblock_cursor_word_start(cur);
691    gint pos = evas_textblock_cursor_pos_get(cur);
692    evas_textblock_cursor_free(cur);
693
694    return pos == offset;
695 }
696
697 /**
698  * @brief Checks whether the character at offset in textblock is a word's end
699  *
700  * @param textblock Evas textblock
701  * @param offset character offset
702  * @return TRUE on success, FALSE otherwise
703  */
704 static gboolean
705 _eail_is_word_end(const Evas_Object *textblock,
706                   gint offset)
707 {
708    /* is first non-word char after a word */
709    Evas_Textblock_Cursor *cur = NULL;
710    cur = evas_object_textblock_cursor_new(textblock);
711    evas_textblock_cursor_pos_set(cur, offset - 2);
712    evas_textblock_cursor_word_end(cur);
713    gint pos = evas_textblock_cursor_pos_get(cur);
714    evas_textblock_cursor_free(cur);
715
716    return (pos + 1 == offset);
717 }
718
719 /**
720  * @brief Checks whether the character at offset in textblock is inside a word
721  *
722  * @param textblock Evas textblock
723  * @param offset character offset
724  * @returns TRUE on success, FALSE otherwise
725  */
726 static gboolean
727 _eail_is_inside_word(const Evas_Object *textblock,
728                      gint offset)
729 {
730    Evas_Textblock_Cursor *cur = NULL;
731    cur = evas_object_textblock_cursor_new(textblock);
732    evas_textblock_cursor_pos_set(cur, offset);
733    evas_textblock_cursor_word_start(cur);
734    gint pos_start = evas_textblock_cursor_pos_get(cur);
735    evas_textblock_cursor_char_prev(cur);
736    evas_textblock_cursor_word_end(cur);
737    gint pos_end = evas_textblock_cursor_pos_get(cur);
738    evas_textblock_cursor_free(cur);
739
740    return (pos_start <= offset && pos_end >= offset);
741 }
742
743 /**
744  * @brief Gets the texblock length
745  *
746  * @param textblock Evas textblock
747  * @returns integer representing the textblock length
748  */
749 static gint
750 _eail_get_len(const Evas_Object *textblock)
751 {
752    Evas_Textblock_Cursor *cur;
753    int ctr = 0;
754
755    cur = evas_object_textblock_cursor_new(textblock);
756    while (evas_textblock_cursor_char_next(cur))
757      ++ctr;
758
759    evas_textblock_cursor_free(cur);
760
761    return ctr;
762 }
763
764 /**
765  * @brief Returns the position that is count words from the given offset
766  *
767  * Count may  be positive or negative. If count is positive, the returned
768  * position will be a word end, otherwise it will be a word start.
769  *
770  * @param textblock Evas textblock
771  * @param offset a character offset
772  * @param count the number of words to move from offset
773  * @returns integer representing the new position
774  */
775 static gint
776 _eail_move_words(const Evas_Object *textblock,
777                  gint offset,
778                  gint count)
779 {
780    gint len = _eail_get_len(textblock);
781
782    while (count > 0 && offset < len)
783      {
784         do
785           offset++;
786         while (offset < len && !_eail_is_word_end(textblock, offset));
787
788         count--;
789      }
790    while (count < 0 && offset > 0)
791      {
792         do
793           offset--;
794         while (offset > 0 && !_eail_is_word_start(textblock, offset));
795
796         count++;
797      }
798
799    return offset;
800 }
801
802 /**
803  * @brief Checks whether the character is sentence break
804  *
805  * @param c character
806  * @returns TRUE on success, FALSE otherwise
807  */
808 static gboolean
809 _eail_is_sentence_break(gunichar c)
810 {
811    if (c == '.' || c == '?' || c == '!') return TRUE;
812
813    return FALSE;
814 }
815
816 /**
817  * @brief Checks whether the character at offset in textblock is a sentence's start
818  *
819  * @param textblock Evas textblock
820  * @param offset character offset
821  * @returns TRUE on success, FALSE otherwise
822  */
823 static gboolean
824 _eail_is_sentence_start(const Evas_Object *textblock,
825                         gint offset)
826 {
827    gunichar c;
828    gint len = _eail_get_len(textblock);
829
830    if (offset > len -1 || offset < 0) return FALSE;
831
832    if (0 == offset) return TRUE;
833
834    c = _eail_get_unichar_at_offset(textblock, offset - 1);
835    if (_eail_is_sentence_break(c)) return TRUE;
836
837    return FALSE;
838 }
839
840 /**
841  * @brief Checks whether the character at offset in textblock is a sentence's end
842  *
843  * @param textblock Evas textblock
844  * @param offset character offset
845  * @return TRUE on success, FALSE otherwise
846  */
847 static gboolean
848 _eail_is_sentence_end(const Evas_Object *textblock,
849                       gint offset)
850 {
851    gunichar c;
852    gint len = _eail_get_len(textblock);
853
854    if (offset > len -1 || offset < 0) return FALSE;
855
856    if (offset == len -1) return TRUE;
857
858    c = _eail_get_unichar_at_offset(textblock, offset);
859    if (_eail_is_sentence_break(c)) return TRUE;
860
861    return FALSE;
862 }
863
864 /**
865  * @brief Checks whether the character at offset in textblock is inside a senetence
866  *
867  * @param textblock Evas textblock
868  * @param offset character offset
869  * @returns TRUE on success, FALSE otherwise
870  */
871 static gboolean
872 _eail_is_inside_sentence(const Evas_Object *textblock,
873                          gint offset)
874 {
875    gunichar c;
876    gint len = _eail_get_len(textblock);
877
878    if ((offset > len -1 || offset < 0)) return FALSE;
879
880    c = _eail_get_unichar_at_offset(textblock, offset);
881
882    if (!_eail_is_sentence_break(c)) return TRUE;
883
884    return FALSE;
885 }
886
887 /**
888  * @brief Returns the position that is count sentences from the given offset
889  *
890  * Count may  be positive or negative. If count is positive, the returned
891  * position will be a sentence end, otherwise it will be a sentence start.
892  *
893  * @param textblock Evas textblock
894  * @param offset a character offset
895  * @param count the number of words to move from offset
896  * @returns integer representing the new position
897  */
898 static gint
899 _eail_move_sentences(const Evas_Object *textblock,
900                       gint offset,
901                       gint count)
902 {
903    gint len = _eail_get_len(textblock);
904
905    while (count > 0 && offset < len)
906      {
907         do
908           offset++;
909         while (offset < len && !_eail_is_sentence_end(textblock, offset));
910
911         count--;
912      }
913    while (count < 0 && offset > 0)
914      {
915         do
916           offset--;
917         while (offset > 0 && !_eail_is_sentence_start(textblock, offset));
918
919         count++;
920      }
921
922    return offset;
923 }
924
925 /**
926  * @brief Checks whether the character at offset in textblock is a line's start
927  *
928  * @param textblock Evas textblock
929  * @param offset character offset
930  * @returns TRUE on success, FALSE otherwise
931  */
932 static gboolean
933 _eail_is_line_start(const Evas_Object *textblock,
934                     gint offset)
935 {
936    Evas_Textblock_Cursor *cur = NULL;
937    cur = evas_object_textblock_cursor_new(textblock);
938    evas_textblock_cursor_pos_set(cur, offset);
939    evas_textblock_cursor_line_char_first(cur);
940    gint pos = evas_textblock_cursor_pos_get(cur);
941    evas_textblock_cursor_free(cur);
942
943    return pos == offset;
944 }
945
946 /**
947  * @brief Checks whether the character at offset in textblock is a line's end
948  *
949  * @param textblock Evas textblock
950  * @param offset character offset
951  * @return TRUE on success, FALSE otherwise
952  */
953 static gboolean
954 _eail_is_line_end(const Evas_Object *textblock,
955                   gint offset)
956 {
957    /* is first non-word char after a word */
958    Evas_Textblock_Cursor *cur = NULL;
959    cur = evas_object_textblock_cursor_new(textblock);
960    evas_textblock_cursor_pos_set(cur, offset);
961    evas_textblock_cursor_line_char_last(cur);
962    gint pos = evas_textblock_cursor_pos_get(cur);
963    evas_textblock_cursor_free(cur);
964
965    return (pos == offset);
966 }
967
968 /**
969  * @brief Checks whether the character at offset in textblock is inside a line
970  *
971  * @param textblock Evas textblock
972  * @param offset character offset
973  * @returns TRUE on success, FALSE otherwise
974  */
975 static gboolean
976 _eail_is_inside_line(const Evas_Object *textblock,
977                      gint offset)
978 {
979    Evas_Textblock_Cursor *cur = NULL;
980    cur = evas_object_textblock_cursor_new(textblock);
981    evas_textblock_cursor_pos_set(cur, offset);
982    evas_textblock_cursor_line_char_last(cur);
983    gint pos_end = evas_textblock_cursor_pos_get(cur);
984    evas_textblock_cursor_free(cur);
985    return pos_end != offset;
986 }
987
988 /**
989  * @brief Returns the position that is count lines from the given offset
990  *
991  * Count may  be positive or negative. If count is positive, the returned
992  * position will be a line end, otherwise it will be a line start.
993  *
994  * @param textblock Evas textblock
995  * @param offset a character offset
996  * @param count the number of words to move from offset
997  * @returns integer representing the new position
998  */
999 static gint
1000 _eail_move_lines(const Evas_Object *textblock,
1001                  gint offset,
1002                  gint count)
1003 {
1004    gint len = _eail_get_len(textblock);
1005
1006    while (count > 0 && offset < len)
1007      {
1008         do
1009           offset++;
1010         while (offset < len && !_eail_is_line_end(textblock, offset));
1011
1012         count--;
1013      }
1014    while (count < 0 && offset > 0)
1015      {
1016         do
1017           offset--;
1018         while (offset > 0 && !_eail_is_line_start(textblock, offset));
1019
1020         count++;
1021      }
1022
1023    return offset;
1024 }
1025
1026 /**
1027  * @brief Gets a slice of the text from textblock after offset
1028  *
1029  * Use g_free() to free the returned string.
1030  *
1031  * @param textblock Evas textblock
1032  * @param offset character offset
1033  * @param boundary_type AtkTextBoundary instance
1034  * @param [out] start_offset start position of the returned text
1035  * @param [out] end_offset end position of the returned text
1036  * @returns newly allocated string containg a slice of text from textblock
1037  */
1038 gchar *
1039 eail_get_text_after(const Evas_Object *textblock,
1040                     gint offset,
1041                     AtkTextBoundary boundary_type,
1042                     gint *start_offset,
1043                     gint *end_offset)
1044 {
1045    const gchar *text;
1046    int len;
1047    gint start, end;
1048
1049    text = evas_textblock_text_markup_to_utf8(
1050        textblock, evas_object_textblock_text_markup_get(textblock));
1051    if (!text)
1052      {
1053         *start_offset = 0;
1054         *end_offset = 0;
1055         return g_strdup("");
1056      }
1057
1058    start = offset;
1059    end = offset;
1060    len = _eail_get_len(textblock);
1061
1062    switch (boundary_type)
1063      {
1064        case ATK_TEXT_BOUNDARY_CHAR:
1065            start = _eail_move_chars(textblock, start, 1);
1066            end = start;
1067            end = _eail_move_chars(textblock, end, 1);
1068            break;
1069
1070        case ATK_TEXT_BOUNDARY_WORD_START:
1071            if (_eail_is_inside_word(textblock, end))
1072              end = _eail_move_words(textblock, end, 1);
1073            while (!_eail_is_word_start(textblock, end) && end < len)
1074              end = _eail_move_chars(textblock, end, 1);
1075            start = end;
1076            if (end < len)
1077              {
1078                 end = _eail_move_words(textblock, end, 1);
1079                 while (!_eail_is_word_start(textblock, end) && end < len)
1080                   end = _eail_move_chars(textblock, end, 1);
1081              }
1082            break;
1083
1084        case ATK_TEXT_BOUNDARY_WORD_END:
1085            end = _eail_move_words(textblock, end, 1);
1086            start = end;
1087            if (end < len)
1088              end = _eail_move_words(textblock, end, 1);
1089            break;
1090
1091        case ATK_TEXT_BOUNDARY_SENTENCE_START:
1092            if (_eail_is_inside_sentence(textblock, end))
1093              end = _eail_move_sentences(textblock, end, 1);
1094            while (!_eail_is_sentence_start(textblock, end) && end < len)
1095              end = _eail_move_chars(textblock, end, 1);
1096            start = end;
1097            if (end < len)
1098              {
1099                 end = _eail_move_sentences(textblock, end, 1);
1100                 while (!_eail_is_sentence_start(textblock, end) && end < len)
1101                   end = _eail_move_chars(textblock, end, 1);
1102              }
1103            break;
1104
1105        case ATK_TEXT_BOUNDARY_SENTENCE_END:
1106            end = _eail_move_sentences(textblock, end, 1);
1107            start = end;
1108            if (end < len)
1109              end = _eail_move_sentences(textblock, end, 1);
1110            break;
1111
1112        case ATK_TEXT_BOUNDARY_LINE_START:
1113            if (_eail_is_inside_line(textblock, end))
1114              end = _eail_move_lines(textblock, end, 1);
1115            while (!_eail_is_line_start(textblock, end) && end < len)
1116              end = _eail_move_chars(textblock, end, 1);
1117            start = end;
1118            if (end < len)
1119              {
1120                 end = _eail_move_lines(textblock, end, 1);
1121                 while (!_eail_is_line_start(textblock, end) && end < len)
1122                   end = _eail_move_chars(textblock, end, 1);
1123              }
1124            break;
1125
1126        case ATK_TEXT_BOUNDARY_LINE_END:
1127            end = _eail_move_lines(textblock, end, 1);
1128            start = end;
1129            if (end < len)
1130              end = _eail_move_lines(textblock, end, 1);
1131            break;
1132      }
1133
1134    *start_offset = start;
1135    *end_offset = end;
1136    g_assert(start <= end);
1137
1138    return g_utf8_substring(text, start, end);
1139 }
1140
1141 /**
1142  * @brief Gets a slice of the text from textblock at offset
1143  *
1144  * Use g_free() to free the returned string.
1145  *
1146  * @param textblock Evas textblock
1147  * @param offset character offset
1148  * @param boundary_type AtkTextBoundary instance
1149  * @param [out] start_offset start position of the returned text
1150  * @param [out] end_offset end position of the return text
1151  * @returns newly allocated string containing a slice of text from textblock
1152  */
1153 gchar *
1154 eail_get_text_at(const Evas_Object *textblock,
1155                  gint offset,
1156                  AtkTextBoundary boundary_type,
1157                  gint *start_offset,
1158                  gint *end_offset)
1159 {
1160    const gchar *text;
1161    int len;
1162    gint start, end;
1163
1164    text = evas_textblock_text_markup_to_utf8(
1165        textblock, evas_object_textblock_text_markup_get(textblock));
1166    if (!text)
1167      {
1168         *start_offset = 0;
1169         *end_offset = 0;
1170         return g_strdup("");
1171      }
1172
1173    start = offset;
1174    end = offset;
1175    len = _eail_get_len(textblock);
1176
1177    switch (boundary_type)
1178      {
1179        case ATK_TEXT_BOUNDARY_CHAR:
1180            end = _eail_move_chars(textblock, end, 1);
1181            break;
1182
1183        case ATK_TEXT_BOUNDARY_WORD_START:
1184            if (!_eail_is_word_start(textblock, start))
1185              start = _eail_move_words(textblock, start, -1);
1186            if (_eail_is_inside_word(textblock, end))
1187              end = _eail_move_words(textblock, end, 1);
1188            while (!_eail_is_word_start(textblock, end) && end < len)
1189              end = _eail_move_chars(textblock, end, 1);
1190            break;
1191
1192        case ATK_TEXT_BOUNDARY_WORD_END:
1193            if (_eail_is_inside_word(textblock, start) &&
1194                !_eail_is_word_start(textblock, start))
1195              start = _eail_move_words(textblock, start, -1);
1196            while (!_eail_is_word_end(textblock, start) && start > 0)
1197              start = _eail_move_chars(textblock, start, -1);
1198            end = _eail_move_words(textblock, end, 1);
1199            break;
1200
1201        case ATK_TEXT_BOUNDARY_SENTENCE_START:
1202            if (!_eail_is_sentence_start(textblock, start))
1203              start = _eail_move_sentences(textblock, start, -1);
1204            if (_eail_is_inside_sentence(textblock, end))
1205              end = _eail_move_sentences(textblock, end, 1);
1206            while (!_eail_is_sentence_start(textblock, end) && end < len)
1207              end = _eail_move_chars(textblock, end, 1);
1208            break;
1209
1210        case ATK_TEXT_BOUNDARY_SENTENCE_END:
1211            if (_eail_is_inside_sentence(textblock, start) &&
1212                !_eail_is_sentence_start(textblock, start))
1213              start = _eail_move_sentences(textblock, start, -1);
1214            while (!_eail_is_sentence_end(textblock, start) && start > 0)
1215              start = _eail_move_chars(textblock, start, -1);
1216            end = _eail_move_sentences(textblock, end, 1);
1217            break;
1218
1219        case ATK_TEXT_BOUNDARY_LINE_START:
1220            if (!_eail_is_line_start(textblock, start))
1221              start = _eail_move_lines(textblock, start, -1);
1222            if (_eail_is_inside_line(textblock, end))
1223              end = _eail_move_lines(textblock, end, 1);
1224            while (!_eail_is_line_start(textblock, end) && end < len)
1225              end = _eail_move_chars(textblock, end, 1);
1226            break;
1227
1228        case ATK_TEXT_BOUNDARY_LINE_END:
1229            if (_eail_is_inside_line(textblock, start) &&
1230                !_eail_is_line_start(textblock, start))
1231              start = _eail_move_lines(textblock, start, -1);
1232            while (!_eail_is_line_end(textblock, start) && start > 0)
1233              start = _eail_move_chars(textblock, start, -1);
1234            end = _eail_move_lines(textblock, end, 1);
1235            break;
1236      }
1237
1238    *start_offset = start;
1239    *end_offset = end;
1240    g_assert(start <= end);
1241
1242    return g_utf8_substring(text, start, end);
1243 }
1244
1245 /**
1246  * @brief Gets a slice of the text from textblock before offset
1247  *
1248  * Use g_free() to free the returned string.
1249  *
1250  * @param textblock Evas textblock
1251  * @param offset character offset
1252  * @param boundary_type AtkTextBoundary instance
1253  * @param [out] start_offset start position of the returned text
1254  * @param [out] end_offset end position of the returned text
1255  * @returns newly allocated string containing a slice of text from textblock
1256  */
1257 gchar *
1258 eail_get_text_before(const Evas_Object *textblock,
1259                      gint offset,
1260                      AtkTextBoundary boundary_type,
1261                      gint *start_offset,
1262                      gint *end_offset)
1263 {
1264    const gchar *text;
1265    gint start, end;
1266
1267    text = evas_textblock_text_markup_to_utf8(
1268        textblock, evas_object_textblock_text_markup_get(textblock));
1269    if (!text)
1270      {
1271         *start_offset = 0;
1272         *end_offset = 0;
1273         return g_strdup("");
1274      }
1275
1276    start = offset;
1277    end = offset;
1278
1279    switch (boundary_type)
1280      {
1281        case ATK_TEXT_BOUNDARY_CHAR:
1282            start = _eail_move_chars(textblock, start, -1);
1283            break;
1284
1285        case ATK_TEXT_BOUNDARY_WORD_START:
1286            if (!_eail_is_word_start(textblock, start))
1287              start = _eail_move_words(textblock, start, -1);
1288            end = start;
1289            start = _eail_move_words(textblock, start, -1);
1290            break;
1291
1292        case ATK_TEXT_BOUNDARY_WORD_END:
1293            if (_eail_is_inside_word(textblock, start) &&
1294                !_eail_is_word_start(textblock, start))
1295              start = _eail_move_words(textblock, start, -1);
1296            while (!_eail_is_word_end(textblock, start) && start > 0)
1297              start = _eail_move_chars(textblock, start, -1);
1298            end = start;
1299            start = _eail_move_words(textblock, start, -1);
1300            while (!_eail_is_word_end(textblock, start) && start > 0)
1301              start = _eail_move_chars(textblock, start, -1);
1302            break;
1303
1304        case ATK_TEXT_BOUNDARY_SENTENCE_START:
1305            if (!_eail_is_sentence_start(textblock, start))
1306              start = _eail_move_sentences(textblock, start, -1);
1307            end = start;
1308            start = _eail_move_sentences(textblock, start, -1);
1309            break;
1310
1311        case ATK_TEXT_BOUNDARY_SENTENCE_END:
1312            if (_eail_is_inside_sentence(textblock, start) &&
1313                !_eail_is_sentence_start(textblock, start))
1314              start = _eail_move_sentences(textblock, start, -1);
1315            while (!_eail_is_sentence_end(textblock, start) && start > 0)
1316              start = _eail_move_chars(textblock, start, -1);
1317            end = start;
1318            start = _eail_move_sentences(textblock, start, -1);
1319            while (!_eail_is_sentence_end(textblock, start) && start > 0)
1320              start = _eail_move_chars(textblock, start, -1);
1321            break;
1322
1323        case ATK_TEXT_BOUNDARY_LINE_START:
1324            if (!_eail_is_line_start(textblock, start))
1325              start = _eail_move_lines(textblock, start, -1);
1326            end = start;
1327            start = _eail_move_lines(textblock, start, -1);
1328            break;
1329
1330        case ATK_TEXT_BOUNDARY_LINE_END:
1331            if (_eail_is_inside_line(textblock, start) &&
1332                !_eail_is_line_start(textblock, start))
1333              start = _eail_move_lines(textblock, start, -1);
1334            while (!_eail_is_line_end(textblock, start) && start > 0)
1335              start = _eail_move_chars(textblock, start, -1);
1336            end = start;
1337            start = _eail_move_lines(textblock, start, -1);
1338            while (!_eail_is_line_end(textblock, start) && start > 0)
1339              start = _eail_move_chars(textblock, start, -1);
1340            break;
1341
1342      }
1343
1344    *start_offset = start;
1345    *end_offset = end;
1346    g_assert(start <= end);
1347
1348    return g_utf8_substring(text, start, end);
1349 }
1350
1351 /*
1352  * @brief Adds attribute to attribute set
1353  *
1354  * @param attrib_set AtkAttributeSet to add the attribute to
1355  * @param attr AtkTextAttrribute to be added
1356  * @param value attribute value
1357  *
1358  * Creates an AtkAttribute from attr and value, and adds it
1359  * to attrib_set.
1360  *
1361  * @returns AtkAttributeSet containing set with added attribute
1362  **/
1363 AtkAttributeSet*
1364 eail_utils_text_add_attribute(AtkAttributeSet *attrib_set,
1365                               AtkTextAttribute attr,
1366                               const gchar     *value)
1367 {
1368    AtkAttributeSet *return_set;
1369    AtkAttribute *at = g_malloc (sizeof (AtkAttribute));
1370    at->name = g_strdup (atk_text_attribute_get_name (attr));
1371    at->value = g_strdup(value);
1372    return_set = g_slist_prepend(attrib_set, at);
1373    return return_set;
1374 }