summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarcel Hollerbach <marcel-hollerbach@t-online.de>2016-10-20 22:59:05 +0200
committerMarcel Hollerbach <marcel-hollerbach@t-online.de>2016-10-28 16:28:12 +0200
commit68c42f5210ff8e2a12e6236d53d917a7e994c600 (patch)
tree403f9024b9ad46ebaf38ef77ea3719f66e181c19
parent860e0d34b0a58cdaf969633249e1a58f166e3970 (diff)
downloadefl-68c42f5210ff8e2a12e6236d53d917a7e994c600.tar.gz
efl_ui: introduce a focus manager and sub manager object
The Efl.Ui.Focus.Manager abstracts the creation of a localization graph and a logical tree. The localization graph is used to find a object right left up or down of a given object. The logical tree is used to iterate throuw the containers which are used to build a ui. Those managers can be used bound to some layer in the ui, so for example the window is a layer, the content of a scroller is a layer. With those layers, we can make sure that movements of a scroller for example just means that this graph of objects in the scroller needs to be recalculated, and not the complete ui. The advantage of having this to layer bound datastructures is that you can easily debug those graphs, since the complete layer of this managerobject can be calculated completly.
-rw-r--r--src/Makefile_Elementary.am16
-rw-r--r--src/lib/elementary/Elementary.h14
-rw-r--r--src/lib/elementary/efl_ui_focus_manager.c1051
-rw-r--r--src/lib/elementary/efl_ui_focus_manager.eo129
-rw-r--r--src/lib/elementary/efl_ui_focus_manager_sub.c188
-rw-r--r--src/lib/elementary/efl_ui_focus_manager_sub.eo25
-rw-r--r--src/lib/elementary/efl_ui_focus_object.c30
-rw-r--r--src/lib/elementary/efl_ui_focus_object.eo28
-rw-r--r--src/lib/elementary/efl_ui_focus_user.eo18
-rw-r--r--src/tests/elementary/elm_suite.c2
-rw-r--r--src/tests/elementary/elm_suite.h2
-rw-r--r--src/tests/elementary/elm_test_focus.c347
-rw-r--r--src/tests/elementary/elm_test_focus_common.c79
-rw-r--r--src/tests/elementary/elm_test_focus_common.h28
-rw-r--r--src/tests/elementary/elm_test_focus_sub.c242
-rw-r--r--src/tests/elementary/focus_test.eo14
-rw-r--r--src/tests/elementary/focus_test_sub.eo8
17 files changed, 2220 insertions, 1 deletions
diff --git a/src/Makefile_Elementary.am b/src/Makefile_Elementary.am
index fec21c2a6d..634651a3ce 100644
--- a/src/Makefile_Elementary.am
+++ b/src/Makefile_Elementary.am
@@ -116,6 +116,10 @@ elm_public_eolian_files = \
lib/elementary/efl_ui_text.eo \
lib/elementary/efl_ui_text_editable.eo \
lib/elementary/efl_config_global.eo \
+ lib/elementary/efl_ui_focus_manager.eo \
+ lib/elementary/efl_ui_focus_manager_sub.eo \
+ lib/elementary/efl_ui_focus_object.eo \
+ lib/elementary/efl_ui_focus_user.eo \
$(NULL)
# Private classes (not exposed or shipped)
@@ -662,6 +666,9 @@ lib_elementary_libelementary_la_SOURCES = \
lib/elementary/efl_ui_grid_static.c \
lib/elementary/efl_ui_grid_private.h \
lib/elementary/efl_ui_text.c \
+ lib/elementary/efl_ui_focus_manager.c \
+ lib/elementary/efl_ui_focus_manager_sub.c \
+ lib/elementary/efl_ui_focus_object.c \
$(NULL)
@@ -1313,7 +1320,14 @@ tests_elementary_elm_suite_SOURCES = \
tests/elementary/elm_code_test_widget.c \
tests/elementary/elm_code_test_widget_text.c \
tests/elementary/elm_code_test_widget_selection.c \
- tests/elementary/elm_code_test_widget_undo.c
+ tests/elementary/elm_code_test_widget_undo.c \
+ tests/elementary/elm_test_focus_common.c \
+ tests/elementary/elm_test_focus.c \
+ tests/elementary/elm_test_focus_sub.c
+
+tests/elementary/tests_elementary_elm_suite-elm_test_focus.$(OBJEXT): tests/elementary/focus_test.eo.c tests/elementary/focus_test.eo.h
+
+tests/elementary/tests_elementary_elm_suite-elm_test_focus_sub.$(OBJEXT): tests/elementary/focus_test_sub.eo.c tests/elementary/focus_test_sub.eo.h
tests_elementary_elm_suite_CPPFLAGS = \
-DTESTS_BUILD_DIR=\"${top_builddir}/src/tests/elementary\" \
diff --git a/src/lib/elementary/Elementary.h b/src/lib/elementary/Elementary.h
index 9de820265d..03d1827269 100644
--- a/src/lib/elementary/Elementary.h
+++ b/src/lib/elementary/Elementary.h
@@ -136,11 +136,25 @@ typedef struct _Elm_Version
EAPI extern Elm_Version *elm_version;
+
/* include these first for general used definitions */
#include <elm_gen.h>
#include <elm_general.h>
#include <elm_config.h>
#include <elm_focus.h>
+
+#ifdef EFL_EO_API_SUPPORT
+# include "efl_ui_focus_object.eo.h"
+# include "efl_ui_focus_manager.eo.h"
+# include "efl_ui_focus_manager_sub.eo.h"
+# include "efl_ui_focus_user.eo.h"
+#else
+# include "efl_ui_focus_object.eo.legacy.h"
+# include "efl_ui_focus_manager.eo.legacy.h"
+# include "efl_ui_focus_manager_sub.eo.legacy.h"
+# include "efl_ui_focus_user.eo.legacy.h"
+#endif
+
#include <elm_tooltip.h>
#include <elm_object_item.h>
#include <elm_focus_item.h>
diff --git a/src/lib/elementary/efl_ui_focus_manager.c b/src/lib/elementary/efl_ui_focus_manager.c
new file mode 100644
index 0000000000..6a4f090d82
--- /dev/null
+++ b/src/lib/elementary/efl_ui_focus_manager.c
@@ -0,0 +1,1051 @@
+#ifdef HAVE_CONFIG_H
+# include "elementary_config.h"
+#endif
+
+#include <Elementary.h>
+#include "elm_priv.h"
+
+#define MY_CLASS EFL_UI_FOCUS_MANAGER_CLASS
+#define FOCUS_DATA(obj) Efl_Ui_Focus_Manager_Data *pd = efl_data_scope_get(obj, MY_CLASS);
+
+#define DIM_EFL_UI_FOCUS_DIRECTION(dim,neg) dim*2+neg
+#define NODE_DIRECTIONS_COUNT 4
+
+#define DIRECTION_CHECK(dir) (dir >= 0 && dir < EFL_UI_FOCUS_DIRECTION_LAST)
+
+//#define DEBUG
+#define DEBUG_TUPLE(obj) efl_name_get(obj), efl_class_name_get(obj)
+
+typedef struct {
+ Eina_Bool positive;
+ Efl_Ui_Focus_Object *anchor;
+} Anchor;
+
+typedef enum {
+ DIMENSION_X = 0,
+ DIMENSION_Y = 1,
+} Dimension;
+
+typedef struct _Border Border;
+typedef struct _Node Node;
+
+struct _Border {
+ Eina_List *partners;
+};
+
+typedef enum {
+ NODE_TYPE_NORMAL = 0,
+ NODE_TYPE_LISTENER = 1,
+} Node_Type;
+
+struct _Node{
+ Node_Type type; //type of the node
+
+ Efl_Ui_Focus_Object *focusable;
+ Efl_Ui_Focus_Manager *manager;
+
+ union {
+ struct {
+ Efl_Ui_Focus_Manager *manager;
+ } listener;
+ struct {
+
+ } normal;
+ } data;
+
+ struct _Tree_Node{
+ Node *parent; //the parent in the tree
+ Eina_List *children; //this saves the original set of elements
+ }tree;
+
+ struct _Graph_Node {
+ Border directions[NODE_DIRECTIONS_COUNT];
+ } graph;
+};
+
+#define T(n) (n->tree)
+#define G(n) (n->graph)
+
+typedef struct {
+ Eina_List *focus_stack;
+ Eina_Hash *node_hash;
+ Efl_Ui_Focus_Manager *redirect;
+ Eina_List *dirty;
+
+ Node *root;
+} Efl_Ui_Focus_Manager_Data;
+
+static Efl_Ui_Focus_Direction
+_complement(Efl_Ui_Focus_Direction dir)
+{
+ #define COMP(a,b) \
+ if (dir == a) return b; \
+ if (dir == b) return a;
+
+ COMP(EFL_UI_FOCUS_DIRECTION_RIGHT, EFL_UI_FOCUS_DIRECTION_LEFT)
+ COMP(EFL_UI_FOCUS_DIRECTION_UP, EFL_UI_FOCUS_DIRECTION_DOWN)
+ COMP(EFL_UI_FOCUS_DIRECTION_PREV, EFL_UI_FOCUS_DIRECTION_NEXT)
+
+ #undef COMP
+
+ return EFL_UI_FOCUS_DIRECTION_LAST;
+}
+
+/*
+ * Set this new list of partners to the border.
+ * All old partners will be deleted
+ */
+static void
+border_partners_set(Node *node, Efl_Ui_Focus_Direction direction, Eina_List *list)
+{
+ Node *partner;
+ Eina_List *lnode;
+ Border *border = &G(node).directions[direction];
+
+ EINA_LIST_FREE(border->partners, partner)
+ {
+ Border *comp_border = &G(partner).directions[_complement(direction)];
+
+ comp_border->partners = eina_list_remove(comp_border->partners, node);
+ }
+
+ border->partners = list;
+
+ EINA_LIST_FOREACH(border->partners, lnode, partner)
+ {
+ Border *comp_border = &G(partner).directions[_complement(direction)];
+
+ comp_border->partners = eina_list_append(comp_border->partners, node);
+ }
+}
+
+/**
+ * Create a new node
+ */
+static Node*
+node_new(Efl_Ui_Focus_Object *focusable, Efl_Ui_Focus_Manager *manager)
+{
+ Node *node;
+
+ node = calloc(1, sizeof(Node));
+
+ node->focusable = focusable;
+ node->manager = manager;
+
+ return node;
+}
+
+static Node*
+node_get(Efl_Ui_Focus_Manager_Data *pd, Efl_Ui_Focus_Object *focusable)
+{
+ Node *ret;
+
+ ret = eina_hash_find(pd->node_hash, &focusable);
+
+ if (ret) return ret;
+
+ ERR("Focusable %p not registered in manager", focusable);
+
+ return NULL;
+}
+
+/**
+ * Free a node item and unlink this item from all direction
+ */
+static void
+node_item_free(Node *item)
+{
+ Node *n;
+ Eina_List *l;
+ //free the graph items
+ for(int i = 0;i < NODE_DIRECTIONS_COUNT; i++)
+ {
+ border_partners_set(item, i, NULL);
+ }
+
+ if (!item->tree.parent && item->tree.children)
+ {
+ ERR("Freeing the root with children is going to break the logical tree!");
+ }
+
+ if (item->tree.parent && item->tree.children)
+ {
+ Node *parent;
+
+ parent = item->tree.parent;
+ //reparent everything into the next layer
+ EINA_LIST_FOREACH(item->tree.children, l, n)
+ {
+ n->tree.parent = item->tree.parent;
+ }
+ parent->tree.children = eina_list_merge(parent->tree.children , item->tree.children);
+ }
+
+ if (item->tree.parent)
+ {
+ Node *parent;
+
+ parent = item->tree.parent;
+ T(parent).children = eina_list_remove(T(parent).children, item);
+ }
+
+ free(item);
+}
+
+
+//CALCULATING STUFF
+
+static inline int
+_distance(Eina_Rectangle node, Eina_Rectangle op, Dimension dim)
+{
+ int min, max, point;
+ int v1, v2;
+
+ if (dim == DIMENSION_X)
+ {
+ min = op.x;
+ max = eina_rectangle_max_x(&op);
+ point = node.x + node.w/2;
+ }
+ else
+ {
+ min = op.y;
+ max = eina_rectangle_max_y(&op);
+ point = node.y + node.h/2;
+ }
+
+ v1 = min - point;
+ v2 = max - point;
+
+ if (abs(v1) < abs(v2))
+ return v1;
+ else
+ return v2;
+}
+
+static inline void
+_calculate_node(Efl_Ui_Focus_Manager_Data *pd, Efl_Ui_Focus_Object *node, Dimension dim, Eina_List **pos, Eina_List **neg)
+{
+ Eina_Rectangle rect = EINA_RECTANGLE_INIT;
+ Efl_Ui_Focus_Object *op;
+ Efl_Ui_Focus_Object **focus_key;
+ int dim_min, dim_max;
+ Eina_Iterator *nodes;
+ int cur_pos_min = 0, cur_neg_min = 0;
+
+ nodes = eina_hash_iterator_key_new(pd->node_hash);
+ efl_ui_focus_object_geometry_get(node, &rect);
+
+ *pos = NULL;
+ *neg = NULL;
+
+ if (dim == DIMENSION_X)
+ {
+ dim_min = rect.y;
+ dim_max = rect.y + rect.h;
+ }
+ else
+ {
+ dim_min = rect.x;
+ dim_max = rect.x + rect.w;
+ }
+
+ EINA_ITERATOR_FOREACH(nodes, focus_key)
+ {
+ Eina_Rectangle op_rect = EINA_RECTANGLE_INIT;
+ int min, max;
+
+ op = *focus_key;
+ if (op == node) continue;
+
+ efl_ui_focus_object_geometry_get(op, &op_rect);
+
+ if (dim == DIMENSION_X)
+ {
+ min = op_rect.y;
+ max = eina_rectangle_max_y(&op_rect);
+ }
+ else
+ {
+ min = op_rect.x;
+ max = eina_rectangle_max_x(&op_rect);
+ }
+
+
+ /* two only way the calculation does make sense is if the two number
+ * lines are not disconnected.
+ * If they are connected one point of the 4 lies between the min and max of the other line
+ */
+ if (!((min <= max && max <= dim_min && dim_min <= dim_max) ||
+ (dim_min <= dim_max && dim_max <= min && min <= max)) &&
+ !eina_rectangle_intersection(&op_rect, &rect))
+ {
+ //this thing hits horizontal
+ int tmp_dis;
+
+ tmp_dis = _distance(rect, op_rect, dim);
+
+ if (tmp_dis < 0)
+ {
+ if (tmp_dis == cur_neg_min)
+ {
+ //add it
+ *neg = eina_list_append(*neg, op);
+ }
+ else if (tmp_dis > cur_neg_min
+ || cur_neg_min == 0) //init case
+ {
+ //nuke the old and add
+#ifdef DEBUG
+ printf("CORRECTION FOR %s-%s\n found anchor %s-%s in distance %d\n (%d,%d,%d,%d)\n (%d,%d,%d,%d)\n\n", DEBUG_TUPLE(node), DEBUG_TUPLE(op),
+ tmp_dis,
+ op_rect.x, op_rect.y, op_rect.w, op_rect.h,
+ rect.x, rect.y, rect.w, rect.h);
+#endif
+ *neg = eina_list_free(*neg);
+ *neg = eina_list_append(NULL, op);
+ cur_neg_min = tmp_dis;
+ }
+ }
+ else
+ {
+ if (tmp_dis == cur_pos_min)
+ {
+ //add it
+ *pos = eina_list_append(*pos, op);
+ }
+ else if (tmp_dis < cur_pos_min
+ || cur_pos_min == 0) //init case
+ {
+ //nuke the old and add
+#ifdef DEBUG
+ printf("CORRECTION FOR %s-%s\n found anchor %s-%s in distance %d\n (%d,%d,%d,%d)\n (%d,%d,%d,%d)\n\n", DEBUG_TUPLE(node), DEBUG_TUPLE(op),
+ tmp_dis,
+ op_rect.x, op_rect.y, op_rect.w, op_rect.h,
+ rect.x, rect.y, rect.w, rect.h);
+#endif
+ *pos = eina_list_free(*pos);
+ *pos = eina_list_append(NULL, op);
+ cur_pos_min = tmp_dis;
+ }
+ }
+
+
+#if 0
+ printf("(%d,%d,%d,%d)%s vs(%d,%d,%d,%d)%s\n", rect.x, rect.y, rect.w, rect.h, elm_widget_part_text_get(node, NULL), op_rect.x, op_rect.y, op_rect.w, op_rect.h, elm_widget_part_text_get(op, NULL));
+ printf("(%d,%d,%d,%d)\n", min, max, dim_min, dim_max);
+ printf("Candidate %d\n", tmp_dis);
+ if (anchor->anchor == NULL || abs(tmp_dis) < abs(distance)) //init case
+ {
+ distance = tmp_dis;
+ anchor->positive = tmp_dis > 0 ? EINA_FALSE : EINA_TRUE;
+ anchor->anchor = op;
+ //Helper for debugging wrong calculations
+
+ }
+#endif
+ }
+
+ }
+}
+
+#ifdef DEBUG
+static void
+_debug_node(Node *node)
+{
+ Eina_List *tmp = NULL;
+
+ if (!node) return;
+
+ printf("NODE %s-%s\n", DEBUG_TUPLE(node->focusable));
+
+#define DIR_LIST(dir) G(node).directions[dir].partners
+
+#define DIR_OUT(dir)\
+ tmp = DIR_LIST(dir); \
+ { \
+ Eina_List *list_node; \
+ Node *partner; \
+ printf("-"#dir"-> ("); \
+ EINA_LIST_FOREACH(tmp, list_node, partner) \
+ printf("%s-%s,", DEBUG_TUPLE(partner->focusable)); \
+ printf(")\n"); \
+ }
+
+ DIR_OUT(EFL_UI_FOCUS_DIRECTION_RIGHT)
+ DIR_OUT(EFL_UI_FOCUS_DIRECTION_LEFT)
+ DIR_OUT(EFL_UI_FOCUS_DIRECTION_UP)
+ DIR_OUT(EFL_UI_FOCUS_DIRECTION_DOWN)
+
+}
+#endif
+
+static void
+convert_border_set(Efl_Ui_Focus_Manager_Data *pd, Node *node, Eina_List *focusable_list, Efl_Ui_Focus_Direction dir)
+{
+ Eina_List *partners = NULL;
+ Efl_Ui_Focus_Object *obj;
+
+ EINA_LIST_FREE(focusable_list, obj)
+ {
+ Node *entry;
+
+ entry = node_get(pd, obj);
+ if (!entry)
+ {
+ CRI("Found a obj in graph without node-entry!");
+ return;
+ }
+ partners = eina_list_append(partners, entry);
+ }
+
+ border_partners_set(node, dir, partners);
+}
+
+static void
+dirty_flush(Efl_Ui_Focus_Manager *obj, Efl_Ui_Focus_Manager_Data *pd)
+{
+ Node *node;
+
+ efl_event_callback_call(obj, EFL_UI_FOCUS_MANAGER_EVENT_PRE_FLUSH, NULL);
+
+ EINA_LIST_FREE(pd->dirty, node)
+ {
+ Eina_List *x_partners_pos, *x_partners_neg;
+ Eina_List *y_partners_pos, *y_partners_neg;
+
+ _calculate_node(pd, node->focusable, DIMENSION_X, &x_partners_pos, &x_partners_neg);
+ _calculate_node(pd, node->focusable, DIMENSION_Y, &y_partners_pos, &y_partners_neg);
+
+ convert_border_set(pd, node, x_partners_pos, EFL_UI_FOCUS_DIRECTION_RIGHT);
+ convert_border_set(pd, node, x_partners_neg, EFL_UI_FOCUS_DIRECTION_LEFT);
+ convert_border_set(pd, node, y_partners_neg, EFL_UI_FOCUS_DIRECTION_UP);
+ convert_border_set(pd, node, y_partners_pos, EFL_UI_FOCUS_DIRECTION_DOWN);
+
+#ifdef DEBUG
+ _debug_node(node);
+#endif
+ }
+}
+static void
+dirty_add(Eo *obj, Efl_Ui_Focus_Manager_Data *pd, Node *dirty)
+{
+ //if (eina_list_data_find(pd->dirty, dirty)) return;
+ pd->dirty = eina_list_remove(pd->dirty, dirty);
+ pd->dirty = eina_list_append(pd->dirty, dirty);
+
+ efl_event_callback_call(obj, EFL_UI_FOCUS_MANAGER_EVENT_COORDS_DIRTY, NULL);
+}
+
+
+static void
+_node_new_geometery_cb(void *data, const Efl_Event *event)
+{
+ Node *node;
+ FOCUS_DATA(data)
+
+ node = node_get(pd, event->object);
+
+ dirty_add(data, pd, node);
+
+ return;
+}
+
+static void
+_focus_in_cb(void *data, const Efl_Event *event)
+{
+ efl_ui_focus_manager_focus(data, event->object);
+}
+
+static void
+_child_del(void *data, const Efl_Event *event)
+{
+ WRN("The manager itself catched a deletion of a child. BAD");
+ efl_ui_focus_manager_unregister(data, event->object);
+}
+
+EFL_CALLBACKS_ARRAY_DEFINE(focusable_node,
+ {EFL_EVENT_DEL, _child_del},
+ {EFL_GFX_EVENT_RESIZE, _node_new_geometery_cb},
+ {EFL_GFX_EVENT_MOVE, _node_new_geometery_cb},
+ //FIXME this is not correctly NOOOO ELM WIDGETS EVENTS HERE
+ {ELM_WIDGET_EVENT_FOCUSED, _focus_in_cb}
+);
+
+//=============================
+
+static Node*
+_register(Eo *obj, Efl_Ui_Focus_Manager_Data *pd, Efl_Ui_Focus_Object *child, Node *parent)
+{
+ Node *node;
+ if (!!eina_hash_find(pd->node_hash, &child))
+ {
+ ERR("Child %p is already registered in the graph", child);
+ return NULL;
+ }
+
+ node = node_new(child, obj);
+ eina_hash_add(pd->node_hash, &child, node);
+
+ //add the parent
+ if (parent)
+ {
+ T(node).parent = parent;
+ T(parent).children = eina_list_append(T(parent).children, node);
+ }
+
+ //listen to changes
+ efl_event_callback_array_add(child, focusable_node(), obj);
+
+ return node;
+}
+
+EOLIAN static Eina_Bool
+_efl_ui_focus_manager_register(Eo *obj, Efl_Ui_Focus_Manager_Data *pd, Efl_Ui_Focus_Object *child, Efl_Ui_Focus_Object *parent, Efl_Ui_Focus_Manager *redirect)
+{
+ Node *node = NULL;
+ Node *pnode = NULL;
+
+ EINA_SAFETY_ON_NULL_RETURN_VAL(child, EINA_FALSE);
+ EINA_SAFETY_ON_NULL_RETURN_VAL(parent, EINA_FALSE);
+
+ pnode = node_get(pd, parent);
+ if (!pnode) return EINA_FALSE;
+
+ node = _register(obj, pd, child, pnode);
+ if (!node) return EINA_FALSE;
+
+ if (!redirect)
+ {
+ node->type = NODE_TYPE_NORMAL;
+ }
+ else
+ {
+ node->type = NODE_TYPE_LISTENER;
+ node->data.listener.manager = redirect;
+ }
+
+ //mark dirty
+ dirty_add(obj, pd, node);
+
+ return EINA_TRUE;
+}
+
+EOLIAN static Eina_Bool
+_efl_ui_focus_manager_update_redirect(Eo *obj EINA_UNUSED, Efl_Ui_Focus_Manager_Data *pd, Efl_Ui_Focus_Object *child, Efl_Ui_Focus_Manager *redirect)
+{
+ Node *node = node_get(pd, child);
+
+ if (!node) return EINA_FALSE;
+ if (node->type != NODE_TYPE_LISTENER) return EINA_FALSE;
+
+ node->data.listener.manager = redirect;
+
+ return EINA_TRUE;
+}
+
+EOLIAN static Eina_Bool
+_efl_ui_focus_manager_update_parent(Eo *obj EINA_UNUSED, Efl_Ui_Focus_Manager_Data *pd, Efl_Ui_Focus_Object *child, Efl_Ui_Focus_Object *parent_obj)
+{
+ Node *node;
+ Node *parent;
+
+ EINA_SAFETY_ON_NULL_RETURN_VAL(parent_obj, EINA_FALSE);
+ EINA_SAFETY_ON_NULL_RETURN_VAL(child, EINA_FALSE);
+
+ node = node_get(pd, child);
+ parent = node_get(pd, parent_obj);
+
+ if (!node || !parent) return EINA_FALSE;
+
+ if (T(node).parent)
+ {
+ Node *old_parent;
+
+ old_parent = T(node).parent;
+
+ T(old_parent).children = eina_list_remove(T(old_parent).children, node);
+ }
+
+ T(node).parent = parent;
+
+ if (T(node).parent)
+ {
+ T(parent).children = eina_list_append(T(parent).children, node);
+ }
+
+ return EINA_TRUE;
+}
+
+static Eina_Bool
+_equal_set(Eina_List *none_nodes, Eina_List *nodes)
+{
+ Eina_List *n;
+ Node *node;
+
+ if (eina_list_count(nodes) != eina_list_count(none_nodes)) return EINA_FALSE;
+
+ EINA_LIST_FOREACH(nodes, n, node)
+ {
+ if (!eina_list_data_find(none_nodes, node->focusable))
+ return EINA_FALSE;
+ }
+
+ return EINA_TRUE;
+}
+
+EOLIAN static Eina_Bool
+_efl_ui_focus_manager_update_children(Eo *obj EINA_UNUSED, Efl_Ui_Focus_Manager_Data *pd, Efl_Ui_Focus_Object *parent, Eina_List *order)
+{
+ Node *pnode;
+
+ pnode = node_get(pd, parent);
+
+ if (!pnode)
+ return EINA_FALSE;
+
+ if (!_equal_set(order, T(pnode).children))
+ {
+ ERR("Set of children is not equal");
+ return EINA_FALSE;
+ }
+
+ T(pnode).children = order;
+
+ return EINA_TRUE;
+}
+
+EOLIAN static void
+_efl_ui_focus_manager_unregister(Eo *obj EINA_UNUSED, Efl_Ui_Focus_Manager_Data *pd, Efl_Ui_Focus_Object *child)
+{
+ Node *node;
+
+ node = node_get(pd, child);
+
+ if (!node) return;
+
+ //remove the object from the stack if it hasnt dont that until now
+ //after this its not at the top anymore
+ //elm_widget_focus_set(node->focusable, EINA_FALSE);
+ //delete again from the list, for the case it was not at the top
+ pd->focus_stack = eina_list_remove(pd->focus_stack, node);
+
+ //add all neighboors of the node to the dirty list
+ for(int i = 0; i < 4; i++)
+ {
+ Node *partner;
+ Eina_List *n;
+
+ EINA_LIST_FOREACH(node->graph.directions[i].partners, n, partner)
+ {
+ dirty_add(obj, pd, partner);
+ }
+ }
+
+ //remove from the dirty parts
+ pd->dirty = eina_list_remove(pd->dirty, node);
+
+ eina_hash_del_by_key(pd->node_hash, &child);
+}
+
+EOLIAN static void
+_efl_ui_focus_manager_redirect_set(Eo *obj EINA_UNUSED, Efl_Ui_Focus_Manager_Data *pd, Efl_Ui_Focus_Manager *redirect)
+{
+ if (pd->redirect == redirect) return;
+
+ if (pd->redirect)
+ efl_unref(pd->redirect);
+
+ pd->redirect = redirect;
+
+ if (pd->redirect)
+ efl_ref(pd->redirect);
+}
+
+EOLIAN static Efl_Ui_Focus_Manager *
+_efl_ui_focus_manager_redirect_get(Eo *obj EINA_UNUSED, Efl_Ui_Focus_Manager_Data *pd)
+{
+ return pd->redirect;
+}
+
+static void
+_free_node(void *data)
+{
+ Node *node = data;
+ FOCUS_DATA(node->manager);
+
+ efl_event_callback_array_del(node->focusable, focusable_node(), node->manager);
+
+ if (pd->root != data)
+ {
+ node_item_free(node);
+ }
+}
+
+EOLIAN static Efl_Object *
+_efl_ui_focus_manager_efl_object_constructor(Eo *obj, Efl_Ui_Focus_Manager_Data *pd)
+{
+ pd->node_hash = eina_hash_pointer_new(_free_node);
+ return efl_constructor(efl_super(obj, MY_CLASS));
+}
+
+EOLIAN static Efl_Object *
+_efl_ui_focus_manager_efl_object_provider_find(Eo *obj, Efl_Ui_Focus_Manager_Data *pd EINA_UNUSED, const Efl_Object *klass)
+{
+ if (klass == MY_CLASS)
+ return obj;
+
+ return efl_provider_find(efl_super(obj, MY_CLASS), klass);
+}
+
+EOLIAN static void
+_efl_ui_focus_manager_efl_object_destructor(Eo *obj, Efl_Ui_Focus_Manager_Data *pd)
+{
+ eina_list_free(pd->focus_stack);
+ eina_list_free(pd->dirty);
+
+ eina_hash_free(pd->node_hash);
+
+ if (pd->root)
+ node_item_free(pd->root);
+ pd->root = NULL;
+
+ efl_destructor(efl_super(obj, MY_CLASS));
+}
+
+typedef struct {
+ Eina_Iterator iterator;
+ Eina_Iterator *real_iterator;
+ Efl_Ui_Focus_Manager *object;
+} Border_Elements_Iterator;
+
+static Eina_Bool
+_iterator_next(Border_Elements_Iterator *it, void **data)
+{
+ Node *node;
+
+ while(eina_iterator_next(it->real_iterator, (void**)&node))
+ {
+ for(int i = 0 ;i < NODE_DIRECTIONS_COUNT; i++)
+ {
+ if (!node->graph.directions[i].partners)
+ {
+ *data = node->focusable;
+ return EINA_TRUE;
+ }
+ }
+ }
+ return EINA_FALSE;
+}
+
+static Elm_Layout *
+_iterator_get_container(Border_Elements_Iterator *it)
+{
+ return it->object;
+}
+
+static void
+_iterator_free(Border_Elements_Iterator *it)
+{
+ eina_iterator_free(it->real_iterator);
+ free(it);
+}
+
+EOLIAN static Eina_Iterator*
+_efl_ui_focus_manager_border_elements_get(Eo *obj, Efl_Ui_Focus_Manager_Data *pd)
+{
+ Border_Elements_Iterator *it;
+
+ dirty_flush(obj, pd);
+
+ it = calloc(1, sizeof(Border_Elements_Iterator));
+
+ EINA_MAGIC_SET(&it->iterator, EINA_MAGIC_ITERATOR);
+
+ it->real_iterator = eina_hash_iterator_data_new(pd->node_hash);
+ it->iterator.version = EINA_ITERATOR_VERSION;
+ it->iterator.next = FUNC_ITERATOR_NEXT(_iterator_next);
+ it->iterator.get_container = FUNC_ITERATOR_GET_CONTAINER(_iterator_get_container);
+ it->iterator.free = FUNC_ITERATOR_FREE(_iterator_free);
+ it->object = obj;
+
+ return (Eina_Iterator*) it;
+}
+
+static Node*
+_no_history_element(Eina_Hash *node_hash)
+{
+ //nothing is selected yet, just try to use the first element in the iterator
+ Eina_Iterator *iter;
+ Node *upper;
+
+ iter = eina_hash_iterator_data_new(node_hash);
+
+ if (!eina_iterator_next(iter, (void**)&upper))
+ return NULL;
+ else
+ return upper;
+ eina_iterator_free(iter);
+}
+
+static Node*
+_coords_movement(Efl_Ui_Focus_Manager_Data *pd, Node *upper, Efl_Ui_Focus_Direction direction)
+{
+ Node *candidate;
+ Eina_List *node;
+
+ //we are searcing which of the partners is lower to the history
+ EINA_LIST_REVERSE_FOREACH(pd->focus_stack, node, candidate)
+ {
+ if (eina_list_data_find(G(upper).directions[direction].partners, candidate))
+ {
+ //this is the next accessable part
+ return candidate;
+ }
+ }
+
+ //if we havent found anything in the history, just use the first partner ... we have to start somewhere
+ //FIXME maybe decide coordinate wise?
+ return eina_list_data_get(G(upper).directions[direction].partners);
+}
+
+
+static Node*
+_parent_item(Node *node, Eina_Bool next)
+{
+ Node *parent;
+ Eina_List *lnode;
+
+ parent = T(node).parent;
+ lnode = eina_list_data_find_list(T(parent).children, node);
+
+ if (next)
+ lnode = eina_list_next(lnode);
+ else
+ lnode = eina_list_prev(lnode);
+
+ if (lnode)
+ return eina_list_data_get(lnode);
+ return NULL;
+}
+
+static Node*
+_next(Node *node)
+{
+ Node *n;
+
+ //Case 1 we are having children
+ if (T(node).children)
+ return eina_list_data_get(T(node).children);
+
+ //case 2 we are the root and we dont have children, return ourself
+ if (!T(node).parent)
+ return node;
+
+ //case 3 we are not at the end of the parents list
+ n = _parent_item(node, EINA_TRUE);
+ if (n)
+ return n;
+
+ //case 4 we are at the end of the parents list
+ n = node;
+ while(T(n).parent)
+ {
+ Node *parent_next;
+
+ parent_next = _parent_item(n, EINA_TRUE);
+
+ if (parent_next)
+ return parent_next;
+
+ n = T(n).parent;
+ }
+ //this is then the root again
+ return n;
+}
+
+static Node*
+_prev(Node *node)
+{
+ Node *n = NULL;
+
+ //this is the root there is no parent
+ if (!T(node).parent)
+ {
+ Node *subtree;
+
+ subtree = node;
+ //search the most down right item
+ while(T(subtree).children)
+ {
+ subtree = eina_list_last_data_get(T(subtree).children);
+ }
+ return subtree;
+ }
+
+ n =_parent_item(node, EINA_FALSE);
+ //case 1 there is a item in the parent previous to node, which has children
+ if (n && T(n).children)
+ return eina_list_last_data_get(T(n).children);
+
+ //case 2 there is a item in the parent preivous to node, which has no children
+ if (n)
+ return n;
+
+ //case 3 there is a no item in the parent provious to this one
+ //if (!n)
+ return T(node).parent;
+}
+
+
+static Node*
+_logical_movement(Efl_Ui_Focus_Manager_Data *pd EINA_UNUSED, Node *upper, Efl_Ui_Focus_Direction direction)
+{
+ if (direction == EFL_UI_FOCUS_DIRECTION_NEXT)
+ return _next(upper);
+ else
+ return _prev(upper);
+}
+
+EOLIAN static Efl_Ui_Focus_Object*
+_efl_ui_focus_manager_request_move(Eo *obj EINA_UNUSED, Efl_Ui_Focus_Manager_Data *pd, Efl_Ui_Focus_Direction direction)
+{
+ dirty_flush(obj, pd);
+
+ EINA_SAFETY_ON_FALSE_RETURN_VAL(DIRECTION_CHECK(direction), NULL);
+
+ if (pd->redirect)
+ return efl_ui_focus_manager_request_move(pd->redirect, direction);
+ else
+ {
+ Node *upper = NULL, *dir = NULL;
+
+ upper = eina_list_last_data_get(pd->focus_stack);
+
+ if (!upper)
+ {
+ upper = _no_history_element(pd->node_hash);
+ if (upper)
+ return upper->focusable;
+ return NULL;
+
+ }
+#ifdef DEBUG
+ _debug_node(upper);
+#endif
+ if (direction == EFL_UI_FOCUS_DIRECTION_PREV
+ || direction == EFL_UI_FOCUS_DIRECTION_NEXT)
+ dir = _logical_movement(pd, upper, direction);
+ else
+ dir = _coords_movement(pd, upper, direction);
+
+ //return the widget
+ if (dir)
+ return dir->focusable;
+ else
+ return NULL;
+ }
+}
+
+EOLIAN static void
+_efl_ui_focus_manager_focus(Eo *obj, Efl_Ui_Focus_Manager_Data *pd, Efl_Ui_Focus_Object *focus)
+{
+ Node *node;
+ Node *old_focus;
+
+ EINA_SAFETY_ON_NULL_RETURN(focus);
+
+ //check if node is part of this manager object
+ node = node_get(pd, focus);
+ if (!node) return;
+
+ //check if this is already the focused object
+ old_focus = eina_list_last_data_get(pd->focus_stack);
+
+ //check if this is already at the top
+ if (old_focus && old_focus->focusable == focus) return;
+
+ //remove the object from the list and add it again
+ pd->focus_stack = eina_list_remove(pd->focus_stack, node);
+ pd->focus_stack = eina_list_append(pd->focus_stack, node);
+
+ //populate the new change
+ if (old_focus) efl_ui_focus_object_focus_set(old_focus->focusable, EINA_FALSE);
+ efl_ui_focus_object_focus_set(node->focusable, EINA_TRUE);
+
+ //now check if this is also a listener object
+ if (node->type == NODE_TYPE_LISTENER)
+ {
+ Efl_Ui_Focus_Manager *redirect;
+
+ redirect = node->data.listener.manager;
+
+ efl_ui_focus_manager_redirect_set(obj, redirect);
+ }
+}
+
+EOLIAN static Efl_Ui_Focus_Object*
+_efl_ui_focus_manager_move(Eo *obj EINA_UNUSED, Efl_Ui_Focus_Manager_Data *pd, Efl_Ui_Focus_Direction direction)
+{
+ Efl_Ui_Focus_Object *candidate;
+
+ EINA_SAFETY_ON_FALSE_RETURN_VAL(DIRECTION_CHECK(direction), NULL);
+
+ if (pd->redirect)
+ {
+ return efl_ui_focus_manager_move(pd->redirect, direction);
+ }
+ else
+ {
+ candidate = efl_ui_focus_manager_request_move(obj, direction);
+ if (candidate)
+ efl_ui_focus_manager_focus(obj, candidate);
+ }
+
+#ifdef DEBUG
+ printf("Focus, MOVE %s %s\n", DEBUG_TUPLE(candidate));
+#endif
+ return candidate;
+}
+
+EOLIAN static void
+_efl_ui_focus_manager_root_set(Eo *obj EINA_UNUSED, Efl_Ui_Focus_Manager_Data *pd, Efl_Ui_Focus_Object *root)
+{
+ Node *node;
+
+ if (pd->root)
+ {
+ ERR("Root element can only be set once!");
+ return;
+ }
+
+ node = _register(obj, pd, root, NULL);
+
+ pd->root = node;
+}
+
+EOLIAN static Efl_Ui_Focus_Object*
+_efl_ui_focus_manager_root_get(Eo *obj EINA_UNUSED, Efl_Ui_Focus_Manager_Data *pd)
+{
+ if (!pd->root) return NULL;
+
+ return pd->root->focusable;
+}
+
+EOLIAN static Efl_Object*
+_efl_ui_focus_manager_efl_object_finalize(Eo *obj, Efl_Ui_Focus_Manager_Data *pd)
+{
+ Efl_Object *result;
+
+ if (!pd->root)
+ {
+ ERR("Constructing failed. No root element set.");
+ return NULL;
+ }
+
+ result = efl_finalize(efl_super(obj, MY_CLASS));
+
+ return result;
+}
+
+#include "efl_ui_focus_manager.eo.c" \ No newline at end of file
diff --git a/src/lib/elementary/efl_ui_focus_manager.eo b/src/lib/elementary/efl_ui_focus_manager.eo
new file mode 100644
index 0000000000..ab70c27c21
--- /dev/null
+++ b/src/lib/elementary/efl_ui_focus_manager.eo
@@ -0,0 +1,129 @@
+enum Efl.Ui.Focus.Direction {
+ right = 0,
+ left = 1,
+ down = 2,
+ up = 3,
+ next = 4,
+ prev = 5,
+ last = 6
+}
+
+
+class Efl.Ui.Focus.Manager (Efl.Object) {
+ methods {
+ move {
+ [[Move the focus into the given direction
+
+ This call flushes all changes.
+ This means all changes between the last flush and now are computed
+ ]]
+ params {
+ direction : Efl.Ui.Focus.Direction; [[The direction to move to]]
+ }
+ return : Efl.Ui.Focus.Object; [[The element which is now focused]]
+ }
+ request_move {
+ [[Returns the object which would be the next object to focus in the given direction]]
+ params {
+ direction : Efl.Ui.Focus.Direction;
+ }
+ return : Efl.Ui.Focus.Object;
+ }
+ register {
+ [[Register a new item in the graph.
+
+ The parent has to be none null, it will be used as the parent in the logical tree.
+ The redirect argument will be set as redirect property on that manager, once child gets focused.
+ ]]
+ params {
+ child : Efl.Ui.Focus.Object @nonull; [[The object to register]]
+ parent : Efl.Ui.Focus.Object @nonull; [[The parent to use in the logical tree]]
+ redirect : Efl.Ui.Focus.Manager; [[The redirect manager to set once this child is focused can be NULL for no redirect]]
+ }
+ return : bool; [[$true if it was successfull $false if not]]
+ }
+ update_redirect {
+ [[Set a new redirect object for the given child
+
+ Once the child is focused the redirect manager will be set in the redirect property.
+ Set to $null if nothing should happen
+ ]]
+ params {
+ child : Efl.Ui.Focus.Object @nonull;
+ redirect : Efl.Ui.Focus.Manager; [[Once $child got focused this element will be set as redirect]]
+ }
+ return : bool;
+ }
+ update_parent {
+ [[Set a new logical parent for the given child]]
+ params {
+ child : Efl.Ui.Focus.Object @nonull; [[The child to update]]
+ parent : Efl.Ui.Focus.Object @nonull; [[The parent which now will be the logical parent of child]]
+ }
+ return : bool;
+ }
+ update_children {
+ [[Give the list of children a different order]]
+ params {
+ parent : Efl.Ui.Focus.Object @nonull; [[the parent to update]]
+ children : list<Efl.Ui.Focus.Object>; [[the list with the new order]]
+ }
+ return : bool;
+ }
+ unregister {
+ [[unregister the given item from the graph]]
+ params {
+ child : Efl.Ui.Focus.Object;
+ }
+ }
+ focus {
+ [[Make the given object the currently focused object in this manager.
+
+ The object has to be part of this manager object.
+ If you want to focus something in the redirect manager, just call the function on the redirect manager]]
+ params {
+ focus : Efl.Ui.Focus.Object @nonull;
+ }
+ }
+ @property redirect {
+ [[Add a another manager to serve the move requests.
+
+ If this value is set all move requests are redirected to this manager object.
+ Set it to $null once nothing should be redirected anymore.]]
+ values {
+ redirect : Efl.Ui.Focus.Manager;
+ }
+ }
+ @property border_elements {
+ [[The list of elements which are at the border of the graph
+ This means one of the relations right,left or down,up are not set.
+
+ This call flushes all changes. see @Efl.Ui.Focus.Manager.move
+ ]]
+ get {
+ }
+ values {
+ border_elements : iterator<Efl.Ui.Focus.Object>;
+ }
+ }
+ @property root {
+ [[Root node for all logical subtrees.
+
+ This property can only be set once.
+ ]]
+ values {
+ root : Efl.Ui.Focus.Object @nonull; [[Will be registered into this manager object]]
+ }
+ }
+ }
+ implements {
+ Efl.Object.constructor;
+ Efl.Object.finalize;
+ Efl.Object.provider_find;
+ Efl.Object.destructor;
+ }
+ events {
+ pre,flush; [[Emitted once the graph calculationg will be performed]]
+ coords,dirty; [[Emitted once the graph is dirty, this means there are potential changes in border_elements you want to know about]]
+ }
+} \ No newline at end of file
diff --git a/src/lib/elementary/efl_ui_focus_manager_sub.c b/src/lib/elementary/efl_ui_focus_manager_sub.c
new file mode 100644
index 0000000000..98fa07503f
--- /dev/null
+++ b/src/lib/elementary/efl_ui_focus_manager_sub.c
@@ -0,0 +1,188 @@
+#ifdef HAVE_CONFIG_H
+# include "elementary_config.h"
+#endif
+
+#include <Elementary.h>
+#include "elm_priv.h"
+
+#define MY_CLASS EFL_UI_FOCUS_MANAGER_SUB_CLASS
+#define MY_DATA(o, p) Efl_Ui_Focus_Manager_Sub_Data *pd = efl_data_scope_get(o, MY_CLASS);
+typedef struct {
+ Efl_Ui_Focus_Manager *manager;
+ Efl_Ui_Focus_Manager *parent;
+ Eina_Bool self_dirty;
+ Eina_List *current_border;
+} Efl_Ui_Focus_Manager_Sub_Data;
+
+static Eina_List*
+_set_a_without_b(Eina_List *a, Eina_List *b)
+{
+ Eina_List *a_out = NULL, *node;
+ void *data;
+
+ a_out = eina_list_clone(a);
+
+ EINA_LIST_FOREACH(b, node, data)
+ {
+ a_out = eina_list_remove(a_out, data);
+ }
+
+ return a_out;
+}
+
+static void
+_border_flush(Eo *obj, Efl_Ui_Focus_Manager_Sub_Data *pd)
+{
+ Eina_Iterator *borders;
+ Eina_List *selection, *tmp;
+ Efl_Ui_Focus_Object *node;
+
+ borders = efl_ui_focus_manager_border_elements_get(obj);
+ selection = efl_ui_focus_manager_sub_select_set(obj, borders);
+
+ //elements which are not in the current border elements
+ tmp = eina_list_clone(pd->current_border);
+ tmp = _set_a_without_b(tmp , selection);
+
+ EINA_LIST_FREE(tmp, node)
+ {
+ efl_ui_focus_manager_unregister(pd->manager, node);
+ }
+
+ //set of the elements which are new without those which are currently registered
+ tmp = eina_list_clone(selection);
+ tmp = _set_a_without_b(tmp, pd->current_border);
+
+ EINA_LIST_FREE(tmp, node)
+ {
+ efl_ui_focus_manager_register(pd->manager, node, obj, obj);
+ }
+
+ eina_list_free(pd->current_border);
+ pd->current_border = selection;
+}
+
+static void
+_border_unregister(Eo *obj EINA_UNUSED, Efl_Ui_Focus_Manager_Sub_Data *pd)
+{
+ Efl_Ui_Focus_Object *node;
+
+ EINA_LIST_FREE(pd->current_border, node)
+ {
+ efl_ui_focus_manager_unregister(pd->manager, node);
+ }
+
+ pd->current_border = NULL;
+}
+
+static void
+_parent_manager_pre_flush(void *data, const Efl_Event *ev EINA_UNUSED)
+{
+ MY_DATA(data, pd);
+
+ if (!pd->self_dirty) return; //we are not interested
+
+ _border_flush(data, pd);
+}
+
+EFL_CALLBACKS_ARRAY_DEFINE(parent_manager,
+ {EFL_UI_FOCUS_MANAGER_EVENT_PRE_FLUSH, _parent_manager_pre_flush}
+);
+
+static void
+_parent_set(Eo *obj, Efl_Ui_Focus_Manager_Sub_Data *pd, Efl_Ui_Focus_Manager *manager)
+{
+ if (pd->manager)
+ {
+ //remove ourself from the manager
+ efl_ui_focus_manager_unregister(pd->manager, obj);
+
+
+ efl_event_callback_array_del(pd->manager, parent_manager(), obj);
+ _border_unregister(obj, pd);
+ }
+
+ pd->manager = manager;
+
+ if (pd->manager)
+ {
+ //register our own root in the upper manager
+ efl_ui_focus_manager_register(pd->manager, obj, pd->parent, obj);
+
+ //listen to the manager
+ efl_event_callback_array_add(pd->manager, parent_manager(), obj);
+ _border_flush(obj, pd);
+ }
+}
+
+static void
+_self_parent_change(void *data EINA_UNUSED, const Efl_Event *ev)
+{
+ MY_DATA(ev->object , pd);
+
+ if (pd->manager == ev->info) return;
+
+ _parent_set(ev->object, pd, ev->info);
+}
+
+static void
+_self_manager_dirty(void *data EINA_UNUSED, const Efl_Event *ev)
+{
+ MY_DATA(ev->object , pd);
+
+ pd->self_dirty = EINA_TRUE;
+}
+
+EFL_CALLBACKS_ARRAY_DEFINE(self_manager,
+ {EFL_UI_FOCUS_USER_EVENT_MANAGER_CHANGED, _self_parent_change},
+ {EFL_UI_FOCUS_MANAGER_EVENT_COORDS_DIRTY, _self_manager_dirty},
+);
+EOLIAN static void
+_efl_ui_focus_manager_sub_parent_set(Eo *obj, Efl_Ui_Focus_Manager_Sub_Data *pd, Efl_Ui_Focus_Object *node)
+{
+ if (node == pd->parent) return;
+
+ pd->parent = node;
+
+ efl_ui_focus_manager_update_parent(pd->manager, obj, node);
+}
+
+EOLIAN static Efl_Ui_Focus_Object*
+_efl_ui_focus_manager_sub_parent_get(Eo *obj EINA_UNUSED, Efl_Ui_Focus_Manager_Sub_Data *pd EINA_UNUSED)
+{
+ return pd->parent;
+}
+
+EOLIAN static Efl_Object*
+_efl_ui_focus_manager_sub_efl_object_constructor(Eo *obj, Efl_Ui_Focus_Manager_Sub_Data *pd EINA_UNUSED)
+{
+ efl_event_callback_array_add(obj, self_manager(), NULL);
+
+ return efl_constructor(efl_super(obj, MY_CLASS));
+}
+
+EOLIAN static void
+_efl_ui_focus_manager_sub_efl_object_destructor(Eo *obj, Efl_Ui_Focus_Manager_Sub_Data *pd)
+{
+ efl_event_callback_array_del(obj, self_manager(), NULL);
+
+ _parent_set(obj, pd, NULL);
+
+ return efl_destructor(efl_super(obj, MY_CLASS));
+}
+
+EOLIAN static Efl_Object*
+_efl_ui_focus_manager_sub_efl_object_finalize(Eo *obj, Efl_Ui_Focus_Manager_Sub_Data *pd)
+{
+ Efl_Ui_Focus_Manager *manager;
+
+ manager = efl_ui_focus_user_manager_get(obj);
+
+ _parent_set(obj, pd, manager);
+
+ return efl_finalize(efl_super(obj, MY_CLASS));
+}
+
+
+
+#include "efl_ui_focus_manager_sub.eo.c" \ No newline at end of file
diff --git a/src/lib/elementary/efl_ui_focus_manager_sub.eo b/src/lib/elementary/efl_ui_focus_manager_sub.eo
new file mode 100644
index 0000000000..984b7e67bb
--- /dev/null
+++ b/src/lib/elementary/efl_ui_focus_manager_sub.eo
@@ -0,0 +1,25 @@
+abstract Efl.Ui.Focus.Manager.Sub (Efl.Ui.Focus.Manager, Efl.Ui.Focus.Object, Efl.Ui.Focus.User)
+{
+ methods {
+ select_set {
+ params {
+ objects : iterator<Efl.Ui.Focus.Object>;
+ }
+ return : list<Efl.Ui.Focus.Object>;
+ }
+ @property parent {
+ values {
+ node : Efl.Ui.Focus.Object;
+ }
+ }
+ }
+ implements {
+ @empty .select_set;
+ @empty Efl.Ui.Focus.Object.geometry_get;
+ @empty Efl.Ui.Focus.Object.focus.get;
+ @empty Efl.Ui.Focus.User.manager.get;
+ Efl.Object.constructor;
+ Efl.Object.destructor;
+ Efl.Object.finalize;
+ }
+} \ No newline at end of file
diff --git a/src/lib/elementary/efl_ui_focus_object.c b/src/lib/elementary/efl_ui_focus_object.c
new file mode 100644
index 0000000000..16d6c3b33e
--- /dev/null
+++ b/src/lib/elementary/efl_ui_focus_object.c
@@ -0,0 +1,30 @@
+#ifdef HAVE_CONFIG_H
+# include "elementary_config.h"
+#endif
+
+#include <Elementary.h>
+#include "elm_priv.h"
+
+typedef struct {
+
+} Efl_Ui_Focus_Object_Data;
+
+EOLIAN static void
+_efl_ui_focus_object_focus_set(Eo *obj, Efl_Ui_Focus_Object_Data *pd EINA_UNUSED, Eina_Bool focus)
+{
+ const Efl_Event_Description *desc;
+
+ if (focus)
+ desc = EFL_UI_FOCUS_OBJECT_EVENT_FOCUS;
+ else
+ desc = EFL_UI_FOCUS_OBJECT_EVENT_UNFOCUS;
+
+ efl_event_callback_call(obj, desc, NULL);
+}
+
+
+#include "efl_ui_focus_object.eo.c"
+typedef struct {
+
+} Efl_Ui_Focus_User_Data;
+#include "efl_ui_focus_user.eo.c" \ No newline at end of file
diff --git a/src/lib/elementary/efl_ui_focus_object.eo b/src/lib/elementary/efl_ui_focus_object.eo
new file mode 100644
index 0000000000..9db36b2022
--- /dev/null
+++ b/src/lib/elementary/efl_ui_focus_object.eo
@@ -0,0 +1,28 @@
+mixin Efl.Ui.Focus.Object
+{
+ [[Functions of focusable objects]]
+ methods {
+ geometry_get {
+ params {
+ @out rect : Eina.Rectangle;
+ }
+ }
+ @property focus {
+ [[This gets called by the manager and should never be called by someone else
+
+ It can be used by a implementation of a focus object to adapt to changes which are needed
+ ]]
+ values {
+ focus : bool;
+ }
+ }
+ }
+ implements {
+ @empty .geometry_get;
+ @empty .focus.get;
+ }
+ events {
+ focus;
+ unfocus;
+ }
+} \ No newline at end of file
diff --git a/src/lib/elementary/efl_ui_focus_user.eo b/src/lib/elementary/efl_ui_focus_user.eo
new file mode 100644
index 0000000000..173988f08d
--- /dev/null
+++ b/src/lib/elementary/efl_ui_focus_user.eo
@@ -0,0 +1,18 @@
+mixin Efl.Ui.Focus.User {
+ methods {
+ @property manager {
+ get {
+
+ }
+ values {
+ manager : Efl.Ui.Focus.Manager;
+ }
+ }
+ }
+ implements {
+ @empty .manager.get;
+ }
+ events {
+ manager,changed : Efl.Ui.Focus.Manager; [[emitted if a new manager is the parent for this one]]
+ }
+} \ No newline at end of file
diff --git a/src/tests/elementary/elm_suite.c b/src/tests/elementary/elm_suite.c
index 0507325a5c..3cda65ffef 100644
--- a/src/tests/elementary/elm_suite.c
+++ b/src/tests/elementary/elm_suite.c
@@ -81,6 +81,8 @@ static const Efl_Test_Case etc[] = {
{ "widget_text", elm_code_test_widget_text },
{ "widget_selection", elm_code_test_widget_selection },
{ "widget_undo", elm_code_test_widget_undo },
+ { "elm_focus", elm_test_focus},
+ { "elm_focus_sub", elm_test_focus_sub},
{ NULL, NULL }
};
diff --git a/src/tests/elementary/elm_suite.h b/src/tests/elementary/elm_suite.h
index 076bb60154..43a69592df 100644
--- a/src/tests/elementary/elm_suite.h
+++ b/src/tests/elementary/elm_suite.h
@@ -69,6 +69,8 @@ void elm_test_panes(TCase *tc);
void elm_test_slideshow(TCase *tc);
void elm_test_spinner(TCase *tc);
void elm_test_plug(TCase *tc);
+void elm_test_focus(TCase *tc);
+void elm_test_focus_sub(TCase *tc);
void elm_code_file_test_load(TCase *tc);
void elm_code_file_test_memory(TCase *tc);
diff --git a/src/tests/elementary/elm_test_focus.c b/src/tests/elementary/elm_test_focus.c
new file mode 100644
index 0000000000..044d391897
--- /dev/null
+++ b/src/tests/elementary/elm_test_focus.c
@@ -0,0 +1,347 @@
+#include "elm_test_focus_common.h"
+
+START_TEST(focus_unregister_twice)
+{
+ elm_init(1, NULL);
+ Efl_Ui_Focus_Object *r1 = efl_add(FOCUS_TEST_CLASS, NULL);
+ Efl_Ui_Focus_Object *r2 = efl_add(FOCUS_TEST_CLASS, NULL);
+
+ Efl_Ui_Focus_Manager *m = efl_add(EFL_UI_FOCUS_MANAGER_CLASS, NULL,
+ efl_ui_focus_manager_root_set(efl_added, r1)
+ );
+
+ fail_if(!efl_ui_focus_manager_register(m, r2, r1, NULL));
+
+ efl_ui_focus_manager_unregister(m, r1);
+ efl_ui_focus_manager_unregister(m, r1);
+ efl_ui_focus_manager_unregister(m, r1);
+
+ efl_del(r2);
+ efl_del(r1);
+ efl_del(m);
+
+ elm_shutdown();
+}
+END_TEST
+
+START_TEST(focus_register_twice)
+{
+ elm_init(1, NULL);
+
+ Efl_Ui_Focus_Object *r1 = elm_focus_test_object_new("r1", 0, 0, 10, 10);
+ Efl_Ui_Focus_Object *r2 = elm_focus_test_object_new("r2", 0, 10, 10, 10);
+
+ Efl_Ui_Focus_Manager *m = efl_add(EFL_UI_FOCUS_MANAGER_CLASS, NULL,
+ efl_ui_focus_manager_root_set(efl_added, r1)
+ );
+
+ fail_if(!efl_ui_focus_manager_register(m, r2, r1, NULL));
+ fail_if(efl_ui_focus_manager_register(m, r2, r1, NULL));
+
+ efl_del(r1);
+ efl_del(m);
+
+ elm_shutdown();
+}
+END_TEST
+
+START_TEST(pos_check)
+{
+ Efl_Ui_Focus_Manager *m;
+ Efl_Ui_Focus_Object *middle, *east, *west, *north, *south;
+
+ elm_init(1, NULL);
+
+ elm_focus_test_setup_cross(&middle, &south, &north, &east, &west);
+
+ m = efl_add(EFL_UI_FOCUS_MANAGER_CLASS, NULL,
+ efl_ui_focus_manager_root_set(efl_added, middle)
+ );
+
+ efl_ui_focus_manager_register(m, north, middle, NULL);
+ efl_ui_focus_manager_register(m, south, middle, NULL);
+ efl_ui_focus_manager_register(m, west, middle, NULL);
+ efl_ui_focus_manager_register(m, east, middle, NULL);
+
+#define CHECK(obj, r,l,u,d) \
+ efl_ui_focus_manager_focus(m, obj); \
+ ck_assert_ptr_eq(efl_ui_focus_manager_move(m, EFL_UI_FOCUS_DIRECTION_RIGHT), r); \
+ efl_ui_focus_manager_focus(m, obj); \
+ ck_assert_ptr_eq(efl_ui_focus_manager_move(m, EFL_UI_FOCUS_DIRECTION_LEFT), l); \
+ efl_ui_focus_manager_focus(m, obj); \
+ ck_assert_ptr_eq(efl_ui_focus_manager_move(m, EFL_UI_FOCUS_DIRECTION_UP), u); \
+ efl_ui_focus_manager_focus(m, obj); \
+ ck_assert_ptr_eq(efl_ui_focus_manager_move(m, EFL_UI_FOCUS_DIRECTION_DOWN), d); \
+ efl_ui_focus_manager_focus(m, obj);
+
+ CHECK(middle, east, west, north, south)
+ CHECK(east, NULL, middle, NULL, NULL)
+ CHECK(west, middle, NULL, NULL, NULL)
+ CHECK(north, NULL, NULL, NULL, middle)
+ CHECK(south, NULL, NULL, middle, NULL)
+
+ efl_del(middle);
+ efl_del(south);
+ efl_del(north);
+ efl_del(east);
+ efl_del(west);
+
+ elm_shutdown();
+}
+END_TEST
+
+START_TEST(redirect)
+{
+ elm_init(1, NULL);
+
+ TEST_OBJ_NEW(root, 0, 0, 20, 20);
+ TEST_OBJ_NEW(one, 0, 0, 20, 20);
+ TEST_OBJ_NEW(two, 20, 0, 20, 20);
+
+ Efl_Ui_Focus_Manager *m = efl_add(EFL_UI_FOCUS_MANAGER_CLASS, NULL,
+ efl_ui_focus_manager_root_set(efl_added, root)
+ );
+
+ Efl_Ui_Focus_Manager *m2 = efl_add(EFL_UI_FOCUS_MANAGER_CLASS, NULL,
+ efl_ui_focus_manager_root_set(efl_added, one)
+ );
+
+ efl_ui_focus_manager_register(m2, two, one, NULL);
+
+ efl_ui_focus_manager_redirect_set(m, m2);
+ efl_ui_focus_manager_focus(m2, one);
+
+ ck_assert_ptr_eq(efl_ui_focus_manager_move(m, EFL_UI_FOCUS_DIRECTION_RIGHT), two);
+
+ elm_shutdown();
+}
+END_TEST
+
+START_TEST(border_check)
+{
+ Efl_Ui_Focus_Manager *m;
+ Efl_Ui_Focus_Object *middle, *east, *west, *north, *south;
+ Eina_List *list = NULL;
+ Eina_Iterator *iter;
+ Efl_Ui_Focus_Object *obj;
+
+ elm_init(1, NULL);
+
+ elm_focus_test_setup_cross(&middle, &south, &north, &east, &west);
+
+ m = efl_add(EFL_UI_FOCUS_MANAGER_CLASS, NULL,
+ efl_ui_focus_manager_root_set(efl_added, middle)
+ );
+ efl_ui_focus_manager_register(m, south, middle, NULL);
+ efl_ui_focus_manager_register(m, north, middle, NULL);
+ efl_ui_focus_manager_register(m, east, middle, NULL);
+ efl_ui_focus_manager_register(m, west, middle, NULL);
+
+ iter = efl_ui_focus_manager_border_elements_get(m);
+
+ EINA_ITERATOR_FOREACH(iter, obj)
+ {
+ list = eina_list_append(list, obj);
+ }
+
+ eina_iterator_free(iter);
+
+ ck_assert(eina_list_data_find(list, east) == east);
+ ck_assert(eina_list_data_find(list, north) == north);
+ ck_assert(eina_list_data_find(list, west) == west);
+ ck_assert(eina_list_data_find(list, east) == east);
+ ck_assert(eina_list_data_find(list, middle) == NULL);
+ ck_assert(eina_list_count(list) == 4);
+
+ elm_shutdown();
+}
+END_TEST
+
+START_TEST(logical_chain)
+{
+ Efl_Ui_Focus_Manager *m;
+ int i = 0;
+
+ elm_init(1, NULL);
+
+ TEST_OBJ_NEW(root, 0, 0, 20, 20);
+
+ m = efl_add(EFL_UI_FOCUS_MANAGER_CLASS, NULL,
+ efl_ui_focus_manager_root_set(efl_added, root)
+ );
+ fail_if(!m);
+
+ efl_ui_focus_manager_focus(m, root);
+
+ i++;
+ TEST_OBJ_NEW(child1, 0, i*20, 20, 20);
+ i++;
+ TEST_OBJ_NEW(child2, 0, i*20, 20, 20);
+ i++;
+ TEST_OBJ_NEW(child3, 0, i*20, 20, 20);
+
+ i++;
+ TEST_OBJ_NEW(subchild11, 0, i*20, 20, 20);
+ i++;
+ TEST_OBJ_NEW(subchild12, 0, i*20, 20, 20);
+ i++;
+ TEST_OBJ_NEW(subchild13, 0, i*20, 20, 20);
+
+ i++;
+ TEST_OBJ_NEW(subchild21, 0, i*20, 20, 20);
+ i++;
+ TEST_OBJ_NEW(subchild22, 0, i*20, 20, 20);
+ i++;
+ TEST_OBJ_NEW(subchild23, 0, i*20, 20, 20);
+
+ //register everything
+ efl_ui_focus_manager_register(m, child1, root, NULL);
+ efl_ui_focus_manager_register(m, child2, root, NULL);
+ efl_ui_focus_manager_register(m, child3, root, NULL);
+ efl_ui_focus_manager_register(m, subchild11, child1, NULL);
+ efl_ui_focus_manager_register(m, subchild12, child1, NULL);
+ efl_ui_focus_manager_register(m, subchild13, child1, NULL);
+ efl_ui_focus_manager_register(m, subchild21, child3, NULL);
+ efl_ui_focus_manager_register(m, subchild22, child3, NULL);
+ efl_ui_focus_manager_register(m, subchild23, child3, NULL);
+
+ Efl_Object *logical_chain[] = {
+ child1, subchild11, subchild12, subchild13,
+ child2, child3, subchild21, subchild22, subchild23, root, NULL
+ };
+ for (i = 0; logical_chain[i]; ++i)
+ {
+ ck_assert_ptr_eq(logical_chain[i], efl_ui_focus_manager_move(m, EFL_UI_FOCUS_DIRECTION_NEXT));
+ }
+ i-= 2;
+ for (; i > 0; --i)
+ {
+ ck_assert_ptr_eq(logical_chain[i], efl_ui_focus_manager_move(m, EFL_UI_FOCUS_DIRECTION_PREV));
+ }
+ elm_shutdown();
+}
+END_TEST
+
+START_TEST(finalize_check)
+{
+ Efl_Ui_Focus_Manager *m;
+
+ elm_init(1, NULL);
+
+ m = efl_add(EFL_UI_FOCUS_MANAGER_CLASS, NULL);
+ fail_if(m);
+
+ elm_shutdown();
+}
+END_TEST
+
+START_TEST(redirect_param)
+{
+ Efl_Ui_Focus_Manager *m, *m2;
+
+ elm_init(1, NULL);
+
+ TEST_OBJ_NEW(root, 0, 20, 20, 20);
+ TEST_OBJ_NEW(root2, 0, 20, 20, 20);
+ TEST_OBJ_NEW(child, 0, 20, 20, 20);
+
+ m = efl_add(EFL_UI_FOCUS_MANAGER_CLASS, NULL,
+ efl_ui_focus_manager_root_set(efl_added, root)
+ );
+
+ m2 = efl_add(EFL_UI_FOCUS_MANAGER_CLASS, NULL,
+ efl_ui_focus_manager_root_set(efl_added, root2)
+ );
+
+ efl_ui_focus_manager_register(m, child, root, m2);
+ efl_ui_focus_manager_focus(m, child);
+
+ ck_assert_ptr_eq(efl_ui_focus_manager_redirect_get(m), m2);
+
+ elm_shutdown();
+}
+END_TEST
+
+START_TEST(invalid_args_check)
+{
+ Efl_Ui_Focus_Manager *m;
+
+ elm_init(1, NULL);
+
+ TEST_OBJ_NEW(root, 0, 20, 20, 20);
+ TEST_OBJ_NEW(child, 0, 20, 20, 20);
+ TEST_OBJ_NEW(child2, 0, 20, 20, 20);
+
+ m = efl_add(EFL_UI_FOCUS_MANAGER_CLASS, NULL,
+ efl_ui_focus_manager_root_set(efl_added, root)
+ );
+
+ //no child and no parent
+ ck_assert_int_eq(efl_ui_focus_manager_register(m, NULL, NULL, NULL), 0);
+ ck_assert_int_eq(efl_ui_focus_manager_register(m, child, NULL, NULL), 0);
+ ck_assert_int_eq(efl_ui_focus_manager_register(m, NULL, root, NULL), 0);
+
+ ck_assert_int_eq(efl_ui_focus_manager_register(m, child, root, NULL), 1);
+
+ ck_assert_int_eq(efl_ui_focus_manager_update_parent(m, child, NULL), 0);
+ ck_assert_int_eq(efl_ui_focus_manager_update_parent(m, NULL, NULL), 0);
+ ck_assert_int_eq(efl_ui_focus_manager_update_parent(m, child, child2), 0);
+
+ ck_assert_int_eq(efl_ui_focus_manager_register(m, child2, root, NULL), 1);
+ ck_assert_int_eq(efl_ui_focus_manager_update_parent(m, child, child2), 1);
+
+ elm_shutdown();
+}
+END_TEST
+
+START_TEST(order_check)
+{
+ Efl_Ui_Focus_Manager *m;
+ Eina_List *order = NULL;
+
+ elm_init(1, NULL);
+
+ TEST_OBJ_NEW(root, 0, 20, 20, 20);
+ TEST_OBJ_NEW(child1, 0, 20, 20, 20);
+ TEST_OBJ_NEW(child2, 0, 20, 20, 20);
+ TEST_OBJ_NEW(child3, 0, 20, 20, 20);
+
+ m = efl_add(EFL_UI_FOCUS_MANAGER_CLASS, NULL,
+ efl_ui_focus_manager_root_set(efl_added, root)
+ );
+
+ //no child and no parent
+ efl_ui_focus_manager_register(m, child1, root, NULL);
+ efl_ui_focus_manager_register(m, child2, root, NULL);
+ efl_ui_focus_manager_register(m, child3, root, NULL);
+
+ //positiv check
+ order = eina_list_append(order, child2);
+ order = eina_list_append(order, child3);
+ order = eina_list_append(order, child1);
+ ck_assert_int_eq(efl_ui_focus_manager_update_children(m, root, order), 1);
+
+ eina_list_free(order);
+ order = NULL;
+
+ //negativ check
+ order = eina_list_append(order, child1);
+ order = eina_list_append(order, child2);
+ ck_assert_int_eq(efl_ui_focus_manager_update_children(m, root, order), 0);
+
+ elm_shutdown();
+}
+END_TEST
+void elm_test_focus(TCase *tc)
+{
+ tcase_add_test(tc, focus_register_twice);
+ tcase_add_test(tc, focus_unregister_twice);
+ tcase_add_test(tc, pos_check);
+ tcase_add_test(tc, redirect);
+ tcase_add_test(tc, border_check);
+ tcase_add_test(tc, finalize_check);
+ tcase_add_test(tc, logical_chain);
+ tcase_add_test(tc, redirect_param);
+ tcase_add_test(tc, invalid_args_check);
+ tcase_add_test(tc, order_check);
+}
diff --git a/src/tests/elementary/elm_test_focus_common.c b/src/tests/elementary/elm_test_focus_common.c
new file mode 100644
index 0000000000..402fac809f
--- /dev/null
+++ b/src/tests/elementary/elm_test_focus_common.c
@@ -0,0 +1,79 @@
+#include "elm_test_focus_common.h"
+
+#define Q(o,_x,_y,_w,_h) \
+ do {\
+ Eina_Rectangle rect = EINA_RECTANGLE_INIT; \
+ rect.x = _x; \
+ rect.y = _y; \
+ rect.w = _w; \
+ rect.h = _h; \
+ focus_test_size(o, rect); \
+ } while (0)
+
+Efl_Ui_Focus_Object*
+elm_focus_test_object_new(const char *name, int x, int y, int w, int h)
+{
+ Efl_Ui_Focus_Object *ret;
+
+ ret = efl_add(FOCUS_TEST_CLASS, NULL,
+ efl_name_set(efl_added, name)
+ );
+ Q(ret, x, y, w, h);
+
+ return ret;
+}
+
+void
+elm_focus_test_setup_cross(Efl_Ui_Focus_Object **middle,
+ Efl_Ui_Focus_Object **south,
+ Efl_Ui_Focus_Object **north,
+ Efl_Ui_Focus_Object **east,
+ Efl_Ui_Focus_Object **west)
+ {
+
+ *middle = elm_focus_test_object_new("middle", 40, 40, 20, 20);
+ *south = elm_focus_test_object_new("south", 40, 80, 20, 20);
+ *north = elm_focus_test_object_new("north", 40, 0, 20, 20);
+ *east = elm_focus_test_object_new("east", 80, 40, 20, 20);
+ *west = elm_focus_test_object_new("west", 0, 40, 20, 20);
+}
+
+//Test class implementation
+
+typedef struct {
+ Eina_Rectangle rect;
+ Eina_Bool focus;
+} Focus_Test_Data;
+
+EOLIAN static Efl_Object*
+_focus_test_efl_object_constructor(Eo *obj, Focus_Test_Data *pd)
+{
+ Eo *eo;
+
+ eo = efl_constructor(efl_super(obj, FOCUS_TEST_CLASS));
+ eina_rectangle_coords_from(&pd->rect, 0, 0, 0, 0);
+ return eo;
+}
+
+EOLIAN static void
+_focus_test_efl_ui_focus_object_focus_set(Eo *obj, Focus_Test_Data *pd, Eina_Bool focus)
+{
+ pd->focus = focus;
+ printf("Object %p now focused\n", obj);
+}
+
+EOLIAN static void
+_focus_test_efl_ui_focus_object_geometry_get(Eo *obj EINA_UNUSED, Focus_Test_Data *pd, Eina_Rectangle *rect)
+{
+ if (!rect) return;
+
+ *rect = pd->rect;
+}
+
+EOLIAN static void
+_focus_test_size(Eo *obj EINA_UNUSED, Focus_Test_Data *pd, Eina_Rectangle rect)
+{
+ pd->rect = rect;
+}
+
+#include "focus_test.eo.c"
diff --git a/src/tests/elementary/elm_test_focus_common.h b/src/tests/elementary/elm_test_focus_common.h
new file mode 100644
index 0000000000..97fbdd17bc
--- /dev/null
+++ b/src/tests/elementary/elm_test_focus_common.h
@@ -0,0 +1,28 @@
+#ifndef ELM_TEST_FOCUS_COMMON_H
+#define ELM_TEST_FOCUS_COMMON_H
+
+#ifdef HAVE_CONFIG_H
+# include "elementary_config.h"
+#endif
+
+#define ELM_INTERFACE_ATSPI_ACCESSIBLE_PROTECTED
+#define ELM_INTERNAL_API_ARGESFSDFEFC
+#include <Elementary.h>
+#include "elm_suite.h"
+#include "elm_widget.h"
+#include "focus_test.eo.h"
+
+#define TEST_OBJ_NEW(name, x, y, w, h) \
+ Efl_Ui_Focus_Object* name; \
+ name = elm_focus_test_object_new("" #name "",x, y, w, h); \
+
+
+Efl_Ui_Focus_Object* elm_focus_test_object_new(const char *name, int x, int y, int w, int h);
+
+void elm_focus_test_setup_cross(Efl_Ui_Focus_Object **middle,
+ Efl_Ui_Focus_Object **south,
+ Efl_Ui_Focus_Object **north,
+ Efl_Ui_Focus_Object **east,
+ Efl_Ui_Focus_Object **west);
+
+#endif \ No newline at end of file
diff --git a/src/tests/elementary/elm_test_focus_sub.c b/src/tests/elementary/elm_test_focus_sub.c
new file mode 100644
index 0000000000..3b253f5e1f
--- /dev/null
+++ b/src/tests/elementary/elm_test_focus_sub.c
@@ -0,0 +1,242 @@
+#include "elm_test_focus_common.h"
+#include "focus_test_sub.eo.h"
+
+typedef struct {
+
+} Focus_Test_Sub_Data;
+
+EOLIAN static Eina_List*
+_focus_test_sub_efl_ui_focus_manager_sub_select_set(Eo *obj EINA_UNUSED, Focus_Test_Sub_Data *pd EINA_UNUSED, Eina_Iterator *objects)
+{
+ Eina_List *list = NULL;
+ Efl_Ui_Focus_Object *o;
+
+ EINA_ITERATOR_FOREACH(objects, o)
+ {
+ list = eina_list_append(list, o);
+ }
+
+ eina_iterator_free(objects);
+
+ return list;
+}
+
+EOLIAN static void
+_focus_test_sub_efl_ui_focus_object_geometry_get(Eo *obj EINA_UNUSED, Focus_Test_Sub_Data *pd EINA_UNUSED, Eina_Rectangle *rect EINA_UNUSED)
+{
+ rect->y = rect->x = 0;
+ rect->w = rect->h = 20;
+}
+
+EOLIAN static Eina_Bool
+_focus_test_sub_efl_ui_focus_object_focus_get(Eo *obj EINA_UNUSED, Focus_Test_Sub_Data *pd EINA_UNUSED)
+{
+ return EINA_FALSE;
+}
+
+EOLIAN static Efl_Ui_Focus_Manager*
+_focus_test_sub_efl_ui_focus_user_manager_get(Eo *obj, Focus_Test_Sub_Data *pd EINA_UNUSED)
+{
+ return efl_parent_get(obj);
+}
+
+static Eina_List *registered;
+static Eina_List *unregistered;
+
+static Eina_Bool
+_register(Eo *eo, void* data EINA_UNUSED, Efl_Ui_Focus_Object *child, Efl_Ui_Focus_Object *parent, Efl_Ui_Focus_Manager *manager)
+{
+ registered = eina_list_append(registered, child);
+ printf("REGISTERED %p %s\n", child, efl_name_get(child));
+
+ return efl_ui_focus_manager_register(efl_super(eo, EFL_OBJECT_OVERRIDE_CLASS) , child, parent, manager);
+}
+
+static void
+_unregister(Eo *eo, void* data EINA_UNUSED, Efl_Ui_Focus_Object *child)
+{
+ unregistered = eina_list_append(unregistered, child);
+ printf("UNREGISTERED %p %s\n", child, efl_name_get(child));
+
+ efl_ui_focus_manager_unregister(efl_super(eo, EFL_OBJECT_OVERRIDE_CLASS) , child);
+}
+
+static Eina_Bool
+_set_equal(Eina_List *a, Eina_List *b)
+{
+ Eina_List *n;
+ void *d;
+
+ if (eina_list_count(a) != eina_list_count(b)) return EINA_FALSE;
+
+ EINA_LIST_FOREACH(a, n, d)
+ {
+ if (!eina_list_data_find(b, d)) return EINA_FALSE;
+ }
+ return EINA_TRUE;
+}
+
+#include "focus_test_sub.eo.c"
+
+static void
+_setup(Efl_Ui_Focus_Manager **m, Efl_Ui_Focus_Manager_Sub **sub, Efl_Ui_Focus_Object **r)
+{
+
+ TEST_OBJ_NEW(root, 10, 10, 10, 10);
+ TEST_OBJ_NEW(root_manager, 0, 20, 20, 20);
+
+ EFL_OPS_DEFINE(manager_tracker,
+ EFL_OBJECT_OP_FUNC(efl_ui_focus_manager_register, _register),
+ EFL_OBJECT_OP_FUNC(efl_ui_focus_manager_unregister, _unregister),
+ );
+
+ Efl_Ui_Focus_Manager *manager = efl_add(EFL_UI_FOCUS_MANAGER_CLASS, NULL,
+ efl_ui_focus_manager_root_set(efl_added, root_manager)
+ );
+ //flush now all changes
+ efl_event_callback_call(manager, EFL_UI_FOCUS_MANAGER_EVENT_PRE_FLUSH, NULL);
+ registered = NULL;
+ unregistered = NULL;
+
+ efl_object_override(manager, &manager_tracker);
+
+ Efl_Ui_Focus_Manager_Sub *subm = efl_add(FOCUS_TEST_SUB_CLASS, manager,
+ efl_ui_focus_manager_sub_parent_set(efl_added, root_manager),
+ efl_ui_focus_manager_root_set(efl_added, root)
+ );
+
+ efl_event_callback_call(manager, EFL_UI_FOCUS_MANAGER_EVENT_PRE_FLUSH, NULL);
+
+ *sub = subm;
+ *m = manager;
+ *r = root;
+}
+
+START_TEST(correct_register)
+{
+ Eina_List *set1 = NULL;
+ Efl_Ui_Focus_Object *root;
+ Efl_Ui_Focus_Manager *manager, *sub;
+ elm_init(0, NULL);
+
+ _setup(&manager, &sub, &root);
+
+ TEST_OBJ_NEW(child1, 0, 0, 10, 10);
+ TEST_OBJ_NEW(child2, 10, 0, 10, 10);
+ TEST_OBJ_NEW(child3, 0, 10, 10, 10);
+
+ set1 = eina_list_append(set1, sub);
+ set1 = eina_list_append(set1, root);
+ set1 = eina_list_append(set1, child1);
+ set1 = eina_list_append(set1, child2);
+ set1 = eina_list_append(set1, child3);
+
+ //test register stuff
+ efl_ui_focus_manager_register(sub, child1, root, NULL);
+ efl_ui_focus_manager_register(sub, child2, root, NULL);
+ efl_ui_focus_manager_register(sub, child3, root, NULL);
+ //now force submanager to flush things
+ efl_event_callback_call(manager, EFL_UI_FOCUS_MANAGER_EVENT_PRE_FLUSH, NULL);
+ ck_assert_ptr_eq(unregistered, NULL);
+ fail_if(!_set_equal(registered, set1));
+
+ efl_del(sub);
+ efl_del(manager);
+ efl_del(root);
+ efl_del(child1);
+ efl_del(child2);
+ efl_del(child3);
+ elm_shutdown();
+}
+END_TEST
+
+START_TEST(correct_unregister)
+{
+ Eina_List *set = NULL;
+ Efl_Ui_Focus_Object *root;
+ Efl_Ui_Focus_Manager *manager, *sub;
+ elm_init(0, NULL);
+
+ _setup(&manager, &sub, &root);
+
+ TEST_OBJ_NEW(child1, 0, 0, 10, 10);
+ TEST_OBJ_NEW(child2, 10, 0, 10, 10);
+ TEST_OBJ_NEW(child3, 0, 10, 10, 10);
+
+ set = eina_list_append(set, child3);
+
+ //test register stuff
+ efl_ui_focus_manager_register(sub, child1, root, NULL);
+ efl_ui_focus_manager_register(sub, child2, root, NULL);
+ efl_ui_focus_manager_register(sub, child3, root, NULL);
+ efl_event_callback_call(manager, EFL_UI_FOCUS_MANAGER_EVENT_PRE_FLUSH, NULL);
+ eina_list_free(unregistered);
+ unregistered = NULL;
+ eina_list_free(registered);
+ registered = NULL;
+
+ //test unregister stuff
+ efl_ui_focus_manager_unregister(sub, child3);
+ efl_event_callback_call(manager, EFL_UI_FOCUS_MANAGER_EVENT_PRE_FLUSH, NULL);
+ ck_assert_ptr_eq(registered, NULL);
+ fail_if(!_set_equal(unregistered, set));
+ eina_list_free(unregistered);
+ unregistered = NULL;
+
+ efl_del(sub);
+ efl_del(manager);
+ efl_del(root);
+ efl_del(child1);
+ efl_del(child2);
+ efl_del(child3);
+ elm_shutdown();
+}
+END_TEST
+
+START_TEST(correct_un_register)
+{
+ Eina_List *set_add = NULL, *set_del = NULL;
+ Efl_Ui_Focus_Object *root;
+ Efl_Ui_Focus_Manager *manager, *sub;
+ elm_init(0, NULL);
+
+ _setup(&manager, &sub, &root);
+
+ TEST_OBJ_NEW(child1, 0, 0, 10, 10);
+ TEST_OBJ_NEW(child2, 10, 0, 10, 10);
+ TEST_OBJ_NEW(child3, 0, 10, 10, 10);
+
+ set_add = eina_list_append(set_add, child2);
+ set_del = eina_list_append(set_del, child3);
+ //test register stuff
+ efl_ui_focus_manager_register(sub, child1, root, NULL);
+ efl_ui_focus_manager_register(sub, child3, root, NULL);
+ efl_event_callback_call(manager, EFL_UI_FOCUS_MANAGER_EVENT_PRE_FLUSH, NULL);
+ eina_list_free(unregistered);
+ unregistered = NULL;
+ eina_list_free(registered);
+ registered = NULL;
+
+ //test unregister stuff
+ efl_ui_focus_manager_unregister(sub, child3);
+ efl_ui_focus_manager_register(sub, child2, root, NULL);
+ efl_event_callback_call(manager, EFL_UI_FOCUS_MANAGER_EVENT_PRE_FLUSH, NULL);
+ fail_if(!_set_equal(registered, set_add));
+ fail_if(!_set_equal(unregistered, set_del));
+
+ efl_del(sub);
+ efl_del(manager);
+ efl_del(root);
+ efl_del(child1);
+ efl_del(child2);
+ efl_del(child3);
+ elm_shutdown();
+}
+END_TEST
+
+void elm_test_focus_sub(TCase *tc)
+{
+ tcase_add_test(tc, correct_register);
+ tcase_add_test(tc, correct_unregister);
+ tcase_add_test(tc, correct_un_register);
+} \ No newline at end of file
diff --git a/src/tests/elementary/focus_test.eo b/src/tests/elementary/focus_test.eo
new file mode 100644
index 0000000000..7417cace1c
--- /dev/null
+++ b/src/tests/elementary/focus_test.eo
@@ -0,0 +1,14 @@
+class Focus.Test(Efl.Object, Efl.Ui.Focus.Object) {
+ methods {
+ size {
+ params {
+ rect : Eina.Rectangle;
+ }
+ }
+ }
+ implements {
+ Efl.Object.constructor;
+ Efl.Ui.Focus.Object.geometry_get;
+ Efl.Ui.Focus.Object.focus.set;
+ }
+} \ No newline at end of file
diff --git a/src/tests/elementary/focus_test_sub.eo b/src/tests/elementary/focus_test_sub.eo
new file mode 100644
index 0000000000..355acb95ef
--- /dev/null
+++ b/src/tests/elementary/focus_test_sub.eo
@@ -0,0 +1,8 @@
+class Focus.Test.Sub(Efl.Ui.Focus.Manager.Sub) {
+ implements {
+ Efl.Ui.Focus.Manager.Sub.select_set;
+ Efl.Ui.Focus.Object.geometry_get;
+ Efl.Ui.Focus.Object.focus.get;
+ Efl.Ui.Focus.User.manager.get;
+ }
+} \ No newline at end of file