#ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include static int _focus_checker_dom = -1; //critical errors are errors which can make the checks produce wrong results #define CRIT(...) EINA_LOG_DOM_CRIT(_focus_checker_dom, __VA_ARGS__) //errors are reporting failures of the test #define ERR(...) EINA_LOG_DOM_ERR(_focus_checker_dom, __VA_ARGS__) #define WRN(...) EINA_LOG_DOM_WARN(_focus_checker_dom, __VA_ARGS__) #define INF(...) EINA_LOG_DOM_INFO(_focus_checker_dom, __VA_ARGS__) #define DBG(...) EINA_LOG_DOM_DBG(_focus_checker_dom, __VA_ARGS__) EAPI const char *clouseau_module_name = "Focus Graph Checker"; typedef struct { Eina_List *right; Eina_List *left; Eina_List *top; Eina_List *down; void *next; void *prev; void *manager; void *redirect; } Focus_Relations; typedef struct _Focus_Manager Focus_Manager; struct _Focus_Manager { void *ptr; void *first_child; Eina_Hash *objects; Eina_List *redirects; struct { Focus_Manager *parent; int depth; } manager_tree_check; //only valid in execution of _managers_link_check(); }; static Eina_Hash *managers = NULL; static Focus_Manager* _manager_get(void *manager) { Focus_Manager *m; m = eina_hash_find(managers, &manager); if (!m) { m = calloc(1, sizeof(Focus_Manager)); m->ptr = manager; m->objects = eina_hash_pointer_new(NULL); eina_hash_add(managers, &manager, m); } return m; } static void _focus_manager_redirect_add(Focus_Manager *m, void *redirect) { m->redirects = eina_list_append(m->redirects, redirect); } static void _pointer_free(void *data) { free(data); } static Eina_Bool _init(void) { eina_init(); _focus_checker_dom = eina_log_domain_register("Focus Checker", EINA_COLOR_CYAN); eina_log_domain_level_set("Focus Checker", EINA_LOG_LEVEL_INFO); managers = eina_hash_pointer_new(_pointer_free); return EINA_TRUE; } static void _shutdown(void) { eina_log_domain_unregister(_focus_checker_dom); eina_shutdown(); } static Eina_List* _list_ptr_get(Efl_Dbg_Info *info) { Eina_Value_List list; Efl_Dbg_Info *data; Eina_List *n, *result = NULL; if (eina_value_type_get(&info->value) != EINA_VALUE_TYPE_LIST) { CRIT("Expected list type"); return NULL; } eina_value_pget(&info->value, &list); EINA_LIST_FOREACH(list.list, n, data) { unsigned long long ptr; eina_value_get(&data->value, &ptr); result = eina_list_append(result, (void*)ptr); } return result; } static Focus_Relations* _tree_it_convert(Clouseau_Tree_Item *it) { Focus_Relations *ret; Efl_Dbg_Info *widget, *focus, *next, *prev, *right, *left, *top, *down, *manager, *redirect; clouseau_tree_item_from_legacy_convert(it); widget = clouseau_eo_info_find(it->new_eo_info , "Elm_Widget"); if (!widget) { return NULL; } focus = clouseau_eo_info_find(widget, "Focus"); if (!focus) { return NULL; } next = clouseau_eo_info_find(focus, "next"); prev = clouseau_eo_info_find(focus, "prev"); right = clouseau_eo_info_find(focus, "right"); left = clouseau_eo_info_find(focus, "left"); top = clouseau_eo_info_find(focus, "top"); down = clouseau_eo_info_find(focus, "down"); manager = clouseau_eo_info_find(focus, "manager"); redirect = clouseau_eo_info_find(focus, "redirect"); if (!next || !prev || !right || !left || !top || !down || !manager || !redirect) { CRIT("Widget %p did not present the full set of properties.", (void*)it->ptr); return NULL; } ret = calloc(1, sizeof(Focus_Relations)); eina_value_get(&next->value, &ret->next); eina_value_get(&prev->value, &ret->prev); ret->right = _list_ptr_get(right); ret->left = _list_ptr_get(left); ret->top = _list_ptr_get(top); ret->down = _list_ptr_get(down); eina_value_get(&manager->value, &ret->manager); eina_value_get(&redirect->value, &ret->redirect); return ret; } static void _add(Clouseau_Tree_Item *it) { Clouseau_Tree_Item *treeit; Focus_Relations *rel; Focus_Manager *m; Eina_List *n; rel = _tree_it_convert(it); if (rel) { m = _manager_get(rel->manager); if (!m->first_child) m->first_child = (void*)it->ptr; if (rel->redirect) _focus_manager_redirect_add(m, rel->redirect); eina_hash_add(m->objects, &it->ptr, rel); } EINA_LIST_FOREACH(it->children , n, treeit) { _add(treeit); } } /* * Check that all widgets are linked to each other * * This is working by duplicating the hash of objects,. * Exploring a item means that all the right left top * down next and prev directions are added into the set of "need explotation". * At first one random element is added to the set of need exploration. * Then remove each time a element from the set, if the element is still * in the duplicated hash, the element is explored, if not the element is ignored. * * If the set is now empty and the hash has still elements there must be a set * of not linked elements to the other elements. */ Eina_Bool _duplicate(const Eina_Hash *hash, const void *key, void *data, void *fdata) { eina_hash_add(fdata, key, data); return EINA_TRUE; } Eina_Bool _count(const Eina_Hash *hash, const void *key, void *data, void *fdata) { (*((int*)fdata))++; return EINA_TRUE; } static void _manager_check(Focus_Manager *manager) { Eina_Hash *dup; Eina_Iterator *itr; void *ptr; Eina_List *runner = NULL, *n, *n_next; dup = eina_hash_pointer_new(NULL); eina_hash_foreach(manager->objects, _duplicate, dup); runner = eina_list_append(runner, manager->first_child); do { void *ptr; Focus_Relations *rel; ptr = eina_list_data_get(runner); runner = eina_list_remove(runner, ptr); rel = eina_hash_find(manager->objects, &ptr); if (!rel) { ERR("In manager %p %p is used in a reference, but not part of the manager", manager->ptr , ptr); continue; } rel = eina_hash_find(dup, &ptr); if (!rel) { continue; } eina_hash_del_by_key(dup, &ptr); runner = eina_list_merge(runner, rel->left); runner = eina_list_merge(runner, rel->right); runner = eina_list_merge(runner, rel->top); runner = eina_list_merge(runner, rel->down); runner = eina_list_append(runner, rel->next); runner = eina_list_append(runner, rel->prev); } while(eina_list_count(runner) != 0); //if we are done and sonething is left in the hash we have a not connected subset in the manager. BAD int count; eina_hash_foreach(dup, _count, &count); if (count > 0) { ERR("Manager %p has a disconnected set of widgets", manager->ptr); /* FIXME print all the children */ } else INF("Manager %p is checked and fine", manager->ptr); } /** * Functions to check that the with redirect created objects are a tree * * This works by iterating 3 times throuw the elements, at first, we are setting the parent of each child object * In the second stage we are searching for the element with no parent, which is the root. * And we ensure that there is just one root. * In the last stage we are calculating the depth of each manager, if in some element a other depth is laready * calculated we know that there is at least one cycle. */ static Eina_Bool _depth_calc(Focus_Manager *m, int depth) { Eina_List *n; Focus_Manager *m2; Eina_Bool suc = EINA_TRUE; if (m->manager_tree_check.depth != 0) { ERR("Manager %p is part of a cycle in the tree!", m->ptr); return EINA_FALSE; } m->manager_tree_check.depth = depth; EINA_LIST_FOREACH(m->redirects, n, m2) { if (!_depth_calc(m2, depth + 1)) suc = EINA_FALSE; } return suc; } static void _managers_link_check(void) { Focus_Manager *root = NULL, *m; Eina_Hash *manager_cache; Eina_Iterator *itr; //check if this things contains cycles manager_cache = eina_hash_pointer_new(NULL); //First stage set parents; itr = eina_hash_iterator_data_new(managers); EINA_ITERATOR_FOREACH(itr, m) { Focus_Manager *m2; Eina_List *n; //now we are setting the children EINA_LIST_FOREACH(m->redirects, n, m2) { m2->manager_tree_check.parent = m; } } eina_iterator_free(itr); //second stage, search for the root itr = eina_hash_iterator_data_new(managers); EINA_ITERATOR_FOREACH(itr, m) { if (!m->manager_tree_check.parent && !root) root = m; else if (!m->manager_tree_check.parent && root) ERR("Error, there is already a root manager (%p), but %p does not have any parents", root->ptr, m->ptr); } eina_iterator_free(itr); //thirds stage, check that this is really a tree if (_depth_calc(root, 1)) INF("All managers are linked in the form of a tree"); } EAPI void clouseau_client_module_run(Eina_List *tree) { Clouseau_Tree_Item *treeit; Eina_List *n; Eina_Iterator *managers_itr; Focus_Manager *manager; //move everything into a pointer hash EINA_LIST_FOREACH(tree, n, treeit) _add(treeit); //check that there are no islands in the graph of a manager managers_itr = eina_hash_iterator_data_new(managers); EINA_ITERATOR_FOREACH(managers_itr, manager) { _manager_check(manager); } //check that all managers are linked to at least one other manager _managers_link_check(); } EINA_MODULE_INIT(_init); EINA_MODULE_SHUTDOWN(_shutdown);