efl/src/lib/elementary/efl_ui_focus_manager_calc.c

1981 lines
53 KiB
C

#ifdef HAVE_CONFIG_H
# include "elementary_config.h"
#endif
#define EFL_UI_FOCUS_OBJECT_PROTECTED
#include <Elementary.h>
#include "elm_priv.h"
#define MY_CLASS EFL_UI_FOCUS_MANAGER_CALC_CLASS
#define FOCUS_DATA(obj) Efl_Ui_Focus_Manager_Calc_Data *pd = efl_data_scope_get(obj, MY_CLASS);
#define NODE_DIRECTIONS_COUNT 4
#define DIRECTION_IS_LOGICAL(dir) (dir >= EFL_UI_FOCUS_DIRECTION_PREVIOUS && dir < EFL_UI_FOCUS_DIRECTION_UP)
#define DIRECTION_IS_2D(dir) (dir >= EFL_UI_FOCUS_DIRECTION_UP && dir < EFL_UI_FOCUS_DIRECTION_LAST)
#define DIRECTION_CHECK(dir) (dir >= EFL_UI_FOCUS_DIRECTION_PREVIOUS && dir < EFL_UI_FOCUS_DIRECTION_LAST)
//#define CALC_DEBUG
#define DEBUG_TUPLE(obj) efl_name_get(obj), efl_class_name_get(obj)
static int _focus_log_domain = -1;
#define F_CRI(...) EINA_LOG_DOM_CRIT(_focus_log_domain, __VA_ARGS__)
#define F_ERR(...) EINA_LOG_DOM_ERR(_focus_log_domain, __VA_ARGS__)
#define F_WRN(...) EINA_LOG_DOM_WARN(_focus_log_domain, __VA_ARGS__)
#define F_INF(...) EINA_LOG_DOM_INFO(_focus_log_domain, __VA_ARGS__)
#define F_DBG(...) EINA_LOG_DOM_DBG(_focus_log_domain, __VA_ARGS__)
#define DIRECTION_ACCESS(V, ID) ((V)->graph.directions[(ID) - 2])
typedef enum {
DIMENSION_X = 0,
DIMENSION_Y = 1,
} Dimension;
typedef struct _Border Border;
typedef struct _Node Node;
struct _Border {
Eina_List *partners; //partners that are linked in both directions
Eina_List *one_direction; //partners that are linked in one direction
Eina_List *cleanup_nodes; //a list of nodes that needs to be cleaned up when this node is deleted
};
typedef enum {
NODE_TYPE_NORMAL = 0,
NODE_TYPE_ONLY_LOGICAL = 2,
} Node_Type;
struct _Node{
Node_Type type; //type of the node
Efl_Ui_Focus_Object *focusable;
Efl_Ui_Focus_Manager *manager;
Efl_Ui_Focus_Manager *redirect_manager;
struct _Tree_Node{
Node *parent; //the parent of the tree
Eina_List *children; //this saves the original set of elements
Eina_List *saved_order;
}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;
Efl_Ui_Focus_Object *redirect_entry;
Eina_List *dirty;
Node *root;
} Efl_Ui_Focus_Manager_Calc_Data;
static Node* _request_subchild(Node *node);
static void dirty_add(Eo *obj, Efl_Ui_Focus_Manager_Calc_Data *pd, Node *dirty);
static Node* _next(Node *node);
static void
_manager_in_chain_set(Eo *obj, Efl_Ui_Focus_Manager_Calc_Data *pd)
{
Eo *manager, *root;
root = efl_ui_focus_manager_root_get(obj);
manager = efl_ui_focus_object_focus_manager_get(pd->root->focusable);
EINA_SAFETY_ON_NULL_RETURN(root);
if (!efl_isa(root, EFL_UI_FOCUS_MANAGER_WINDOW_ROOT_INTERFACE))
EINA_SAFETY_ON_NULL_RETURN(manager);
//so we dont run infinitly this does not fix it, but at least we only have a error
EINA_SAFETY_ON_TRUE_RETURN(manager == obj);
if (manager)
efl_ui_focus_manager_focus_set(manager, root);
else
DBG("No focus manager for focusable %s@%p",
efl_class_name_get(pd->root->focusable), root);
}
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_PREVIOUS, 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;
EINA_SAFETY_ON_FALSE_RETURN(DIRECTION_IS_2D(direction));
border = &DIRECTION_ACCESS(node, direction);
EINA_LIST_FREE(border->partners, partner)
{
Border *comp_border = &DIRECTION_ACCESS(partner, _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 = &DIRECTION_ACCESS(partner,_complement(direction));
comp_border->partners = eina_list_append(comp_border->partners, node);
}
}
static void
border_onedirection_set(Node *node, Efl_Ui_Focus_Direction direction, Eina_List *list)
{
Node *partner;
Eina_List *lnode;
Border *border;
EINA_SAFETY_ON_FALSE_RETURN(DIRECTION_IS_2D(direction));
border = &DIRECTION_ACCESS(node, direction);
EINA_LIST_FREE(border->one_direction, partner)
{
Border *b = &DIRECTION_ACCESS(partner, _complement(direction));
b->cleanup_nodes = eina_list_remove(b->cleanup_nodes, node);
}
border->one_direction = list;
EINA_LIST_FOREACH(border->one_direction, lnode, partner)
{
Border *comp_border = &DIRECTION_ACCESS(partner,_complement(direction));
comp_border->cleanup_nodes = eina_list_append(comp_border->cleanup_nodes, node);
}
}
static void
border_onedirection_cleanup(Node *node, Efl_Ui_Focus_Direction direction)
{
Node *partner;
Border *border;
EINA_SAFETY_ON_FALSE_RETURN(DIRECTION_IS_2D(direction));
border = &DIRECTION_ACCESS(node, direction);
EINA_LIST_FREE(border->cleanup_nodes, partner)
{
Border *b = &DIRECTION_ACCESS(partner, _complement(direction));
b->one_direction = eina_list_remove(b->one_direction, 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;
}
/**
* Looks up given focus object from the focus manager.
*
* @returns node found, or NULL if focusable was not found in the manager.
*/
static Node*
node_get(Efl_Ui_Focus_Manager *obj, Efl_Ui_Focus_Manager_Calc_Data *pd, Efl_Ui_Focus_Object *focusable)
{
Node *ret;
ret = eina_hash_find(pd->node_hash, &focusable);
if (ret) return ret;
ERR("Focusable %p (%s) not registered in manager %p", focusable, efl_class_name_get(focusable), obj);
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;
Eo *obj = item->manager;
FOCUS_DATA(obj);
/*cleanup graph parts*/
//add all neighbors of the node to the dirty list
for(int i = EFL_UI_FOCUS_DIRECTION_UP; i < EFL_UI_FOCUS_DIRECTION_LAST; i++)
{
Node *partner;
Eina_List *n;
#define MAKE_LIST_DIRTY(node, field) \
EINA_LIST_FOREACH(DIRECTION_ACCESS(node, i).field, n, partner) \
{ \
dirty_add(obj, pd, partner); \
}
MAKE_LIST_DIRTY(item, partners)
MAKE_LIST_DIRTY(item, one_direction)
MAKE_LIST_DIRTY(item, cleanup_nodes)
border_partners_set(item, i, NULL);
border_onedirection_cleanup(item, i);
border_onedirection_set(item, i, NULL);
}
/*cleanup manager householdings*/
//remove from the focus stack
pd->focus_stack = eina_list_remove(pd->focus_stack, item);
//if this is the entry for redirect, NULL them out!
if (pd->redirect_entry == item->focusable)
pd->redirect_entry = NULL;
//remove from the dirty parts
pd->dirty = eina_list_remove(pd->dirty, item);
/*merge tree items*/
//free the tree items
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 the safed order
ELM_SAFE_FREE(T(item).saved_order, eina_list_free);
free(item);
}
//FOCUS-STACK HELPERS
static Efl_Ui_Focus_Object*
_focus_stack_unfocus_last(Efl_Ui_Focus_Manager_Calc_Data *pd)
{
Efl_Ui_Focus_Object *focusable = NULL;
Node *n;
n = eina_list_last_data_get(pd->focus_stack);
if (n)
focusable = n->focusable;
pd->focus_stack = eina_list_remove(pd->focus_stack, n);
if (n)
efl_ui_focus_object_focus_set(n->focusable, EINA_FALSE);
return focusable;
}
//CALCULATING STUFF
static inline int
_distance(Eina_Rect node, Eina_Rect op, Dimension dim)
{
int min, max, point;
int v1, v2;
if (dim == DIMENSION_X)
{
min = op.x;
max = eina_rectangle_max_x(&op.rect);
point = node.x + node.w/2;
}
else
{
min = op.y;
max = eina_rectangle_max_y(&op.rect);
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
_min_max_gen(Dimension dim, Eina_Rect rect, int *min, int *max)
{
if (dim == DIMENSION_X)
{
*min = rect.y;
*max = eina_rectangle_max_y(&rect.rect);
}
else
{
*min = rect.x;
*max = eina_rectangle_max_x(&rect.rect);
}
}
static inline void
_calculate_node(Efl_Ui_Focus_Manager_Calc_Data *pd, Efl_Ui_Focus_Object *node, Dimension dim, Eina_List **pos, Eina_List **neg)
{
int dim_min, dim_max, cur_pos_min = 0, cur_neg_min = 0;
Efl_Ui_Focus_Object *op;
Eina_Iterator *nodes;
Eina_Rect rect;
Node *n;
*pos = NULL;
*neg = NULL;
rect = efl_ui_focus_object_focus_geometry_get(node);
nodes = eina_hash_iterator_data_new(pd->node_hash);
_min_max_gen(dim, rect, &dim_min, &dim_max);
EINA_ITERATOR_FOREACH(nodes, n)
{
Eina_Rect op_rect;
int min, max;
op = n->focusable;
if (op == node) continue;
if (n->type == NODE_TYPE_ONLY_LOGICAL) continue;
op_rect = efl_ui_focus_object_focus_geometry_get(op);
_min_max_gen(dim, op_rect, &min, &max);
/* The 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, &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 CALC_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 CALC_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
}
}
eina_iterator_free(nodes);
nodes = NULL;
}
static inline Eina_Position2D
_relative_position_rects(Eina_Rect *a, Eina_Rect *b)
{
Eina_Position2D a_pos = {a->rect.x + a->rect.w/2, a->rect.y + a->rect.h/2};
Eina_Position2D b_pos = {b->rect.x + b->rect.w/2, b->rect.y + b->rect.h/2};
return (Eina_Position2D){b_pos.x - a_pos.x, b_pos.y - b_pos.y};
}
static inline Eina_Rectangle_Outside
_direction_to_outside(Efl_Ui_Focus_Direction direction)
{
if (direction == EFL_UI_FOCUS_DIRECTION_RIGHT) return EINA_RECTANGLE_OUTSIDE_RIGHT;
if (direction == EFL_UI_FOCUS_DIRECTION_LEFT) return EINA_RECTANGLE_OUTSIDE_LEFT;
if (direction == EFL_UI_FOCUS_DIRECTION_DOWN) return EINA_RECTANGLE_OUTSIDE_BOTTOM;
if (direction == EFL_UI_FOCUS_DIRECTION_UP) return EINA_RECTANGLE_OUTSIDE_TOP;
return -1;
}
static inline void
_calculate_node_indirection(Efl_Ui_Focus_Manager_Calc_Data *pd, Efl_Ui_Focus_Object *node, Efl_Ui_Focus_Direction direction, Eina_List **lst)
{
Efl_Ui_Focus_Object *op;
Eina_Iterator *nodes;
int min_distance = 0;
Node *n;
Eina_Rect rect;
rect = efl_ui_focus_object_focus_geometry_get(node);
nodes = eina_hash_iterator_data_new(pd->node_hash);
EINA_ITERATOR_FOREACH(nodes, n)
{
Eina_Rectangle_Outside outside, outside_dir;
Eina_Position2D pos;
int distance;
Eina_Rect op_rect;
op = n->focusable;
if (op == node) continue;
if (n->type == NODE_TYPE_ONLY_LOGICAL) continue;
op_rect = efl_ui_focus_object_focus_geometry_get(op);
outside = eina_rectangle_outside_position(&rect.rect, &op_rect.rect);
outside_dir = _direction_to_outside(direction);
//calculate relative position of the nodes
pos = _relative_position_rects(&rect, &op_rect);
//calculate distance
distance = pow(pos.x, 2) + pow(pos.y, 2);
if (outside & outside_dir)
{
if (min_distance == 0 || min_distance > distance)
{
min_distance = distance;
*lst = eina_list_free(*lst);
*lst = eina_list_append(*lst, op);
}
else if (min_distance == distance)
{
*lst = eina_list_append(*lst, op);
}
}
}
}
#ifdef CALC_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) DIRECTION_ACCESS(node,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_set(Efl_Ui_Focus_Manager *obj, Efl_Ui_Focus_Manager_Calc_Data *pd, Node *node, Eina_List *focusable_list, Efl_Ui_Focus_Direction dir, void (*converter)(Node *node, Efl_Ui_Focus_Direction direction, Eina_List *list))
{
Eina_List *partners = NULL;
Efl_Ui_Focus_Object *fobj;
EINA_LIST_FREE(focusable_list, fobj)
{
Node *entry;
entry = node_get(obj, pd, fobj);
if (!entry)
{
CRI("Found a obj in graph without node-entry!");
return;
}
partners = eina_list_append(partners, entry);
}
converter(node, dir, partners);
}
static void
dirty_flush_node(Efl_Ui_Focus_Manager *obj EINA_UNUSED, Efl_Ui_Focus_Manager_Calc_Data *pd, Node *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_set(obj, pd, node, x_partners_pos, EFL_UI_FOCUS_DIRECTION_RIGHT, border_partners_set);
convert_set(obj, pd, node, x_partners_neg, EFL_UI_FOCUS_DIRECTION_LEFT, border_partners_set);
convert_set(obj, pd, node, y_partners_neg, EFL_UI_FOCUS_DIRECTION_UP, border_partners_set);
convert_set(obj, pd, node, y_partners_pos, EFL_UI_FOCUS_DIRECTION_DOWN, border_partners_set);
/*
* Stage 2: if there is still no relation in a special direction,
* just take every single node that is in the given direction
* and take the one with the shortest direction
*/
for(int i = EFL_UI_FOCUS_DIRECTION_UP; i < EFL_UI_FOCUS_DIRECTION_LAST; i++)
{
if (!DIRECTION_ACCESS(node, i).partners)
{
Eina_List *tmp = NULL;
_calculate_node_indirection(pd, node->focusable, i, &tmp);
convert_set(obj, pd, node, tmp, i, border_onedirection_set);
}
}
#ifdef CALC_DEBUG
_debug_node(node);
#endif
}
static void
dirty_flush(Efl_Ui_Focus_Manager *obj, Efl_Ui_Focus_Manager_Calc_Data *pd, Node *node)
{
if (!eina_list_data_find(pd->dirty, node)) return;
efl_event_callback_call(obj, EFL_UI_FOCUS_MANAGER_EVENT_FLUSH_PRE, NULL);
pd->dirty = eina_list_remove(pd->dirty, node);
dirty_flush_node(obj, pd, node);
}
static void
dirty_flush_all(Efl_Ui_Focus_Manager *obj, Efl_Ui_Focus_Manager_Calc_Data *pd)
{
Node *node;
efl_event_callback_call(obj, EFL_UI_FOCUS_MANAGER_EVENT_FLUSH_PRE, NULL);
EINA_LIST_FREE(pd->dirty, node)
{
dirty_flush_node(obj, pd, node);
}
}
static void
dirty_add(Eo *obj, Efl_Ui_Focus_Manager_Calc_Data *pd, Node *dirty)
{
if (dirty->type == NODE_TYPE_ONLY_LOGICAL)
{
ERR("Only not only logical nodes can be marked dirty");
return;
}
//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_geometry_cb(void *data, const Efl_Event *event)
{
Node *node;
FOCUS_DATA(data)
node = node_get(data, pd, event->object);
if (!node)
return;
dirty_add(data, pd, node);
return;
}
static void
_object_del_cb(void *data, const Efl_Event *event)
{
/*
* Lets just implicitly delete items that are deleted
* Otherwise we have later just a bunch of errors
*/
efl_ui_focus_manager_calc_unregister(data, event->object);
}
EFL_CALLBACKS_ARRAY_DEFINE(regular_node,
{EFL_GFX_ENTITY_EVENT_RESIZE, _node_new_geometry_cb},
{EFL_GFX_ENTITY_EVENT_MOVE, _node_new_geometry_cb},
{EFL_EVENT_DEL, _object_del_cb},
);
EFL_CALLBACKS_ARRAY_DEFINE(logical_node,
{EFL_EVENT_DEL, _object_del_cb},
);
//=============================
static Node*
_register(Eo *obj, Efl_Ui_Focus_Manager_Calc_Data *pd, Efl_Ui_Focus_Object *child, Node *parent)
{
Node *node;
node = eina_hash_find(pd->node_hash, &child);
if (node)
{
ERR("Child %p is already registered in the graph (%s)", child, node->type == NODE_TYPE_ONLY_LOGICAL ? "logical" : "regular");
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);
}
return node;
}
EOLIAN static Eina_Bool
_efl_ui_focus_manager_calc_register_logical(Eo *obj, Efl_Ui_Focus_Manager_Calc_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);
if (redirect)
EINA_SAFETY_ON_FALSE_RETURN_VAL(efl_isa(redirect, EFL_UI_FOCUS_MANAGER_INTERFACE), EINA_FALSE);
F_DBG("Manager: %p register %p %p %p", obj, child, parent, redirect);
pnode = node_get(obj, pd, parent);
if (!pnode) return EINA_FALSE;
node = _register(obj, pd, child, pnode);
if (!node) return EINA_FALSE;
//listen to deletion
efl_event_callback_array_add(child, logical_node(), obj);
node->type = NODE_TYPE_ONLY_LOGICAL;
node->redirect_manager = redirect;
//set again
if (T(pnode).saved_order)
{
Eina_List *tmp;
tmp = eina_list_clone(T(pnode).saved_order);
efl_ui_focus_manager_calc_update_order(obj, parent, tmp);
}
return EINA_TRUE;
}
EOLIAN static Eina_Bool
_efl_ui_focus_manager_calc_register(Eo *obj, Efl_Ui_Focus_Manager_Calc_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);
if (redirect)
EINA_SAFETY_ON_FALSE_RETURN_VAL(efl_isa(redirect, EFL_UI_FOCUS_MANAGER_INTERFACE), EINA_FALSE);
F_DBG("Manager: %p register %p %p %p", obj, child, parent, redirect);
pnode = node_get(obj, pd, parent);
if (!pnode) return EINA_FALSE;
node = _register(obj, pd, child, pnode);
if (!node) return EINA_FALSE;
//listen to changes
efl_event_callback_array_add(child, regular_node(), obj);
node->type = NODE_TYPE_NORMAL;
node->redirect_manager = redirect;
//mark dirty
dirty_add(obj, pd, node);
//set again
if (T(pnode).saved_order)
{
Eina_List *tmp;
tmp = eina_list_clone(T(pnode).saved_order);
efl_ui_focus_manager_calc_update_order(obj, parent, tmp);
}
return EINA_TRUE;
}
EOLIAN static Eina_Bool
_efl_ui_focus_manager_calc_update_redirect(Eo *obj, Efl_Ui_Focus_Manager_Calc_Data *pd, Efl_Ui_Focus_Object *child, Efl_Ui_Focus_Manager *redirect)
{
Node *node = node_get(obj, pd, child);
if (!node) return EINA_FALSE;
if (redirect)
EINA_SAFETY_ON_FALSE_RETURN_VAL(efl_isa(redirect, MY_CLASS), EINA_FALSE);
node->redirect_manager = redirect;
return EINA_TRUE;
}
EOLIAN static Eina_Bool
_efl_ui_focus_manager_calc_update_parent(Eo *obj EINA_UNUSED, Efl_Ui_Focus_Manager_Calc_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(obj, pd, child);
parent = node_get(obj, 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_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 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))
return EINA_FALSE;
}
return EINA_TRUE;
}
EOLIAN static void
_efl_ui_focus_manager_calc_update_order(Eo *obj, Efl_Ui_Focus_Manager_Calc_Data *pd, Efl_Ui_Focus_Object *parent, Eina_List *order)
{
Node *pnode;
Efl_Ui_Focus_Object *o;
Eina_List *node_order = NULL, *not_ordered, *trash, *node_order_clean, *n;
F_DBG("Manager_update_order on %p %p", obj, parent);
pnode = node_get(obj, pd, parent);
if (!pnode)
return;
ELM_SAFE_FREE(T(pnode).saved_order, eina_list_free);
T(pnode).saved_order = order;
//get all nodes from the subset
EINA_LIST_FOREACH(order, n, o)
{
Node *tmp;
tmp = eina_hash_find(pd->node_hash, &o);
if (!tmp) continue;
node_order = eina_list_append(node_order, tmp);
}
not_ordered = _set_a_without_b(T(pnode).children, node_order);
trash = _set_a_without_b(node_order, T(pnode).children);
node_order_clean = _set_a_without_b(node_order, trash);
eina_list_free(node_order);
eina_list_free(trash);
eina_list_free(T(pnode).children);
T(pnode).children = eina_list_merge(node_order_clean, not_ordered);
return;
}
EOLIAN static Eina_Bool
_efl_ui_focus_manager_calc_update_children(Eo *obj EINA_UNUSED, Efl_Ui_Focus_Manager_Calc_Data *pd, Efl_Ui_Focus_Object *parent, Eina_List *order)
{
Node *pnode;
Efl_Ui_Focus_Object *o;
Eina_Bool fail = EINA_FALSE;
Eina_List *node_order = NULL;
pnode = node_get(obj, pd, parent);
if (!pnode)
return EINA_FALSE;
//get all nodes from the subset
EINA_LIST_FREE(order, o)
{
Node *tmp;
tmp = node_get(obj, pd, o);
if (!tmp)
fail = EINA_TRUE;
node_order = eina_list_append(node_order, tmp);
}
if (fail)
{
eina_list_free(node_order);
return EINA_FALSE;
}
if (!_equal_set(node_order, T(pnode).children))
{
ERR("Set of children is not equal");
return EINA_FALSE;
}
eina_list_free(T(pnode).children);
T(pnode).children = node_order;
return EINA_TRUE;
}
static inline Node*
_request_subchild_except(Node *n, Node *except)
{
n = _request_subchild(n);
while (n == except)
{
n = _next(n);
}
return n;
}
EOLIAN static void
_efl_ui_focus_manager_calc_unregister(Eo *obj EINA_UNUSED, Efl_Ui_Focus_Manager_Calc_Data *pd, Efl_Ui_Focus_Object *child)
{
Node *node;
node = eina_hash_find(pd->node_hash, &child);
if (!node) return;
F_DBG("Manager: %p unregister %p", obj, child);
if (eina_list_last_data_get(pd->focus_stack) == node)
{
if (!efl_invalidated_get(pd->root->focusable))
{
Node *n;
n = eina_list_nth(pd->focus_stack, eina_list_count(pd->focus_stack) - 2);
if (!n)
n = _request_subchild_except(pd->root, node);
if (n)
efl_ui_focus_manager_focus_set(obj, n->focusable);
}
}
eina_hash_del_by_key(pd->node_hash, &child);
}
EOLIAN static void
_efl_ui_focus_manager_calc_efl_ui_focus_manager_redirect_set(Eo *obj, Efl_Ui_Focus_Manager_Calc_Data *pd, Efl_Ui_Focus_Manager *redirect)
{
Efl_Ui_Focus_Manager *old_manager;
if (pd->redirect == redirect) return;
F_DBG("Manager: %p setting redirect from %p to %p", obj, pd->redirect, redirect);
old_manager = pd->redirect;
if (pd->redirect)
efl_wref_del(pd->redirect, &pd->redirect);
pd->redirect = redirect;
if (pd->redirect)
efl_wref_add(pd->redirect, &pd->redirect);
efl_ui_focus_manager_reset_history(old_manager);
//adjust focus property of the most upper element
{
Node *n = NULL;
n = eina_list_last_data_get(pd->focus_stack);
if (n)
{
if (!pd->redirect && old_manager)
efl_ui_focus_object_focus_set(n->focusable, EINA_TRUE);
else if (pd->redirect && !old_manager)
efl_ui_focus_object_focus_set(n->focusable, EINA_FALSE);
}
}
efl_event_callback_call(obj, EFL_UI_FOCUS_MANAGER_EVENT_REDIRECT_CHANGED , old_manager);
//just set the root of the new redirect as focused, so it is in a known state
if (redirect)
{
efl_ui_focus_manager_setup_on_first_touch(redirect, EFL_UI_FOCUS_DIRECTION_LAST, NULL);
}
}
EOLIAN static Efl_Ui_Focus_Manager *
_efl_ui_focus_manager_calc_efl_ui_focus_manager_redirect_get(const Eo *obj EINA_UNUSED, Efl_Ui_Focus_Manager_Calc_Data *pd)
{
return pd->redirect;
}
static void
_free_node(void *data)
{
Node *node = data;
FOCUS_DATA(node->manager);
if (node->type == NODE_TYPE_ONLY_LOGICAL)
efl_event_callback_array_del(node->focusable, logical_node(), node->manager);
else
efl_event_callback_array_del(node->focusable, regular_node(), node->manager);
if (pd->root != data)
{
node_item_free(node);
}
}
EOLIAN static Efl_Object *
_efl_ui_focus_manager_calc_efl_object_constructor(Eo *obj, Efl_Ui_Focus_Manager_Calc_Data *pd)
{
obj = efl_constructor(efl_super(obj, MY_CLASS));
pd->node_hash = eina_hash_pointer_new(_free_node);
return obj;
}
EOLIAN static Efl_Object *
_efl_ui_focus_manager_calc_efl_object_provider_find(const Eo *obj, Efl_Ui_Focus_Manager_Calc_Data *pd EINA_UNUSED, const Efl_Object *klass)
{
if (klass == MY_CLASS)
return (Efl_Object *) obj;
return efl_provider_find(efl_super(obj, MY_CLASS), klass);
}
EOLIAN static void
_efl_ui_focus_manager_calc_efl_object_destructor(Eo *obj, Efl_Ui_Focus_Manager_Calc_Data *pd)
{
pd->focus_stack = eina_list_free(pd->focus_stack);
pd->dirty = eina_list_free(pd->dirty);
eina_hash_free(pd->node_hash);
efl_ui_focus_manager_redirect_set(obj, NULL);
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;
EINA_ITERATOR_FOREACH(it->real_iterator, node)
{
if (node->type == NODE_TYPE_ONLY_LOGICAL) continue;
for(int i = EFL_UI_FOCUS_DIRECTION_UP ;i < EFL_UI_FOCUS_DIRECTION_LAST; i++)
{
if (!DIRECTION_ACCESS(node, i).partners)
{
*data = node->focusable;
return EINA_TRUE;
}
}
}
return EINA_FALSE;
}
static Eo *
_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_calc_efl_ui_focus_manager_border_elements_get(const Eo *obj, Efl_Ui_Focus_Manager_Calc_Data *pd)
{
Border_Elements_Iterator *it;
Node *n;
/* XXX const */
dirty_flush_all((Eo *)obj, pd);
Eina_Iterator *elements = eina_hash_iterator_data_new(pd->node_hash);
EINA_ITERATOR_FOREACH(elements, n)
{
efl_ui_focus_object_prepare_logical(n->focusable);
}
eina_iterator_free(elements);
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 = (Eo *)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);
EINA_ITERATOR_FOREACH(iter, upper)
{
if (upper->type == NODE_TYPE_NORMAL)
break;
}
eina_iterator_free(iter);
if (upper->type != NODE_TYPE_NORMAL)
return NULL;
return upper;
}
static void
_get_middle(Evas_Object *obj, Eina_Vector2 *elem)
{
Eina_Rect geom;
geom = efl_ui_focus_object_focus_geometry_get(obj);
elem->x = geom.x + geom.w/2;
elem->y = geom.y + geom.h/2;
}
static Node*
_coords_movement(Efl_Ui_Focus_Manager_Calc_Data *pd, Node *upper, Efl_Ui_Focus_Direction direction)
{
Node *candidate;
Eina_List *node_list;
Eina_List *lst;
EINA_SAFETY_ON_FALSE_RETURN_VAL(DIRECTION_IS_2D(direction), NULL);
//decide which direction we take
lst = DIRECTION_ACCESS(upper, direction).partners;
if (!lst)
lst = DIRECTION_ACCESS(upper, direction).one_direction;
//we are searching which of the partners is lower to the history
EINA_LIST_REVERSE_FOREACH(pd->focus_stack, node_list, candidate)
{
//we only calculate partners for normal nodes
if (candidate->type == NODE_TYPE_NORMAL) continue;
if (eina_list_data_find(lst, candidate))
{
//this is the next accessible part
return candidate;
}
}
//if we haven't found anything in the history, use the widget with the smallest distance
{
Eina_List *n;
Node *node, *min = NULL;
Eina_Vector2 elem, other;
float min_distance = 0.0;
_get_middle(upper->focusable, &elem);
EINA_LIST_FOREACH(lst, n, node)
{
_get_middle(node->focusable, &other);
float tmp = eina_vector2_distance_get(&other, &elem);
if (!min || tmp < min_distance)
{
min = node;
min_distance = tmp;
}
}
candidate = min;
}
return candidate;
}
static Node*
_prev_item(Node *node)
{
Node *parent;
Eina_List *lnode;
parent = T(node).parent;
//we are accessing the parents children, prepare!
efl_ui_focus_object_prepare_logical(parent->focusable);
lnode = eina_list_data_find_list(T(parent).children, node);
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
//But only enter the children if it does NOT have a redirect manager
if (T(node).children && !node->redirect_manager)
{
return eina_list_data_get(T(node).children);
}
//case 2 we are the root and we don't have children, return ourself
if (!T(node).parent)
{
return node;
}
//case 3 we are not at the end of the parents list
n = node;
while(T(n).parent)
{
Node *parent;
Eina_List *lnode;
parent = T(n).parent;
//we are accessing the parents children, prepare!
efl_ui_focus_object_prepare_logical(parent->focusable);
lnode = eina_list_data_find_list(T(parent).children, n);
lnode = eina_list_next(lnode);
if (lnode)
{
return eina_list_data_get(lnode);
}
n = parent;
}
//this is then the root again
return NULL;
}
static Node*
_prev(Node *node)
{
Node *n = NULL;
//this is the root there is no parent
if (!T(node).parent)
return NULL;
n =_prev_item(node);
//we are accessing prev items children, prepare them!
if (n && n->focusable)
efl_ui_focus_object_prepare_logical(n->focusable);
//case 1 there is a item in the parent previous to node, which has children
if (n && T(n).children && !n->redirect_manager)
{
do
{
n = eina_list_last_data_get(T(n).children);
}
while (T(n).children && !n->redirect_manager);
return n;
}
//case 2 there is a item in the parent previous to node, which has no children
if (n)
return n;
//case 3 there is a no item in the parent previous to this one
return T(node).parent;
}
static Node*
_logical_movement(Efl_Ui_Focus_Manager_Calc_Data *pd EINA_UNUSED, Node *upper, Efl_Ui_Focus_Direction direction, Eina_Bool accept_logical)
{
Node* (*deliver)(Node *n);
Node *result;
Eina_List *stack = NULL;
EINA_SAFETY_ON_FALSE_RETURN_VAL(DIRECTION_IS_LOGICAL(direction), NULL);
if (direction == EFL_UI_FOCUS_DIRECTION_NEXT)
deliver = _next;
else
deliver = _prev;
//search as long as we have a none logical parent
result = upper;
do
{
//give up, if we have already been here
if (!!eina_list_data_find(stack, result))
{
eina_list_free(stack);
ERR("Warning cycle detected\n");
return NULL;
}
stack = eina_list_append(stack, result);
if (direction == EFL_UI_FOCUS_DIRECTION_NEXT)
efl_ui_focus_object_prepare_logical(result->focusable);
result = deliver(result);
if (accept_logical)
break;
} while(result && result->type != NODE_TYPE_NORMAL && !result->redirect_manager);
eina_list_free(stack);
return result;
}
static Efl_Ui_Focus_Object*
_request_move(Eo *obj EINA_UNUSED, Efl_Ui_Focus_Manager_Calc_Data *pd, Efl_Ui_Focus_Direction direction, Node *upper, Eina_Bool accept_logical)
{
Node *dir = NULL;
if (!upper)
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;
}
dirty_flush(obj, pd, upper);
if (direction == EFL_UI_FOCUS_DIRECTION_PREVIOUS
|| direction == EFL_UI_FOCUS_DIRECTION_NEXT)
dir = _logical_movement(pd, upper, direction, accept_logical);
else
dir = _coords_movement(pd, upper, direction);
//return the widget
if (dir)
return dir->focusable;
else
return NULL;
}
EOLIAN static Efl_Ui_Focus_Object*
_efl_ui_focus_manager_calc_efl_ui_focus_manager_request_move(Eo *obj, Efl_Ui_Focus_Manager_Calc_Data *pd, Efl_Ui_Focus_Direction direction, Efl_Ui_Focus_Object *child, Eina_Bool logical)
{
Node *child_node;
EINA_SAFETY_ON_FALSE_RETURN_VAL(DIRECTION_CHECK(direction), NULL);
if (!child)
child_node = eina_list_last_data_get(pd->focus_stack);
else
child_node = node_get(obj, pd, child);
if (!child_node)
return NULL;
return _request_move(obj, pd, direction, child_node, logical);
}
static int
_node_depth(Node *node)
{
int i = 0;
while (node && node->tree.parent)
{
node = node->tree.parent;
i++;
}
return i;
}
static Node*
_request_subchild(Node *node)
{
//important! if there are no children _next would return the parent of node which will exceed the limit of children of node
Node *target = NULL;
if (node->tree.children)
{
int new_depth, old_depth = _node_depth(node);
target = node;
//try to find a child that is not logical or has a redirect manager
do
{
if (target != node)
efl_ui_focus_object_prepare_logical(target->focusable);
target = _next(target);
//abort if we are exceeding the childrens of node
new_depth = _node_depth(target);
if (new_depth <= old_depth) target = NULL;
}
while (target && target->type == NODE_TYPE_ONLY_LOGICAL && !target->redirect_manager);
F_DBG("Found node %p", target);
}
return target;
}
EOLIAN static void
_efl_ui_focus_manager_calc_efl_ui_focus_manager_manager_focus_set(Eo *obj, Efl_Ui_Focus_Manager_Calc_Data *pd, Efl_Ui_Focus_Object *focus)
{
Node *node, *last;
Efl_Ui_Focus_Object *last_focusable = NULL, *new_focusable;
Efl_Ui_Focus_Manager *redirect_manager;
Node_Type node_type;
EINA_SAFETY_ON_NULL_RETURN(focus);
//check if node is part of this manager object
node = node_get(obj, pd, focus);
if (!node) return;
if (node->type == NODE_TYPE_ONLY_LOGICAL && !node->redirect_manager)
{
Node *target = NULL;
F_DBG(" %p is logical, fetching the next subnode that is either a redirect or a regular", obj);
//important! if there are no children _next would return the parent of node which will exceed the limit of children of node
efl_ui_focus_object_prepare_logical(node->focusable);
target = _request_subchild(node);
//check if we have found anything
if (target)
{
node = target;
}
else
{
ERR("Could not fetch a node located at %p", node->focusable);
return;
}
}
F_DBG("Manager: %p focusing object %p %s", obj, node->focusable, efl_class_name_get(node->focusable));
//make sure this manager is in the chain of redirects
_manager_in_chain_set(obj, pd);
if (eina_list_last_data_get(pd->focus_stack) == node)
{
//the correct one is focused
if (node->redirect_manager == pd->redirect)
return;
}
node_type = node->type;
new_focusable = node->focusable;
redirect_manager = node->redirect_manager;
last = eina_list_last_data_get(pd->focus_stack);
if (last)
last_focusable = last->focusable;
//remove the object from the list and add it again
if (node_type == NODE_TYPE_NORMAL)
{
pd->focus_stack = eina_list_remove(pd->focus_stack, node);
pd->focus_stack = eina_list_append(pd->focus_stack, node);
}
//capture how we came to the redirect manager
if (redirect_manager)
{
pd->redirect_entry = new_focusable;
}
//set to NULL here, from the event earlier this pointer could be dead.
node = NULL;
//unset redirect manager for the case that its a different one to the one we want
if (pd->redirect && pd->redirect != redirect_manager)
{
Efl_Ui_Focus_Manager *m = obj;
//completely unset the current redirect chain
while (efl_ui_focus_manager_redirect_get(m))
{
Efl_Ui_Focus_Manager *old = m;
m = efl_ui_focus_manager_redirect_get(m);
efl_ui_focus_manager_redirect_set(old, NULL);
}
}
//now check if this is also a listener object
if (redirect_manager)
{
//set the redirect
efl_ui_focus_manager_redirect_set(obj, redirect_manager);
}
/*
Only emit those signals if we are already at the top of the focus stack.
Otherwise focus_get in the callback to that signal might return false.
*/
if (node_type == NODE_TYPE_NORMAL)
{
//populate the new change
if (last_focusable)
efl_ui_focus_object_focus_set(last_focusable, EINA_FALSE);
if (new_focusable)
efl_ui_focus_object_focus_set(new_focusable, EINA_TRUE);
efl_event_callback_call(obj, EFL_UI_FOCUS_MANAGER_EVENT_FOCUSED, last_focusable);
}
}
EOLIAN static void
_efl_ui_focus_manager_calc_efl_ui_focus_manager_setup_on_first_touch(Eo *obj, Efl_Ui_Focus_Manager_Calc_Data *pd, Efl_Ui_Focus_Direction direction, Efl_Ui_Focus_Object *entry)
{
if (direction == EFL_UI_FOCUS_DIRECTION_PREVIOUS && entry)
{
Efl_Ui_Focus_Manager_Logical_End_Detail last;
Efl_Ui_Focus_Manager *rec_manager = obj;
do
{
last = efl_ui_focus_manager_logical_end(rec_manager);
EINA_SAFETY_ON_NULL_RETURN(last.element);
efl_ui_focus_manager_focus_set(rec_manager, last.element);
rec_manager = efl_ui_focus_manager_redirect_get(rec_manager);
}
while (rec_manager);
}
else if (DIRECTION_IS_2D(direction) && entry)
efl_ui_focus_manager_focus_set(obj, entry);
else
efl_ui_focus_manager_focus_set(obj, pd->root->focusable);
}
EOLIAN static Efl_Ui_Focus_Object*
_efl_ui_focus_manager_calc_efl_ui_focus_manager_move(Eo *obj EINA_UNUSED, Efl_Ui_Focus_Manager_Calc_Data *pd, Efl_Ui_Focus_Direction direction)
{
Efl_Ui_Focus_Object *candidate = NULL;
Efl_Ui_Focus_Manager *early, *late;
// for the case that focus is set to a new element, a new redirect
// manager could have been set, to adjust everything
// in the new reidirect manager we have to call the first touch function.
Eina_Bool adjust_redirect_manager = EINA_FALSE;
EINA_SAFETY_ON_FALSE_RETURN_VAL(DIRECTION_CHECK(direction), NULL);
early = efl_ui_focus_manager_redirect_get(obj);
if (pd->redirect)
{
Efl_Ui_Focus_Object *old_candidate = NULL;
candidate = efl_ui_focus_manager_move(pd->redirect, direction);
if (!candidate)
{
Efl_Ui_Focus_Object *new_candidate = NULL;
if (DIRECTION_IS_LOGICAL(direction))
{
// lets just take the redirect_entry
Node *n = node_get(obj, pd, pd->redirect_entry);
new_candidate = _request_move(obj, pd, direction, n, EINA_FALSE);
if (new_candidate)
{
efl_ui_focus_manager_focus_set(obj, new_candidate);
adjust_redirect_manager = EINA_TRUE;
}
else
{
//we set the redirect to NULL since it cannot
//help us, later on the redirect manager can be
//set to the same again, and it is strictly new setted up.
efl_ui_focus_manager_redirect_set(obj, NULL);
pd->redirect_entry = NULL;
}
candidate = new_candidate;
}
else
{
Node *n;
old_candidate = efl_ui_focus_manager_focus_get(pd->redirect);
n = eina_hash_find(pd->node_hash, &old_candidate);
if (n)
new_candidate = _request_move(obj, pd, direction, n, EINA_FALSE);
if (new_candidate)
{
//redirect does not have smth. but we do have.
efl_ui_focus_manager_focus_set(obj, new_candidate);
adjust_redirect_manager = EINA_TRUE;
}
candidate = new_candidate;
}
}
}
else
{
Efl_Ui_Focus_Object *child = NULL;
if (!pd->focus_stack)
{
Node *child_node;
child_node = _no_history_element(pd->node_hash);
if (child_node)
child = child_node->focusable;
}
candidate = efl_ui_focus_manager_request_move(obj, direction, child, EINA_FALSE);
F_DBG("Manager: %p moved to %p %s in direction %d", obj, candidate, efl_class_name_get(candidate), direction);
if (candidate)
{
efl_ui_focus_manager_focus_set(obj, candidate);
adjust_redirect_manager = EINA_TRUE;
}
}
if (adjust_redirect_manager)
{
late = efl_ui_focus_manager_redirect_get(obj);
if (early != late)
{
//this is a new manager, we have to init its case!
if (late)
efl_ui_focus_manager_setup_on_first_touch(late, direction, candidate);
}
}
return candidate;
}
EOLIAN static Eina_Bool
_efl_ui_focus_manager_calc_efl_ui_focus_manager_root_set(Eo *obj EINA_UNUSED, Efl_Ui_Focus_Manager_Calc_Data *pd, Efl_Ui_Focus_Object *root)
{
Node *node;
if (pd->root)
{
ERR("Root element can only be set once!");
return EINA_FALSE;
}
node = _register(obj, pd, root, NULL);
node->type = NODE_TYPE_ONLY_LOGICAL;
pd->root = node;
return EINA_TRUE;
}
EOLIAN static Efl_Ui_Focus_Object*
_efl_ui_focus_manager_calc_efl_ui_focus_manager_root_get(const Eo *obj EINA_UNUSED, Efl_Ui_Focus_Manager_Calc_Data *pd)
{
if (!pd->root) return NULL;
return pd->root->focusable;
}
EOLIAN static Efl_Object*
_efl_ui_focus_manager_calc_efl_object_finalize(Eo *obj, Efl_Ui_Focus_Manager_Calc_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;
}
static Eina_List*
_convert(Border b)
{
Eina_List *n, *par = NULL;
Node *node;
EINA_LIST_FOREACH(b.partners, n, node)
par = eina_list_append(par, node->focusable);
EINA_LIST_FOREACH(b.one_direction, n, node)
par = eina_list_append(par, node->focusable);
return par;
}
EOLIAN static Efl_Ui_Focus_Object*
_efl_ui_focus_manager_calc_efl_ui_focus_manager_manager_focus_get(const Eo *obj EINA_UNUSED, Efl_Ui_Focus_Manager_Calc_Data *pd)
{
Node *upper = NULL;
if (pd->redirect && pd->redirect_entry) return pd->redirect_entry;
upper = eina_list_last_data_get(pd->focus_stack);
if (!upper)
return NULL;
return upper->focusable;
}
EOLIAN static Efl_Ui_Focus_Relations*
_efl_ui_focus_manager_calc_efl_ui_focus_manager_fetch(Eo *obj, Efl_Ui_Focus_Manager_Calc_Data *pd, Efl_Ui_Focus_Object *child)
{
Efl_Ui_Focus_Relations *res;
Node *n, *tmp;
n = node_get(obj, pd, child);
if (!n)
return NULL;
res = calloc(1, sizeof(Efl_Ui_Focus_Relations));
dirty_flush(obj, pd, n);
//make sure to prepare_logical so next and prev are correctly
if (n->tree.parent)
efl_ui_focus_object_prepare_logical(n->tree.parent->focusable);
efl_ui_focus_object_prepare_logical(n->focusable);
#define DIR_CLONE(dir) _convert(DIRECTION_ACCESS(n,dir));
res->right = DIR_CLONE(EFL_UI_FOCUS_DIRECTION_RIGHT);
res->left = DIR_CLONE(EFL_UI_FOCUS_DIRECTION_LEFT);
res->top = DIR_CLONE(EFL_UI_FOCUS_DIRECTION_UP);
res->down = DIR_CLONE(EFL_UI_FOCUS_DIRECTION_DOWN);
res->next = (tmp = _next(n)) ? tmp->focusable : NULL;
res->prev = (tmp = _prev(n)) ? tmp->focusable : NULL;
res->position_in_history = eina_list_data_idx(pd->focus_stack, n);
res->node = child;
res->logical = (n->type == NODE_TYPE_ONLY_LOGICAL);
if (T(n).parent)
res->parent = T(n).parent->focusable;
res->redirect = n->redirect_manager;
#undef DIR_CLONE
return res;
}
EOLIAN static void
_efl_ui_focus_manager_calc_class_constructor(Efl_Class *c EINA_UNUSED)
{
_focus_log_domain = eina_log_domain_register("elementary-focus", EINA_COLOR_CYAN);
}
EOLIAN static void
_efl_ui_focus_manager_calc_class_destructor(Efl_Class *c EINA_UNUSED)
{
eina_log_domain_unregister(_focus_log_domain);
_focus_log_domain = -1;
}
EOLIAN static Efl_Ui_Focus_Manager_Logical_End_Detail
_efl_ui_focus_manager_calc_efl_ui_focus_manager_logical_end(Eo *obj EINA_UNUSED, Efl_Ui_Focus_Manager_Calc_Data *pd)
{
Node *child = pd->root;
Efl_Ui_Focus_Manager_Logical_End_Detail ret = { 0, NULL};
EINA_SAFETY_ON_NULL_RETURN_VAL(child, ret);
//we need to return the most lower right element
while ((child) && (T(child).children) && (!child->redirect_manager))
child = eina_list_last_data_get(T(child).children);
while ((child) && (child->type != NODE_TYPE_NORMAL) && (!child->redirect_manager))
child = _prev(child);
if (child)
{
ret.is_regular_end = child->type == NODE_TYPE_NORMAL;
ret.element = child->focusable;
}
return ret;
}
EOLIAN static void
_efl_ui_focus_manager_calc_efl_ui_focus_manager_reset_history(Eo *obj EINA_UNUSED, Efl_Ui_Focus_Manager_Calc_Data *pd)
{
Efl_Ui_Focus_Object *last_focusable;
if (!pd->focus_stack) return;
last_focusable = _focus_stack_unfocus_last(pd);
pd->focus_stack = eina_list_free(pd->focus_stack);
efl_event_callback_call(obj, EFL_UI_FOCUS_MANAGER_EVENT_FOCUSED, last_focusable);
}
EOLIAN static void
_efl_ui_focus_manager_calc_efl_ui_focus_manager_pop_history_stack(Eo *obj EINA_UNUSED, Efl_Ui_Focus_Manager_Calc_Data *pd)
{
Efl_Ui_Focus_Object *last_focusable;
Node *last;
if (!pd->focus_stack) return;
last_focusable = _focus_stack_unfocus_last(pd);
//get now the highest, and unfocus that!
last = eina_list_last_data_get(pd->focus_stack);
if (last) efl_ui_focus_object_focus_set(last->focusable, EINA_TRUE);
efl_event_callback_call(obj, EFL_UI_FOCUS_MANAGER_EVENT_FOCUSED, last_focusable);
}
EOLIAN static Efl_Ui_Focus_Object*
_efl_ui_focus_manager_calc_efl_ui_focus_manager_request_subchild(Eo *obj, Efl_Ui_Focus_Manager_Calc_Data *pd, Efl_Ui_Focus_Object *child_obj)
{
Node *child, *target;
child = node_get(obj, pd, child_obj);
if (!child) return NULL;
target = _request_subchild(child);
if (target) return target->focusable;
return NULL;
}
EOLIAN static void
_efl_ui_focus_manager_calc_efl_object_dbg_info_get(Eo *obj, Efl_Ui_Focus_Manager_Calc_Data *pd, Efl_Dbg_Info *root)
{
efl_dbg_info_get(efl_super(obj, MY_CLASS), root);
Efl_Dbg_Info *append, *group = EFL_DBG_INFO_LIST_APPEND(root, "Efl.Ui.Focus.Manager");
Eina_Iterator *iter;
Node *node;
append = EFL_DBG_INFO_LIST_APPEND(group, "children");
iter = eina_hash_iterator_data_new(pd->node_hash);
EINA_ITERATOR_FOREACH(iter, node)
{
EFL_DBG_INFO_APPEND(append, "-", EINA_VALUE_TYPE_UINT64, node->focusable);
}
eina_iterator_free(iter);
}
#define EFL_UI_FOCUS_MANAGER_CALC_EXTRA_OPS \
EFL_OBJECT_OP_FUNC(efl_dbg_info_get, _efl_ui_focus_manager_calc_efl_object_dbg_info_get)
#include "efl_ui_focus_manager_calc.eo.c"