summaryrefslogtreecommitdiff
path: root/src/flat_navi.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/flat_navi.c')
-rw-r--r--src/flat_navi.c693
1 files changed, 693 insertions, 0 deletions
diff --git a/src/flat_navi.c b/src/flat_navi.c
new file mode 100644
index 0000000..72aac40
--- /dev/null
+++ b/src/flat_navi.c
@@ -0,0 +1,693 @@
+/*
+ * Copyright (c) 2015 Samsung Electronics Co., Ltd. All rights reserved.
+ *
+ * Licensed under the Flora License, Version 1.1 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://floralicense.org/license/
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "flat_navi.h"
+#include "logger.h"
+
+struct _FlatNaviContext {
+ AtspiAccessible *root;
+ AtspiAccessible *current;
+ AtspiAccessible *first;
+ AtspiAccessible *last;
+};
+
+static const AtspiStateType required_states[] = {
+ ATSPI_STATE_SHOWING,
+ ATSPI_STATE_VISIBLE,
+ ATSPI_STATE_FOCUSABLE,
+ ATSPI_STATE_LAST_DEFINED
+};
+
+static const AtspiRole interesting_roles[] = {
+ ATSPI_ROLE_CALENDAR,
+ ATSPI_ROLE_CHECK_BOX,
+ ATSPI_ROLE_COLOR_CHOOSER,
+ ATSPI_ROLE_COMBO_BOX,
+ ATSPI_ROLE_DATE_EDITOR,
+ ATSPI_ROLE_DIALOG,
+ ATSPI_ROLE_FILE_CHOOSER,
+ ATSPI_ROLE_FILLER,
+ ATSPI_ROLE_FONT_CHOOSER,
+ ATSPI_ROLE_GLASS_PANE,
+ ATSPI_ROLE_HEADER,
+ ATSPI_ROLE_HEADING,
+ ATSPI_ROLE_ICON,
+ ATSPI_ROLE_ENTRY,
+ ATSPI_ROLE_LABEL,
+ ATSPI_ROLE_LINK,
+ ATSPI_ROLE_LIST_ITEM,
+ ATSPI_ROLE_MENU_ITEM,
+ ATSPI_ROLE_PANEL,
+ ATSPI_ROLE_PARAGRAPH,
+ ATSPI_ROLE_PASSWORD_TEXT,
+ ATSPI_ROLE_POPUP_MENU,
+ ATSPI_ROLE_PUSH_BUTTON,
+ ATSPI_ROLE_PROGRESS_BAR,
+ ATSPI_ROLE_RADIO_BUTTON,
+ ATSPI_ROLE_RADIO_MENU_ITEM,
+ ATSPI_ROLE_SLIDER,
+ ATSPI_ROLE_SPIN_BUTTON,
+ ATSPI_ROLE_TABLE_CELL,
+ ATSPI_ROLE_TEXT,
+ ATSPI_ROLE_TOGGLE_BUTTON,
+ ATSPI_ROLE_TOOL_TIP,
+ ATSPI_ROLE_TREE_ITEM,
+ ATSPI_ROLE_LAST_DEFINED
+};
+
+static Eina_Bool _has_escape_action(AtspiAccessible * obj)
+{
+ Eina_Bool ret = EINA_FALSE;
+
+ AtspiAction *action = NULL;
+
+ action = atspi_accessible_get_action_iface(obj);
+ if (action) {
+ int i = 0;
+ for (; i < atspi_action_get_n_actions(action, NULL); i++) {
+ gchar *action_name = atspi_action_get_action_name(action, i, NULL);
+ Eina_Bool equal = !strcmp(action_name, "escape");
+ g_free(action_name);
+ if (equal) {
+ ret = EINA_TRUE;
+ break;
+ }
+ }
+ g_object_unref(action);
+ }
+ DEBUG("Obj %s %s escape action", atspi_accessible_get_role_name(obj, NULL), ret ? "has" : "doesn't have");
+ return ret;
+}
+
+static Eina_Bool _is_collapsed(AtspiStateSet * ss)
+{
+ if (!ss)
+ return EINA_FALSE;
+
+ Eina_Bool ret = EINA_FALSE;
+ if (atspi_state_set_contains(ss, ATSPI_STATE_EXPANDABLE) && !atspi_state_set_contains(ss, ATSPI_STATE_EXPANDED))
+ ret = EINA_TRUE;
+
+ return ret;
+}
+
+static Eina_Bool _object_is_item(AtspiAccessible * obj)
+{
+ if (!obj)
+ return EINA_FALSE;
+
+ Eina_Bool ret = EINA_FALSE;
+ AtspiRole role = atspi_accessible_get_role(obj, NULL);
+ if (role == ATSPI_ROLE_LIST_ITEM || role == ATSPI_ROLE_MENU_ITEM)
+ ret = EINA_TRUE;
+ DEBUG("IS ITEM %d", ret);
+ return ret;
+}
+
+static AtspiAccessible *_get_object_in_relation(AtspiAccessible * source, AtspiRelationType search_type)
+{
+ GArray *relations;
+ AtspiAccessible *ret = NULL;
+ AtspiRelation *relation;
+ AtspiRelationType type;
+ int i;
+ if (source) {
+ DEBUG("CHECKING RELATIONS");
+ relations = atspi_accessible_get_relation_set(source, NULL);
+ if (relations) {
+ for (i = 0; i < relations->len; i++) {
+ DEBUG("ALL RELATIONS FOUND: %d", relations->len);
+ relation = g_array_index(relations, AtspiRelation *, i);
+ type = atspi_relation_get_relation_type(relation);
+ DEBUG("RELATION: %d", type);
+
+ if (type == search_type) {
+ ret = atspi_relation_get_target(relation, 0);
+ DEBUG("SEARCHED RELATION FOUND");
+ break;
+ }
+ }
+ g_array_free(relations, TRUE);
+ }
+ }
+ return ret;
+}
+
+static Eina_Bool _accept_object(AtspiAccessible * obj)
+{
+ DEBUG("START");
+ if (!obj)
+ return EINA_FALSE;
+
+ Eina_Bool ret = EINA_FALSE;
+ gchar *name = NULL;
+ gchar *desc = NULL;
+ AtspiAction *action = NULL;
+ AtspiEditableText *etext = NULL;
+ AtspiText *text = NULL;
+ AtspiValue *value = NULL;
+ AtspiStateSet *ss = NULL;
+ AtspiComponent *component;
+ AtspiRect *extent;
+
+ AtspiRole r = atspi_accessible_get_role(obj, NULL);
+
+ switch (r) {
+ case ATSPI_ROLE_APPLICATION:
+ case ATSPI_ROLE_FILLER:
+ case ATSPI_ROLE_SCROLL_PANE:
+ case ATSPI_ROLE_SPLIT_PANE:
+ case ATSPI_ROLE_WINDOW:
+ case ATSPI_ROLE_IMAGE:
+ case ATSPI_ROLE_LIST:
+ case ATSPI_ROLE_PAGE_TAB_LIST:
+ case ATSPI_ROLE_TOOL_BAR:
+ case ATSPI_ROLE_REDUNDANT_OBJECT:
+ return EINA_FALSE;
+ case ATSPI_ROLE_DIALOG:
+ if (!_has_escape_action(obj))
+ return EINA_FALSE;
+ break;
+ default:
+ break;
+ }
+
+ // When given accessibility object is controlled by other object we consider
+ // it as not "user-presentable" on and skip it in navigation tree
+ AtspiAccessible *relation = _get_object_in_relation(obj, ATSPI_RELATION_CONTROLLED_BY);
+ if (relation)
+ {
+ g_object_unref(relation);
+ return EINA_FALSE;
+ }
+
+ ss = atspi_accessible_get_state_set(obj);
+ if (ss) {
+ if (_object_is_item(obj)) {
+ AtspiAccessible *parent = atspi_accessible_get_parent(obj, NULL);
+ if (parent) {
+ AtspiStateSet *pss = atspi_accessible_get_state_set(parent);
+ g_object_unref(parent);
+ if (pss) {
+ ret = atspi_state_set_contains(pss, ATSPI_STATE_SHOWING) && atspi_state_set_contains(pss, ATSPI_STATE_VISIBLE) && !_is_collapsed(pss);
+ DEBUG("ITEM HAS SHOWING && VISIBLE && NOT COLLAPSED PARENT %d", ret);
+ g_object_unref(pss);
+ g_object_unref(ss);
+ return ret;
+ }
+ }
+ } else {
+ /* Extent of candidate object could be 0 */
+ component = atspi_accessible_get_component_iface(obj);
+ extent = atspi_component_get_extents(component, ATSPI_COORD_TYPE_SCREEN, NULL);
+ g_object_unref(component);
+
+ if (extent->width <= 0 || extent->height <= 0) {
+ g_free(extent);
+ g_object_unref(ss);
+ return EINA_FALSE;
+ }
+ g_free(extent);
+
+ ret = atspi_state_set_contains(ss, ATSPI_STATE_SHOWING) && atspi_state_set_contains(ss, ATSPI_STATE_VISIBLE);
+ }
+ g_object_unref(ss);
+ }
+ if (!ret) {
+ return EINA_FALSE;
+ }
+
+ name = atspi_accessible_get_name(obj, NULL);
+
+ ret = EINA_FALSE;
+ if (name) {
+ if (strncmp(name, "\0", 1)) {
+ DEBUG("Has name:[%s]", name);
+ ret = EINA_TRUE;
+ }
+ g_free(name);
+ }
+ if (!ret) {
+ desc = atspi_accessible_get_description(obj, NULL);
+ if (desc) {
+ if (strncmp(desc, "\0", 1)) {
+ DEBUG("Has description:[%s]", desc);
+ ret = EINA_TRUE;
+ }
+ g_free(desc);
+ }
+ }
+ if (!ret) {
+ action = atspi_accessible_get_action_iface(obj);
+ if (action) {
+ DEBUG("Has action interface");
+ ret = EINA_TRUE;
+ g_object_unref(action);
+ }
+ }
+ if (!ret) {
+ value = atspi_accessible_get_value_iface(obj);
+ if (value) {
+ DEBUG("Has value interface");
+ ret = EINA_TRUE;
+ g_object_unref(value);
+ }
+ }
+ if (!ret) {
+ etext = atspi_accessible_get_editable_text_iface(obj);
+ if (etext) {
+ DEBUG("Has editable text interface");
+ ret = EINA_TRUE;
+ g_object_unref(etext);
+ }
+ }
+ if (!ret) {
+ text = atspi_accessible_get_text_iface(obj);
+ if (text) {
+ DEBUG("Has text interface");
+ ret = EINA_TRUE;
+ g_object_unref(text);
+ }
+ }
+
+ DEBUG("END:%d", ret);
+ return ret;
+}
+
+#ifdef SCREEN_READER_FLAT_NAVI_TEST_DUMMY_IMPLEMENTATION
+Eina_Bool flat_navi_context_current_at_x_y_set(FlatNaviContext * ctx, gint x_cord, gint y_cord, AtspiAccessible ** target)
+{
+ return EINA_FALSE;
+}
+#else
+
+int _object_has_modal_state(AtspiAccessible * obj)
+{
+ if (!obj)
+ return EINA_FALSE;
+
+ Eina_Bool ret = EINA_FALSE;
+
+ AtspiStateSet *ss = atspi_accessible_get_state_set(obj);
+
+ if (atspi_state_set_contains(ss, ATSPI_STATE_MODAL))
+ ret = EINA_TRUE;
+ g_object_unref(ss);
+ return ret;
+}
+
+Eina_Bool flat_navi_context_current_at_x_y_set(FlatNaviContext * ctx, gint x_cord, gint y_cord, AtspiAccessible ** target)
+{
+ if (!ctx || !target)
+ return EINA_FALSE;
+
+ if (!ctx->root) {
+ DEBUG("NO top window");
+ return EINA_FALSE;
+ }
+
+ AtspiAccessible *current_obj = flat_navi_context_current_get(ctx);
+
+ Eina_Bool ret = EINA_FALSE;
+ GError *error = NULL;
+
+ AtspiAccessible *obj = g_object_ref(ctx->root);
+ AtspiAccessible *youngest_ancestor_in_context = (_accept_object(obj) ? g_object_ref(obj) : NULL);
+ AtspiComponent *component;
+ Eina_Bool look_for_next_descendant = EINA_TRUE;
+
+ while (look_for_next_descendant) {
+ component = atspi_accessible_get_component_iface(obj);
+
+ g_object_unref(obj);
+ obj = component ? atspi_component_get_accessible_at_point(component, x_cord, y_cord, ATSPI_COORD_TYPE_WINDOW, &error) : NULL;
+ g_clear_object(&component);
+
+ if (error) {
+ DEBUG("Got error from atspi_component_get_accessible_at_point, domain: %i, code: %i, message: %s", error->domain, error->code, error->message);
+ g_clear_error(&error);
+ g_clear_object(&obj);
+ if (youngest_ancestor_in_context)
+ g_clear_object(&youngest_ancestor_in_context);
+ look_for_next_descendant = EINA_FALSE;
+ } else if (obj) {
+ DEBUG("Found object %s, role %s", atspi_accessible_get_name(obj, NULL), atspi_accessible_get_role_name(obj, NULL));
+ if (_accept_object(obj)) {
+ DEBUG("Object %s with role %s fulfills highlight conditions", atspi_accessible_get_name(obj, NULL), atspi_accessible_get_role_name(obj, NULL));
+ if (youngest_ancestor_in_context)
+ g_object_unref(youngest_ancestor_in_context);
+ youngest_ancestor_in_context = g_object_ref(obj);
+ }
+ } else {
+ g_clear_object(&obj);
+ look_for_next_descendant = EINA_FALSE;
+ }
+ }
+
+ if (youngest_ancestor_in_context && !_object_has_modal_state(youngest_ancestor_in_context)) {
+ if (youngest_ancestor_in_context == current_obj || flat_navi_context_current_set(ctx, youngest_ancestor_in_context)) {
+ 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));
+ *target = youngest_ancestor_in_context;
+ ret = EINA_TRUE;
+ }
+ } else
+ DEBUG("NO widget under (%d, %d) found or the same widget under hover", x_cord, y_cord);
+ DEBUG("END");
+ return ret;
+}
+#endif
+
+AtspiAccessible *_get_child(AtspiAccessible * obj, int i)
+{
+ DEBUG("START:%d", i);
+ if (i < 0) {
+ DEBUG("END");
+ return NULL;
+ }
+ if (!obj) {
+ DEBUG("END");
+ return NULL;
+ }
+ int cc = atspi_accessible_get_child_count(obj, NULL);
+ if (cc == 0 || i >= cc) {
+ DEBUG("END");
+ return NULL;
+ }
+ return atspi_accessible_get_child_at_index(obj, i, NULL);
+}
+
+static Eina_Bool _has_next_sibling(AtspiAccessible * obj, int next_sibling_idx_modifier)
+{
+ Eina_Bool ret = EINA_FALSE;
+ if (!obj) return ret;
+
+ int idx = atspi_accessible_get_index_in_parent(obj, NULL);
+ if (idx >= 0) {
+ AtspiAccessible *parent = atspi_accessible_get_parent(obj, NULL);
+ int cc = atspi_accessible_get_child_count(parent, NULL);
+ g_object_unref(parent);
+ if ((next_sibling_idx_modifier > 0 && idx < cc - 1) || (next_sibling_idx_modifier < 0 && idx > 0)) {
+ ret = EINA_TRUE;
+ }
+ }
+ return ret;
+}
+
+AtspiAccessible *_directional_depth_first_search(AtspiAccessible * root, AtspiAccessible * start, int next_sibling_idx_modifier, Eina_Bool(*stop_condition) (AtspiAccessible *))
+{
+ Eina_Bool start_is_not_defunct = EINA_FALSE;
+ AtspiStateSet *ss;
+
+ if (start) {
+ AtspiStateSet *ss = atspi_accessible_get_state_set(start);
+ start_is_not_defunct = !atspi_state_set_contains(ss, ATSPI_STATE_DEFUNCT);
+ g_object_unref(ss);
+ if (!start_is_not_defunct)
+ DEBUG("Start is defunct!");
+ }
+
+ AtspiAccessible *node = (start && start_is_not_defunct)
+ ? g_object_ref(start)
+ : (root ? g_object_ref(root) : NULL);
+
+ if (!node)
+ return NULL;
+
+ AtspiAccessible *next_related_in_direction = (next_sibling_idx_modifier > 0)
+ ? _get_object_in_relation(node, ATSPI_RELATION_FLOWS_TO)
+ : _get_object_in_relation(node, ATSPI_RELATION_FLOWS_FROM);
+
+ Eina_Bool relation_mode = EINA_FALSE;
+ if (next_related_in_direction) {
+ relation_mode = EINA_TRUE;
+ g_object_unref(next_related_in_direction);
+ }
+
+ while (node) {
+ AtspiAccessible *prev_related_in_direction = (next_sibling_idx_modifier > 0)
+ ? _get_object_in_relation(node, ATSPI_RELATION_FLOWS_FROM)
+ : _get_object_in_relation(node, ATSPI_RELATION_FLOWS_TO);
+
+ if (node != start && (relation_mode || !prev_related_in_direction) && stop_condition(node)) {
+ g_object_unref(prev_related_in_direction);
+ return node;
+ }
+
+ AtspiAccessible *next_related_in_direction = (next_sibling_idx_modifier > 0)
+ ? _get_object_in_relation(node, ATSPI_RELATION_FLOWS_TO)
+ : _get_object_in_relation(node, ATSPI_RELATION_FLOWS_FROM);
+
+ DEBUG("RELATION MODE: %d", relation_mode);
+ if (!prev_related_in_direction)
+ DEBUG("PREV IN RELATION NULL");
+ if (!next_related_in_direction)
+ DEBUG("NEXT IN RELATION NULL");
+
+ if ((!relation_mode && !prev_related_in_direction && next_related_in_direction) || (relation_mode && next_related_in_direction)) {
+ DEBUG("APPLICABLE FOR RELATION NAVIG");
+ g_object_unref(prev_related_in_direction);
+ relation_mode = EINA_TRUE;
+ g_object_unref(node);
+ node = next_related_in_direction;
+ } else {
+ g_object_unref(prev_related_in_direction);
+ g_object_unref(next_related_in_direction);
+ relation_mode = EINA_FALSE;
+ int cc = atspi_accessible_get_child_count(node, NULL);
+ ss = atspi_accessible_get_state_set(node);
+
+ if (cc > 0 && atspi_state_set_contains(ss, ATSPI_STATE_SHOWING)) // walk down
+ {
+ int idx = next_sibling_idx_modifier > 0 ? 0 : cc - 1;
+ g_object_unref(node);
+ node = atspi_accessible_get_child_at_index(node, idx, NULL);
+ DEBUG("DFS DOWN");
+ } else {
+ while (!_has_next_sibling(node, next_sibling_idx_modifier) || node == root) // no next sibling
+ {
+ DEBUG("DFS NO SIBLING");
+ if (!node || node == root) {
+ DEBUG("DFS END");
+ g_object_unref(node);
+ g_object_unref(ss);
+ return NULL;
+ }
+ g_object_unref(node);
+ node = atspi_accessible_get_parent(node, NULL); // walk up...
+ DEBUG("DFS UP");
+ }
+ int idx = atspi_accessible_get_index_in_parent(node, NULL);
+ g_object_unref(node);
+ node = atspi_accessible_get_child_at_index(atspi_accessible_get_parent(node, NULL), idx + next_sibling_idx_modifier, NULL); //... and next
+ DEBUG("DFS NEXT %d", idx + next_sibling_idx_modifier);
+ }
+ g_object_unref(ss);
+ }
+ }
+ DEBUG("DFS END");
+ return NULL;
+}
+
+AtspiAccessible *_first(FlatNaviContext * ctx)
+{
+ DEBUG("START");
+ return _directional_depth_first_search(ctx->root, NULL, 1, &_accept_object);
+}
+
+AtspiAccessible *_last(FlatNaviContext * ctx)
+{
+ DEBUG("START");
+ return _directional_depth_first_search(ctx->root, NULL, -1, &_accept_object);
+}
+
+AtspiAccessible *_next(FlatNaviContext * ctx)
+{
+ DEBUG("START");
+ AtspiAccessible *root = ctx->root;
+ AtspiAccessible *current = ctx->current;
+ AtspiAccessible *ret = NULL;
+
+ ret = _directional_depth_first_search(root, current, 1, &_accept_object);
+
+ if (current && !ret) {
+ DEBUG("DFS SECOND PASS");
+ ret = _directional_depth_first_search(root, NULL, 1, &_accept_object);
+ }
+ return ret;
+}
+
+AtspiAccessible *_prev(FlatNaviContext * ctx)
+{
+ DEBUG("START\n");
+ AtspiAccessible *root = ctx->root;
+ AtspiAccessible *current = ctx->current;
+ AtspiAccessible *ret = NULL;
+
+ ret = _directional_depth_first_search(root, current, -1, &_accept_object);
+ if (current && !ret) {
+ DEBUG("DFS SECOND PASS");
+ ret = _directional_depth_first_search(root, NULL, -1, &_accept_object);
+ }
+ return ret;
+}
+
+int flat_navi_context_current_children_count_visible_get(FlatNaviContext * ctx)
+{
+ if (!ctx)
+ return -1;
+ int count = 0;
+ /*
+ AtspiAccessible *obj = NULL;
+ AtspiStateSet *ss = NULL;
+
+ Eina_List *l, *l2, *line;
+ AtspiAccessible *current = flat_navi_context_current_get(ctx);
+ AtspiAccessible *parent = atspi_accessible_get_parent (current, NULL);
+
+ EINA_LIST_FOREACH(ctx->lines, l, line)
+ {
+ EINA_LIST_FOREACH(line, l2, obj)
+ {
+ ss = atspi_accessible_get_state_set(obj);
+ if (atspi_state_set_contains(ss, ATSPI_STATE_SHOWING) && parent == atspi_accessible_get_parent(obj, NULL))
+ count++;
+ g_object_unref(ss);
+ }
+ }
+ */
+ return count;
+}
+
+FlatNaviContext *flat_navi_context_create(AtspiAccessible * root)
+{
+ DEBUG("START");
+ if (!root)
+ return NULL;
+ FlatNaviContext *ret;
+ ret = calloc(1, sizeof(FlatNaviContext));
+ if (!ret)
+ return NULL;
+
+ ret->root = root;
+ ret->current = _first(ret);
+ return ret;
+}
+
+void flat_navi_context_free(FlatNaviContext * ctx)
+{
+ if (!ctx)
+ return;
+ free(ctx);
+}
+
+AtspiAccessible *flat_navi_context_root_get(FlatNaviContext * ctx)
+{
+ if (!ctx)
+ return NULL;
+
+ return ctx->root;
+}
+
+const AtspiAccessible *flat_navi_context_first_get(FlatNaviContext * ctx)
+{
+ if (!ctx)
+ return NULL;
+
+ return _first(ctx);
+}
+
+const AtspiAccessible *flat_navi_context_last_get(FlatNaviContext * ctx)
+{
+ if (!ctx)
+ return NULL;
+
+ return _last(ctx);
+}
+
+AtspiAccessible *flat_navi_context_current_get(FlatNaviContext * ctx)
+{
+ if (!ctx)
+ return NULL;
+
+ return ctx->current;
+}
+
+Eina_Bool flat_navi_context_current_set(FlatNaviContext * ctx, AtspiAccessible * target)
+{
+ if (!ctx || !target)
+ return EINA_FALSE;
+
+ ctx->current = target;
+
+ return EINA_TRUE;
+}
+
+AtspiAccessible *flat_navi_context_next(FlatNaviContext * ctx)
+{
+ if (!ctx)
+ return NULL;
+
+ AtspiAccessible *ret = _next(ctx);
+ ctx->current = ret;
+
+ return ret;
+
+}
+
+AtspiAccessible *flat_navi_context_prev(FlatNaviContext * ctx)
+{
+ if (!ctx)
+ return NULL;
+
+ AtspiAccessible *ret = _prev(ctx);
+ ctx->current = ret;
+
+ return ret;
+}
+
+AtspiAccessible *flat_navi_context_first(FlatNaviContext * ctx)
+{
+ if (!ctx)
+ return NULL;
+
+ AtspiAccessible *ret = _first(ctx);
+ ctx->current = ret;
+
+ return ret;
+}
+
+AtspiAccessible *flat_navi_context_last(FlatNaviContext * ctx)
+{
+ if (!ctx)
+ return NULL;
+
+ AtspiAccessible *ret = _last(ctx);
+ ctx->current = ret;
+
+ return ret;
+}
+
+Eina_Bool flat_navi_is_valid(FlatNaviContext * context, AtspiAccessible * new_root)
+{
+ Eina_Bool ret = EINA_FALSE;
+ if (!context || !context->current || context->root != new_root)
+ return ret;
+ AtspiStateSet *ss = atspi_accessible_get_state_set(context->current);
+
+ ret = atspi_state_set_contains(ss, ATSPI_STATE_SHOWING) && !atspi_state_set_contains(ss, ATSPI_STATE_DEFUNCT);
+ g_object_unref(ss);
+ return ret;
+}