2 * Copyright (c) 2015 Samsung Electronics Co., Ltd. All rights reserved.
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
8 * http://floralicense.org/license/
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.
17 #include "flat_navi.h"
20 struct _FlatNaviContext {
21 AtspiAccessible *root;
22 AtspiAccessible *current;
23 AtspiAccessible *first;
24 AtspiAccessible *last;
27 static const AtspiStateType required_states[] = {
30 ATSPI_STATE_FOCUSABLE,
31 ATSPI_STATE_LAST_DEFINED
34 static const AtspiRole interesting_roles[] = {
37 ATSPI_ROLE_COLOR_CHOOSER,
39 ATSPI_ROLE_DATE_EDITOR,
41 ATSPI_ROLE_FILE_CHOOSER,
43 ATSPI_ROLE_FONT_CHOOSER,
44 ATSPI_ROLE_GLASS_PANE,
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,
62 ATSPI_ROLE_SPIN_BUTTON,
63 ATSPI_ROLE_TABLE_CELL,
65 ATSPI_ROLE_TOGGLE_BUTTON,
68 ATSPI_ROLE_LAST_DEFINED
71 static Eina_Bool _has_escape_action(AtspiAccessible * obj)
73 Eina_Bool ret = EINA_FALSE;
75 AtspiAction *action = NULL;
77 action = atspi_accessible_get_action_iface(obj);
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");
89 g_object_unref(action);
91 DEBUG("Obj %s %s escape action", atspi_accessible_get_role_name(obj, NULL), ret ? "has" : "doesn't have");
95 static Eina_Bool _is_collapsed(AtspiStateSet * ss)
100 Eina_Bool ret = EINA_FALSE;
101 if (atspi_state_set_contains(ss, ATSPI_STATE_EXPANDABLE) && !atspi_state_set_contains(ss, ATSPI_STATE_EXPANDED))
107 static Eina_Bool _object_is_item(AtspiAccessible * obj)
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)
116 DEBUG("IS ITEM %d", ret);
120 static AtspiAccessible *_get_object_in_relation(AtspiAccessible * source, AtspiRelationType search_type)
123 AtspiAccessible *ret = NULL;
124 AtspiRelation *relation;
125 AtspiRelationType type;
128 DEBUG("CHECKING RELATIONS");
129 relations = atspi_accessible_get_relation_set(source, NULL);
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);
137 if (type == search_type) {
138 ret = atspi_relation_get_target(relation, 0);
139 DEBUG("SEARCHED RELATION FOUND");
143 g_array_free(relations, TRUE);
149 static Eina_Bool _accept_object(AtspiAccessible * obj)
155 Eina_Bool ret = EINA_FALSE;
158 AtspiAction *action = NULL;
159 AtspiEditableText *etext = NULL;
160 AtspiText *text = NULL;
161 AtspiValue *value = NULL;
162 AtspiStateSet *ss = NULL;
163 AtspiComponent *component;
166 AtspiRole r = atspi_accessible_get_role(obj, NULL);
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:
180 case ATSPI_ROLE_DIALOG:
181 if (!_has_escape_action(obj))
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);
193 g_object_unref(relation);
197 ss = atspi_accessible_get_state_set(obj);
199 if (_object_is_item(obj)) {
200 AtspiAccessible *parent = atspi_accessible_get_parent(obj, NULL);
202 AtspiStateSet *pss = atspi_accessible_get_state_set(parent);
203 g_object_unref(parent);
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);
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);
218 if (extent->width <= 0 || extent->height <= 0) {
225 ret = atspi_state_set_contains(ss, ATSPI_STATE_SHOWING) && atspi_state_set_contains(ss, ATSPI_STATE_VISIBLE);
233 name = atspi_accessible_get_name(obj, NULL);
237 if (strncmp(name, "\0", 1)) {
238 DEBUG("Has name:[%s]", name);
244 desc = atspi_accessible_get_description(obj, NULL);
246 if (strncmp(desc, "\0", 1)) {
247 DEBUG("Has description:[%s]", desc);
254 action = atspi_accessible_get_action_iface(obj);
256 DEBUG("Has action interface");
258 g_object_unref(action);
262 value = atspi_accessible_get_value_iface(obj);
264 DEBUG("Has value interface");
266 g_object_unref(value);
270 etext = atspi_accessible_get_editable_text_iface(obj);
272 DEBUG("Has editable text interface");
274 g_object_unref(etext);
278 text = atspi_accessible_get_text_iface(obj);
280 DEBUG("Has text interface");
282 g_object_unref(text);
286 DEBUG("END:%d", ret);
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)
297 int _object_has_modal_state(AtspiAccessible * obj)
302 Eina_Bool ret = EINA_FALSE;
304 AtspiStateSet *ss = atspi_accessible_get_state_set(obj);
306 if (atspi_state_set_contains(ss, ATSPI_STATE_MODAL))
312 Eina_Bool flat_navi_context_current_at_x_y_set(FlatNaviContext * ctx, gint x_cord, gint y_cord, AtspiAccessible ** target)
318 DEBUG("NO top window");
322 AtspiAccessible *current_obj = flat_navi_context_current_get(ctx);
324 Eina_Bool ret = EINA_FALSE;
325 GError *error = NULL;
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;
332 while (look_for_next_descendant) {
333 component = atspi_accessible_get_component_iface(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);
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;
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);
355 g_clear_object(&obj);
356 look_for_next_descendant = EINA_FALSE;
360 if (youngest_ancestor_in_context && !_object_has_modal_state(youngest_ancestor_in_context)) {
362 Eina_Bool update_target = youngest_ancestor_in_context == current_obj;
364 update_target = flat_navi_context_current_set(ctx, youngest_ancestor_in_context);
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;
371 DEBUG("NO widget under (%d, %d) found or the same widget under hover", x_cord, y_cord);
377 AtspiAccessible *_get_child(AtspiAccessible * obj, int i)
379 DEBUG("START:%d", i);
388 int cc = atspi_accessible_get_child_count(obj, NULL);
389 if (cc == 0 || i >= cc) {
393 return atspi_accessible_get_child_at_index(obj, i, NULL);
396 static Eina_Bool _has_next_sibling(AtspiAccessible * obj, int next_sibling_idx_modifier)
398 Eina_Bool ret = EINA_FALSE;
399 if (!obj) return ret;
401 int idx = atspi_accessible_get_index_in_parent(obj, NULL);
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)) {
413 AtspiAccessible *_directional_depth_first_search(AtspiAccessible * root, AtspiAccessible * start, int next_sibling_idx_modifier, Eina_Bool(*stop_condition) (AtspiAccessible *))
415 Eina_Bool start_is_not_defunct = EINA_FALSE;
419 AtspiStateSet *ss = atspi_accessible_get_state_set(start);
420 start_is_not_defunct = !atspi_state_set_contains(ss, ATSPI_STATE_DEFUNCT);
422 if (!start_is_not_defunct)
423 DEBUG("Start is defunct!");
426 AtspiAccessible *node = (start && start_is_not_defunct)
427 ? g_object_ref(start)
428 : (root ? g_object_ref(root) : NULL);
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);
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);
443 Eina_Bool all_children_visited = (start && start != root && next_sibling_idx_modifier < 0)?EINA_TRUE:EINA_FALSE;
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);
450 int cc = atspi_accessible_get_child_count(node, NULL);
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);
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);
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");
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;
477 g_object_unref(prev_related_in_direction);
478 g_object_unref(next_related_in_direction);
479 relation_mode = EINA_FALSE;
481 ss = atspi_accessible_get_state_set(node);
483 if (cc > 0 && !all_children_visited && atspi_state_set_contains(ss, ATSPI_STATE_SHOWING)) // walk down
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);
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
495 DEBUG("DFS NO SIBLING");
496 if (!node || node == root) {
498 g_object_unref(node);
502 g_object_unref(node);
503 node = atspi_accessible_get_parent(node, NULL); // walk up...
505 // in backward traversing stop the walk up on parent
506 if (next_sibling_idx_modifier < 0) {
507 up_node_found = EINA_TRUE;
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);
527 AtspiAccessible *_first(FlatNaviContext * ctx)
530 return _directional_depth_first_search(ctx->root, NULL, 1, &_accept_object);
533 AtspiAccessible *_last(FlatNaviContext * ctx)
536 return _directional_depth_first_search(ctx->root, NULL, -1, &_accept_object);
539 AtspiAccessible *_next(FlatNaviContext * ctx)
542 AtspiAccessible *root = ctx->root;
543 AtspiAccessible *current = ctx->current;
544 AtspiAccessible *ret = NULL;
546 ret = _directional_depth_first_search(root, current, 1, &_accept_object);
548 if (current && !ret) {
549 DEBUG("DFS SECOND PASS");
550 ret = _directional_depth_first_search(root, NULL, 1, &_accept_object);
555 AtspiAccessible *_prev(FlatNaviContext * ctx)
558 AtspiAccessible *root = ctx->root;
559 AtspiAccessible *current = ctx->current;
560 AtspiAccessible *ret = NULL;
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);
570 int flat_navi_context_current_children_count_visible_get(FlatNaviContext * ctx)
576 AtspiAccessible *obj = NULL;
577 AtspiStateSet *ss = NULL;
579 Eina_List *l, *l2, *line;
580 AtspiAccessible *current = flat_navi_context_current_get(ctx);
581 AtspiAccessible *parent = atspi_accessible_get_parent (current, NULL);
583 EINA_LIST_FOREACH(ctx->lines, l, line)
585 EINA_LIST_FOREACH(line, l2, obj)
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))
597 FlatNaviContext *flat_navi_context_create(AtspiAccessible * root)
602 FlatNaviContext *ret;
603 ret = calloc(1, sizeof(FlatNaviContext));
608 ret->current = _first(ret);
612 void flat_navi_context_free(FlatNaviContext * ctx)
619 AtspiAccessible *flat_navi_context_root_get(FlatNaviContext * ctx)
627 const AtspiAccessible *flat_navi_context_first_get(FlatNaviContext * ctx)
635 const AtspiAccessible *flat_navi_context_last_get(FlatNaviContext * ctx)
643 AtspiAccessible *flat_navi_context_current_get(FlatNaviContext * ctx)
651 Eina_Bool flat_navi_context_current_set(FlatNaviContext * ctx, AtspiAccessible * target)
656 ctx->current = target;
661 AtspiAccessible *flat_navi_context_next(FlatNaviContext * ctx)
666 AtspiAccessible *ret = _next(ctx);
673 AtspiAccessible *flat_navi_context_prev(FlatNaviContext * ctx)
678 AtspiAccessible *ret = _prev(ctx);
684 AtspiAccessible *flat_navi_context_first(FlatNaviContext * ctx)
689 AtspiAccessible *ret = _first(ctx);
695 AtspiAccessible *flat_navi_context_last(FlatNaviContext * ctx)
700 AtspiAccessible *ret = _last(ctx);
706 Eina_Bool flat_navi_is_valid(FlatNaviContext * context, AtspiAccessible * new_root)
708 Eina_Bool ret = EINA_FALSE;
709 if (!context || !context->current || context->root != new_root)
711 AtspiStateSet *ss = atspi_accessible_get_state_set(context->current);
713 ret = atspi_state_set_contains(ss, ATSPI_STATE_SHOWING) && !atspi_state_set_contains(ss, ATSPI_STATE_DEFUNCT);