mobile-lua: add additional LABELLED_BY relation
[profile/tv/apps/native/screen-reader.git] / src / flat_navi.c
1 /*
2  * Copyright (c) 2015 Samsung Electronics Co., Ltd. All rights reserved.
3  *
4  * Licensed under the Flora License, Version 1.1 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://floralicense.org/license/
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 #include "flat_navi.h"
18 #include "logger.h"
19
20 struct _FlatNaviContext {
21         AtspiAccessible *root;
22         AtspiAccessible *current;
23         AtspiAccessible *first;
24         AtspiAccessible *last;
25 };
26
27 static const AtspiStateType required_states[] = {
28         ATSPI_STATE_SHOWING,
29         ATSPI_STATE_VISIBLE,
30         ATSPI_STATE_FOCUSABLE,
31         ATSPI_STATE_LAST_DEFINED
32 };
33
34 static const AtspiRole interesting_roles[] = {
35         ATSPI_ROLE_CALENDAR,
36         ATSPI_ROLE_CHECK_BOX,
37         ATSPI_ROLE_COLOR_CHOOSER,
38         ATSPI_ROLE_COMBO_BOX,
39         ATSPI_ROLE_DATE_EDITOR,
40         ATSPI_ROLE_DIALOG,
41         ATSPI_ROLE_FILE_CHOOSER,
42         ATSPI_ROLE_FILLER,
43         ATSPI_ROLE_FONT_CHOOSER,
44         ATSPI_ROLE_GLASS_PANE,
45         ATSPI_ROLE_HEADER,
46         ATSPI_ROLE_HEADING,
47         ATSPI_ROLE_ICON,
48         ATSPI_ROLE_ENTRY,
49         ATSPI_ROLE_LABEL,
50         ATSPI_ROLE_LINK,
51         ATSPI_ROLE_LIST_ITEM,
52         ATSPI_ROLE_MENU_ITEM,
53         ATSPI_ROLE_PANEL,
54         ATSPI_ROLE_PARAGRAPH,
55         ATSPI_ROLE_PASSWORD_TEXT,
56         ATSPI_ROLE_POPUP_MENU,
57         ATSPI_ROLE_PUSH_BUTTON,
58         ATSPI_ROLE_PROGRESS_BAR,
59         ATSPI_ROLE_RADIO_BUTTON,
60         ATSPI_ROLE_RADIO_MENU_ITEM,
61         ATSPI_ROLE_SLIDER,
62         ATSPI_ROLE_SPIN_BUTTON,
63         ATSPI_ROLE_TABLE_CELL,
64         ATSPI_ROLE_TEXT,
65         ATSPI_ROLE_TOGGLE_BUTTON,
66         ATSPI_ROLE_TOOL_TIP,
67         ATSPI_ROLE_TREE_ITEM,
68         ATSPI_ROLE_LAST_DEFINED
69 };
70
71 static Eina_Bool _has_escape_action(AtspiAccessible * obj)
72 {
73         Eina_Bool ret = EINA_FALSE;
74
75         AtspiAction *action = NULL;
76
77         action = atspi_accessible_get_action_iface(obj);
78         if (action) {
79                 int i = 0;
80                 for (; i < atspi_action_get_n_actions(action, NULL); i++) {
81                         gchar *action_name = atspi_action_get_action_name(action, i, NULL);
82                         Eina_Bool equal = !strcmp(action_name, "escape");
83                         g_free(action_name);
84                         if (equal) {
85                                 ret = EINA_TRUE;
86                                 break;
87                         }
88                 }
89                 g_object_unref(action);
90         }
91         DEBUG("Obj %s %s escape action", atspi_accessible_get_role_name(obj, NULL), ret ? "has" : "doesn't have");
92         return ret;
93 }
94
95 static Eina_Bool _is_collapsed(AtspiStateSet * ss)
96 {
97         if (!ss)
98                 return EINA_FALSE;
99
100         Eina_Bool ret = EINA_FALSE;
101         if (atspi_state_set_contains(ss, ATSPI_STATE_EXPANDABLE) && !atspi_state_set_contains(ss, ATSPI_STATE_EXPANDED))
102                 ret = EINA_TRUE;
103
104         return ret;
105 }
106
107 static Eina_Bool _object_is_item(AtspiAccessible * obj)
108 {
109         if (!obj)
110                 return EINA_FALSE;
111
112         Eina_Bool ret = EINA_FALSE;
113         AtspiRole role = atspi_accessible_get_role(obj, NULL);
114         if (role == ATSPI_ROLE_LIST_ITEM || role == ATSPI_ROLE_MENU_ITEM)
115                 ret = EINA_TRUE;
116         DEBUG("IS ITEM %d", ret);
117         return ret;
118 }
119
120 static AtspiAccessible *_get_object_in_relation(AtspiAccessible * source, AtspiRelationType search_type)
121 {
122         GArray *relations;
123         AtspiAccessible *ret = NULL;
124         AtspiRelation *relation;
125         AtspiRelationType type;
126         int i;
127         if (source) {
128                 DEBUG("CHECKING RELATIONS");
129                 relations = atspi_accessible_get_relation_set(source, NULL);
130                 if (relations) {
131                         for (i = 0; i < relations->len; i++) {
132                                 DEBUG("ALL RELATIONS FOUND: %d", relations->len);
133                                 relation = g_array_index(relations, AtspiRelation *, i);
134                                 type = atspi_relation_get_relation_type(relation);
135                                 DEBUG("RELATION:  %d", type);
136
137                                 if (type == search_type) {
138                                         ret = atspi_relation_get_target(relation, 0);
139                                         DEBUG("SEARCHED RELATION FOUND");
140                                         break;
141                                 }
142                         }
143                         g_array_free(relations, TRUE);
144                 }
145         }
146         return ret;
147 }
148
149 static Eina_Bool _accept_object(AtspiAccessible * obj)
150 {
151         DEBUG("START");
152         if (!obj)
153                 return EINA_FALSE;
154
155         Eina_Bool ret = EINA_FALSE;
156         gchar *name = NULL;
157         gchar *desc = NULL;
158         AtspiAction *action = NULL;
159         AtspiEditableText *etext = NULL;
160         AtspiText *text = NULL;
161         AtspiValue *value = NULL;
162         AtspiStateSet *ss = NULL;
163         AtspiComponent *component;
164         AtspiRect *extent;
165
166         AtspiRole r = atspi_accessible_get_role(obj, NULL);
167
168         switch (r) {
169         case ATSPI_ROLE_APPLICATION:
170         case ATSPI_ROLE_FILLER:
171         case ATSPI_ROLE_SCROLL_PANE:
172         case ATSPI_ROLE_SPLIT_PANE:
173         case ATSPI_ROLE_WINDOW:
174         case ATSPI_ROLE_IMAGE:
175         case ATSPI_ROLE_LIST:
176         case ATSPI_ROLE_PAGE_TAB_LIST:
177         case ATSPI_ROLE_TOOL_BAR:
178         case ATSPI_ROLE_REDUNDANT_OBJECT:
179                 return EINA_FALSE;
180         case ATSPI_ROLE_DIALOG:
181                 if (!_has_escape_action(obj))
182                         return EINA_FALSE;
183                 break;
184         default:
185                 break;
186         }
187
188         // When given accessibility object is controlled by other object we consider
189         // it as not "user-presentable" on and skip it in navigation tree
190         AtspiAccessible *relation = _get_object_in_relation(obj, ATSPI_RELATION_CONTROLLED_BY);
191         if (relation)
192         {
193                 g_object_unref(relation);
194                 return EINA_FALSE;
195         }
196
197         ss = atspi_accessible_get_state_set(obj);
198         if (ss) {
199                 if (_object_is_item(obj)) {
200                         AtspiAccessible *parent = atspi_accessible_get_parent(obj, NULL);
201                         if (parent) {
202                                 AtspiStateSet *pss = atspi_accessible_get_state_set(parent);
203                                 g_object_unref(parent);
204                                 if (pss) {
205                                         ret = atspi_state_set_contains(pss, ATSPI_STATE_SHOWING) && atspi_state_set_contains(pss, ATSPI_STATE_VISIBLE) && !_is_collapsed(pss);
206                                         DEBUG("ITEM HAS SHOWING && VISIBLE && NOT COLLAPSED PARENT %d", ret);
207                                         g_object_unref(pss);
208                                         g_object_unref(ss);
209                                         return ret;
210                                 }
211                         }
212                 } else {
213                         /* Extent of candidate object could be 0 */
214                         component = atspi_accessible_get_component_iface(obj);
215                         extent = atspi_component_get_extents(component, ATSPI_COORD_TYPE_SCREEN, NULL);
216                         g_object_unref(component);
217
218                         if (extent->width <= 0 || extent->height <= 0) {
219                                 g_free(extent);
220                                 g_object_unref(ss);
221                                 return EINA_FALSE;
222                         }
223                         g_free(extent);
224
225                         ret = atspi_state_set_contains(ss, ATSPI_STATE_SHOWING) && atspi_state_set_contains(ss, ATSPI_STATE_VISIBLE);
226                 }
227                 g_object_unref(ss);
228         }
229         if (!ret) {
230                 return EINA_FALSE;
231         }
232
233         name = atspi_accessible_get_name(obj, NULL);
234
235         ret = EINA_FALSE;
236         if (name) {
237                 if (strncmp(name, "\0", 1)) {
238                         DEBUG("Has name:[%s]", name);
239                         ret = EINA_TRUE;
240                 }
241                 g_free(name);
242         }
243         if (!ret) {
244                 desc = atspi_accessible_get_description(obj, NULL);
245                 if (desc) {
246                         if (strncmp(desc, "\0", 1)) {
247                                 DEBUG("Has description:[%s]", desc);
248                                 ret = EINA_TRUE;
249                         }
250                         g_free(desc);
251                 }
252         }
253         if (!ret) {
254                 action = atspi_accessible_get_action_iface(obj);
255                 if (action) {
256                         DEBUG("Has action interface");
257                         ret = EINA_TRUE;
258                         g_object_unref(action);
259                 }
260         }
261         if (!ret) {
262                 value = atspi_accessible_get_value_iface(obj);
263                 if (value) {
264                         DEBUG("Has value interface");
265                         ret = EINA_TRUE;
266                         g_object_unref(value);
267                 }
268         }
269         if (!ret) {
270                 etext = atspi_accessible_get_editable_text_iface(obj);
271                 if (etext) {
272                         DEBUG("Has editable text interface");
273                         ret = EINA_TRUE;
274                         g_object_unref(etext);
275                 }
276         }
277         if (!ret) {
278                 text = atspi_accessible_get_text_iface(obj);
279                 if (text) {
280                         DEBUG("Has text interface");
281                         ret = EINA_TRUE;
282                         g_object_unref(text);
283                 }
284         }
285
286         DEBUG("END:%d", ret);
287         return ret;
288 }
289
290 #ifdef SCREEN_READER_FLAT_NAVI_TEST_DUMMY_IMPLEMENTATION
291 Eina_Bool flat_navi_context_current_at_x_y_set(FlatNaviContext * ctx, gint x_cord, gint y_cord, AtspiAccessible ** target)
292 {
293         return EINA_FALSE;
294 }
295 #else
296
297 int _object_has_modal_state(AtspiAccessible * obj)
298 {
299         if (!obj)
300                 return EINA_FALSE;
301
302         Eina_Bool ret = EINA_FALSE;
303
304         AtspiStateSet *ss = atspi_accessible_get_state_set(obj);
305
306         if (atspi_state_set_contains(ss, ATSPI_STATE_MODAL))
307                 ret = EINA_TRUE;
308         g_object_unref(ss);
309         return ret;
310 }
311
312 Eina_Bool flat_navi_context_current_at_x_y_set(FlatNaviContext * ctx, gint x_cord, gint y_cord, AtspiAccessible ** target)
313 {
314         if (!ctx || !target)
315                 return EINA_FALSE;
316
317         if (!ctx->root) {
318                 DEBUG("NO top window");
319                 return EINA_FALSE;
320         }
321
322         AtspiAccessible *current_obj = flat_navi_context_current_get(ctx);
323
324         Eina_Bool ret = EINA_FALSE;
325         GError *error = NULL;
326
327         AtspiAccessible *obj = g_object_ref(ctx->root);
328         AtspiAccessible *youngest_ancestor_in_context = (_accept_object(obj) ? g_object_ref(obj) : NULL);
329         AtspiComponent *component;
330         Eina_Bool look_for_next_descendant = EINA_TRUE;
331
332         while (look_for_next_descendant) {
333                 component = atspi_accessible_get_component_iface(obj);
334
335                 g_object_unref(obj);
336                 obj = component ? atspi_component_get_accessible_at_point(component, x_cord, y_cord, ATSPI_COORD_TYPE_WINDOW, &error) : NULL;
337                 g_clear_object(&component);
338
339                 if (error) {
340                         DEBUG("Got error from atspi_component_get_accessible_at_point, domain: %i, code: %i, message: %s", error->domain, error->code, error->message);
341                         g_clear_error(&error);
342                         g_clear_object(&obj);
343                         if (youngest_ancestor_in_context)
344                                 g_clear_object(&youngest_ancestor_in_context);
345                         look_for_next_descendant = EINA_FALSE;
346                 } else if (obj) {
347                         DEBUG("Found object %s, role %s", atspi_accessible_get_name(obj, NULL), atspi_accessible_get_role_name(obj, NULL));
348                         if (_accept_object(obj)) {
349                                 DEBUG("Object  %s with role %s fulfills highlight conditions", atspi_accessible_get_name(obj, NULL), atspi_accessible_get_role_name(obj, NULL));
350                                 if (youngest_ancestor_in_context)
351                                         g_object_unref(youngest_ancestor_in_context);
352                                 youngest_ancestor_in_context = g_object_ref(obj);
353                         }
354                 } else {
355                         g_clear_object(&obj);
356                         look_for_next_descendant = EINA_FALSE;
357                 }
358         }
359
360         if (youngest_ancestor_in_context && !_object_has_modal_state(youngest_ancestor_in_context)) {
361
362                 Eina_Bool update_target = youngest_ancestor_in_context == current_obj;
363                 if (!update_target)
364                         update_target = flat_navi_context_current_set(ctx, youngest_ancestor_in_context);
365                 if (update_target) {
366                         DEBUG("Setting highlight to object %s with role %s", atspi_accessible_get_name(youngest_ancestor_in_context, NULL), atspi_accessible_get_role_name(youngest_ancestor_in_context, NULL));
367                         *target = youngest_ancestor_in_context;
368                         ret = EINA_TRUE;
369                 }
370         } else
371                 DEBUG("NO widget under (%d, %d) found or the same widget under hover", x_cord, y_cord);
372         DEBUG("END");
373         return ret;
374 }
375 #endif
376
377 AtspiAccessible *_get_child(AtspiAccessible * obj, int i)
378 {
379         DEBUG("START:%d", i);
380         if (i < 0) {
381                 DEBUG("END");
382                 return NULL;
383         }
384         if (!obj) {
385                 DEBUG("END");
386                 return NULL;
387         }
388         int cc = atspi_accessible_get_child_count(obj, NULL);
389         if (cc == 0 || i >= cc) {
390                 DEBUG("END");
391                 return NULL;
392         }
393         return atspi_accessible_get_child_at_index(obj, i, NULL);
394 }
395
396 static Eina_Bool _has_next_sibling(AtspiAccessible * obj, int next_sibling_idx_modifier)
397 {
398         Eina_Bool ret = EINA_FALSE;
399         if (!obj) return ret;
400
401         int idx = atspi_accessible_get_index_in_parent(obj, NULL);
402         if (idx >= 0) {
403                 AtspiAccessible *parent = atspi_accessible_get_parent(obj, NULL);
404                 int cc = atspi_accessible_get_child_count(parent, NULL);
405                 g_object_unref(parent);
406                 if ((next_sibling_idx_modifier > 0 && idx < cc - 1) || (next_sibling_idx_modifier < 0 && idx > 0)) {
407                         ret = EINA_TRUE;
408                 }
409         }
410         return ret;
411 }
412
413 AtspiAccessible *_directional_depth_first_search(AtspiAccessible * root, AtspiAccessible * start, int next_sibling_idx_modifier, Eina_Bool(*stop_condition) (AtspiAccessible *))
414 {
415         Eina_Bool start_is_not_defunct = EINA_FALSE;
416         AtspiStateSet *ss;
417
418         if (start) {
419                 AtspiStateSet *ss = atspi_accessible_get_state_set(start);
420                 start_is_not_defunct = !atspi_state_set_contains(ss, ATSPI_STATE_DEFUNCT);
421                 g_object_unref(ss);
422                 if (!start_is_not_defunct)
423                         DEBUG("Start is defunct!");
424         }
425
426         AtspiAccessible *node = (start && start_is_not_defunct)
427                 ? g_object_ref(start)
428                 : (root ? g_object_ref(root) : NULL);
429
430         if (!node)
431                 return NULL;
432
433         AtspiAccessible *next_related_in_direction = (next_sibling_idx_modifier > 0)
434                 ? _get_object_in_relation(node, ATSPI_RELATION_FLOWS_TO)
435                 : _get_object_in_relation(node, ATSPI_RELATION_FLOWS_FROM);
436
437         Eina_Bool relation_mode = EINA_FALSE;
438         if (next_related_in_direction) {
439                 relation_mode = EINA_TRUE;
440                 g_object_unref(next_related_in_direction);
441         }
442
443         Eina_Bool all_children_visited = (start && start != root && next_sibling_idx_modifier < 0)?EINA_TRUE:EINA_FALSE;
444
445         while (node) {
446                 AtspiAccessible *prev_related_in_direction = (next_sibling_idx_modifier > 0)
447                         ? _get_object_in_relation(node, ATSPI_RELATION_FLOWS_FROM)
448                         : _get_object_in_relation(node, ATSPI_RELATION_FLOWS_TO);
449
450                 int cc = atspi_accessible_get_child_count(node, NULL);
451                 // do not accept:
452                 // 1. start node
453                 // 2. internal nodes of flow relation chains
454                 // 3. parent before children in backward traversing
455                 if (node != start && (relation_mode || !prev_related_in_direction) && !(cc > 0 && next_sibling_idx_modifier < 0 && !all_children_visited) && stop_condition(node)) {
456                         g_object_unref(prev_related_in_direction);
457                         return node;
458                 }
459
460                 AtspiAccessible *next_related_in_direction = (next_sibling_idx_modifier > 0)
461                         ? _get_object_in_relation(node, ATSPI_RELATION_FLOWS_TO)
462                         : _get_object_in_relation(node, ATSPI_RELATION_FLOWS_FROM);
463
464                 DEBUG("RELATION MODE: %d", relation_mode);
465                 if (!prev_related_in_direction)
466                         DEBUG("PREV IN RELATION NULL");
467                 if (!next_related_in_direction)
468                         DEBUG("NEXT IN RELATION NULL");
469
470                 if ((!relation_mode && !prev_related_in_direction && next_related_in_direction) || (relation_mode && next_related_in_direction)) {
471                         DEBUG("APPLICABLE FOR RELATION NAVIG");
472                         g_object_unref(prev_related_in_direction);
473                         relation_mode = EINA_TRUE;
474                         g_object_unref(node);
475                         node = next_related_in_direction;
476                 } else {
477                         g_object_unref(prev_related_in_direction);
478                         g_object_unref(next_related_in_direction);
479                         relation_mode = EINA_FALSE;
480
481                         ss = atspi_accessible_get_state_set(node);
482
483                         if (cc > 0 && !all_children_visited && atspi_state_set_contains(ss, ATSPI_STATE_SHOWING))                       // walk down
484                         {
485                                 int idx = next_sibling_idx_modifier > 0 ? 0 : cc - 1;
486                                 g_object_unref(node);
487                                 node = atspi_accessible_get_child_at_index(node, idx, NULL);
488                                 DEBUG("DFS DOWN");
489                         } else {
490                                 all_children_visited = EINA_TRUE;
491                                 DEBUG("ALL CHILD VISITED (TRUE)");
492                                 Eina_Bool up_node_found = EINA_FALSE;
493                                 while (!_has_next_sibling(node, next_sibling_idx_modifier) || node == root)     // no next sibling
494                                 {
495                                         DEBUG("DFS NO SIBLING");
496                                         if (!node || node == root) {
497                                                 DEBUG("DFS END");
498                                                 g_object_unref(node);
499                                                 g_object_unref(ss);
500                                                 return NULL;
501                                         }
502                                         g_object_unref(node);
503                                         node = atspi_accessible_get_parent(node, NULL); // walk up...
504                                         DEBUG("DFS UP");
505                                         // in backward traversing stop the walk up on parent
506                                         if (next_sibling_idx_modifier < 0) {
507                                                 up_node_found = EINA_TRUE;
508                                                 break;
509                                         }
510                                 }
511                                 if (!up_node_found) {
512                                         int idx = atspi_accessible_get_index_in_parent(node, NULL);
513                                         g_object_unref(node);
514                                         node = atspi_accessible_get_child_at_index(atspi_accessible_get_parent(node, NULL), idx + next_sibling_idx_modifier, NULL);     //... and next
515                                         all_children_visited = EINA_FALSE;
516                                         DEBUG("RESET ALL CHILD VISITED (FALSE) FOR NEW SIBLING");
517                                         DEBUG("DFS NEXT %d", idx + next_sibling_idx_modifier);
518                                 }
519                         }
520                         g_object_unref(ss);
521                 }
522         }
523         DEBUG("DFS END");
524         return NULL;
525 }
526
527 AtspiAccessible *_first(FlatNaviContext * ctx)
528 {
529         DEBUG("START");
530         return _directional_depth_first_search(ctx->root, NULL, 1, &_accept_object);
531 }
532
533 AtspiAccessible *_last(FlatNaviContext * ctx)
534 {
535         DEBUG("START");
536         return _directional_depth_first_search(ctx->root, NULL, -1, &_accept_object);
537 }
538
539 AtspiAccessible *_next(FlatNaviContext * ctx)
540 {
541         DEBUG("START");
542         AtspiAccessible *root = ctx->root;
543         AtspiAccessible *current = ctx->current;
544         AtspiAccessible *ret = NULL;
545
546         ret = _directional_depth_first_search(root, current, 1, &_accept_object);
547
548         if (current && !ret) {
549                 DEBUG("DFS SECOND PASS");
550                 ret = _directional_depth_first_search(root, NULL, 1, &_accept_object);
551         }
552         return ret;
553 }
554
555 AtspiAccessible *_prev(FlatNaviContext * ctx)
556 {
557         DEBUG("START\n");
558         AtspiAccessible *root = ctx->root;
559         AtspiAccessible *current = ctx->current;
560         AtspiAccessible *ret = NULL;
561
562         ret = _directional_depth_first_search(root, current, -1, &_accept_object);
563         if (current && !ret) {
564                 DEBUG("DFS SECOND PASS");
565                 ret = _directional_depth_first_search(root, NULL, -1, &_accept_object);
566         }
567         return ret;
568 }
569
570 int flat_navi_context_current_children_count_visible_get(FlatNaviContext * ctx)
571 {
572         if (!ctx)
573                 return -1;
574         int count = 0;
575         /*
576            AtspiAccessible *obj = NULL;
577            AtspiStateSet *ss = NULL;
578
579            Eina_List *l, *l2, *line;
580            AtspiAccessible *current = flat_navi_context_current_get(ctx);
581            AtspiAccessible *parent = atspi_accessible_get_parent (current, NULL);
582
583            EINA_LIST_FOREACH(ctx->lines, l, line)
584            {
585            EINA_LIST_FOREACH(line, l2, obj)
586            {
587            ss = atspi_accessible_get_state_set(obj);
588            if (atspi_state_set_contains(ss, ATSPI_STATE_SHOWING) && parent == atspi_accessible_get_parent(obj, NULL))
589            count++;
590            g_object_unref(ss);
591            }
592            }
593          */
594         return count;
595 }
596
597 FlatNaviContext *flat_navi_context_create(AtspiAccessible * root)
598 {
599         DEBUG("START");
600         if (!root)
601                 return NULL;
602         FlatNaviContext *ret;
603         ret = calloc(1, sizeof(FlatNaviContext));
604         if (!ret)
605                 return NULL;
606
607         ret->root = root;
608         ret->current = _first(ret);
609         return ret;
610 }
611
612 void flat_navi_context_free(FlatNaviContext * ctx)
613 {
614         if (!ctx)
615                 return;
616         free(ctx);
617 }
618
619 AtspiAccessible *flat_navi_context_root_get(FlatNaviContext * ctx)
620 {
621         if (!ctx)
622                 return NULL;
623
624         return ctx->root;
625 }
626
627 const AtspiAccessible *flat_navi_context_first_get(FlatNaviContext * ctx)
628 {
629         if (!ctx)
630                 return NULL;
631
632         return _first(ctx);
633 }
634
635 const AtspiAccessible *flat_navi_context_last_get(FlatNaviContext * ctx)
636 {
637         if (!ctx)
638                 return NULL;
639
640         return _last(ctx);
641 }
642
643 AtspiAccessible *flat_navi_context_current_get(FlatNaviContext * ctx)
644 {
645         if (!ctx)
646                 return NULL;
647
648         return ctx->current;
649 }
650
651 Eina_Bool flat_navi_context_current_set(FlatNaviContext * ctx, AtspiAccessible * target)
652 {
653         if (!ctx || !target)
654                 return EINA_FALSE;
655
656         ctx->current = target;
657
658         return EINA_TRUE;
659 }
660
661 AtspiAccessible *flat_navi_context_next(FlatNaviContext * ctx)
662 {
663         if (!ctx)
664                 return NULL;
665
666         AtspiAccessible *ret = _next(ctx);
667         ctx->current = ret;
668
669         return ret;
670
671 }
672
673 AtspiAccessible *flat_navi_context_prev(FlatNaviContext * ctx)
674 {
675         if (!ctx)
676                 return NULL;
677
678         AtspiAccessible *ret = _prev(ctx);
679         ctx->current = ret;
680
681         return ret;
682 }
683
684 AtspiAccessible *flat_navi_context_first(FlatNaviContext * ctx)
685 {
686         if (!ctx)
687                 return NULL;
688
689         AtspiAccessible *ret = _first(ctx);
690         ctx->current = ret;
691
692         return ret;
693 }
694
695 AtspiAccessible *flat_navi_context_last(FlatNaviContext * ctx)
696 {
697         if (!ctx)
698                 return NULL;
699
700         AtspiAccessible *ret = _last(ctx);
701         ctx->current = ret;
702
703         return ret;
704 }
705
706 Eina_Bool flat_navi_is_valid(FlatNaviContext * context, AtspiAccessible * new_root)
707 {
708         Eina_Bool ret = EINA_FALSE;
709         if (!context || !context->current || context->root != new_root)
710                 return ret;
711         AtspiStateSet *ss = atspi_accessible_get_state_set(context->current);
712
713         ret = atspi_state_set_contains(ss, ATSPI_STATE_SHOWING) && !atspi_state_set_contains(ss, ATSPI_STATE_DEFUNCT);
714         g_object_unref(ss);
715         return ret;
716 }