#ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include "enventor_private.h" /* This is a node for a list structure. Each node has its parent name list. */ typedef struct keyword_s { char *name; char *desc; Eina_List *children_list; //Children keyword data list int ref_cnt; //Reference count } keyword_data; typedef struct ref_s { Eina_File *source_file; keyword_data *keyword_root; //Root node of keyword data tree char *keyword_name; char *keyword_desc; edit_data *ed; Evas_Object *event_rect; Evas_Object *layout; } ref_data; typedef struct inherit_s { keyword_data *derived_keyword; //Derived keyword char *base_keyword_full_name; //Base keyword's name (e.g. collections.group) } inherit_data; static ref_data *g_rd = NULL; /*****************************************************************************/ /* Internal method implementation */ /*****************************************************************************/ static void keyword_data_free(keyword_data *keyword); static void ref_event_rect_delete(void); static void ref_layout_delete(void); static void keyword_data_free(keyword_data *keyword) { if (!keyword) return; keyword->ref_cnt--; if (keyword->ref_cnt > 0) return; if (keyword->name) free(keyword->name); if (keyword->desc) free(keyword->desc); keyword_data *child_keyword; EINA_LIST_FREE(keyword->children_list, child_keyword) { keyword_data_free(child_keyword); } free(keyword); } static char * cursor_keyword_name_find(Evas_Object *entry) { Evas_Object *tb = NULL; Evas_Textblock_Cursor *cur_orig = NULL; Evas_Textblock_Cursor *cur_begin = NULL; Evas_Textblock_Cursor *cur_end = NULL; int cur_orig_pos = 0; int cur_begin_pos = 0; char *cur_orig_ptr = NULL; char *cur_begin_ptr = NULL; char *cur_end_ptr = NULL; char *cur_text = NULL; char *keyword_name = NULL; tb = elm_entry_textblock_get(entry); cur_orig = evas_object_textblock_cursor_get(tb); if (!cur_orig) return NULL; //Show keyword reference only if cursor is located in a correct keyword. cur_text = evas_textblock_cursor_content_get(cur_orig); if (!cur_text || (!isalnum(*cur_text) && (*cur_text != '_'))) goto end; free(cur_text); cur_text = NULL; cur_orig_pos = evas_textblock_cursor_pos_get(cur_orig); //Find beginning cursor position of the keyword. cur_begin = evas_object_textblock_cursor_new(tb); if (!cur_begin) goto end; evas_textblock_cursor_pos_set(cur_begin, cur_orig_pos); if (!evas_textblock_cursor_word_start(cur_begin)) goto end; //Find ending cursor position of the keyword. cur_end = evas_object_textblock_cursor_new(tb); if (!cur_end) goto end; evas_textblock_cursor_pos_set(cur_end, cur_orig_pos); if (!evas_textblock_cursor_word_end(cur_end)) goto end; /* Move ending cursor by one character because cursor_range_text_get does not include character of ending cursor. */ evas_textblock_cursor_char_next(cur_end); cur_text = evas_textblock_cursor_range_text_get(cur_begin, cur_end, EVAS_TEXTBLOCK_TEXT_PLAIN); if (!cur_text) goto end; //Need to check if cursor text contains special character such as '.'. cur_begin_pos = evas_textblock_cursor_pos_get(cur_begin); cur_orig_ptr = cur_text + (cur_orig_pos - cur_begin_pos); //Find valid keyword beginning position from cursor text. char *cur_ptr = cur_orig_ptr; while (cur_ptr >= cur_text) { if (isalnum(*cur_ptr) || (*cur_ptr == '_')) cur_begin_ptr = cur_ptr; else break; cur_ptr--; } //Find valid keyword ending position from cursor text. cur_ptr = cur_orig_ptr; char *cur_text_end = cur_text + strlen(cur_text) - 1; while (cur_ptr <= cur_text_end) { if (isalnum(*cur_ptr) || (*cur_ptr == '_')) cur_end_ptr = cur_ptr; else break; cur_ptr++; } keyword_name = eina_strndup(cur_begin_ptr, (cur_end_ptr - cur_begin_ptr + 1)); end: if (cur_begin) evas_textblock_cursor_free(cur_begin); if (cur_end) evas_textblock_cursor_free(cur_end); if (cur_text) free(cur_text); return keyword_name; } /* Find keyword hierarchy in the given text and keyword name. (e.g. Keyword hierarchy of "parts" is collections -> group -> parts) Return keyword name list (e.g. collections -> group -> parts). */ static Eina_List * keyword_hierarchy_find(const char *text, const char *keyword_name) { ref_data *rd = g_rd; if (!rd) return NULL; if (!rd->ed) return NULL; if (!text) return NULL; if (!keyword_name) return NULL; Eina_List *keyword_hierarchy = eina_list_append(NULL, strdup(keyword_name)); //Check from the end of the text. char *ptr = (char *)(text + ((strlen(text) - 1) * sizeof(char))); int height = 0; int next_height = height + 1; const char *parent_begin = NULL; const char *parent_end = NULL; //Check if dot('.') grammar is valid to identify parent keyword. Eina_Bool dot_grammar_valid = EINA_TRUE; while (text <= ptr) { if ((*ptr == '{') || (dot_grammar_valid && (*ptr == '.'))) { if (dot_grammar_valid) dot_grammar_valid = EINA_FALSE; height++; if (height == next_height) { ptr--; if (text > ptr) break; parent_begin = NULL; parent_end = NULL; while (text <= ptr) { if (isspace(*ptr)) { if (parent_end) { parent_begin = ptr + 1; break; } } else { if (!parent_end) parent_end = ptr; } ptr--; } if (!parent_end) continue; if (parent_end && !parent_begin) parent_begin = text; char *parent_name = eina_strndup(parent_begin, (parent_end - parent_begin + 1)); keyword_hierarchy = eina_list_prepend(keyword_hierarchy, parent_name); next_height++; } } else if (*ptr == '}') { if (dot_grammar_valid) dot_grammar_valid = EINA_FALSE; height--; } else if (dot_grammar_valid && !isalnum(*ptr)) { dot_grammar_valid = EINA_FALSE; } ptr--; } //In case of sub items, it won't contain "collections". //We added it arbitrary. if (!edit_is_main_file(rd->ed)) { keyword_hierarchy = eina_list_prepend(keyword_hierarchy, strdup("collections")); } return keyword_hierarchy; } static keyword_data * keyword_data_find_internal(Eina_List *keyword_list, const char *keyword_name) { if (!keyword_list) return NULL; if (!keyword_name) return NULL; Eina_List *l = NULL; keyword_data *keyword = NULL; EINA_LIST_FOREACH(keyword_list, l, keyword) { if (!strcmp(keyword->name, keyword_name)) return keyword; } return NULL; } /* Find keyword which has the given keyword hierarchy. (e.g. Find "group" keyword which has keyword hierarchy, collections -> group) */ static keyword_data * keyword_data_find(keyword_data *keyword_root, Eina_List *keyword_hierarchy) { if (!keyword_root) return NULL; if (!keyword_hierarchy) return NULL; Eina_List *l = NULL; char *keyword_name = NULL; keyword_data *keyword = keyword_root; keyword_data *found_keyword = NULL; EINA_LIST_FOREACH(keyword_hierarchy, l, keyword_name) { keyword = keyword_data_find_internal(keyword->children_list, keyword_name); found_keyword = keyword; if (!keyword) break; } return found_keyword; } static keyword_data * cursor_keyword_data_find(Evas_Object *entry, keyword_data *keyword_root, const char *keyword_name) { keyword_data *found_keyword = NULL; if (!entry) return NULL; if (!keyword_root) return NULL; if (!keyword_name) return NULL; //Get text before cursor position. Evas_Object *tb = elm_entry_textblock_get(entry); Evas_Textblock_Cursor *cur_begin = evas_object_textblock_cursor_new(tb); Evas_Textblock_Cursor *cur_end = evas_object_textblock_cursor_get(tb); char *utf8_text = NULL; Eina_List *keyword_hierarchy = NULL; /* FIXME: Getting text from range with EVAS_TEXTBLOCK_TEXT_PLAIN generates garbage characters. So please use EVAS_TEXTBLOCK_TEXT_PLAIN after the bug is fixed. */ char *markup_text = evas_textblock_cursor_range_text_get(cur_begin, cur_end, EVAS_TEXTBLOCK_TEXT_MARKUP); if (!markup_text) goto end; utf8_text = evas_textblock_text_markup_to_utf8(NULL, markup_text); free(markup_text); markup_text = NULL; //Find keyword hierarchy of the selected keyword in text. keyword_hierarchy = keyword_hierarchy_find((const char *)utf8_text, keyword_name); found_keyword = keyword_data_find(keyword_root, keyword_hierarchy); end: if (cur_begin) evas_textblock_cursor_free(cur_begin); if (utf8_text) free(utf8_text); char *keyword_hierarchy_name = NULL; EINA_LIST_FREE(keyword_hierarchy, keyword_hierarchy_name) { free(keyword_hierarchy_name); } return found_keyword; } /* Parse keyword's full name into keyword name list. (e.g. collections.group.parts is parsed into collections -> group -> parts) Return keyword name list. */ static Eina_List * keyword_full_name_parse(const char *keyword_full_name) { if (!keyword_full_name) return NULL; Eina_List *keyword_hierarchy = NULL; int len = strlen(keyword_full_name); char *keyword_name = NULL; char *keyword_begin = (char *)keyword_full_name; char *keyword_end = (char *)keyword_full_name + len; char *dot = strstr(keyword_full_name, "."); while (dot) { keyword_name = eina_strndup(keyword_begin, (dot - keyword_begin)); keyword_hierarchy = eina_list_append(keyword_hierarchy, keyword_name); keyword_begin = dot + 1; //Move pointer after ".". if (keyword_begin >= keyword_end) break; dot = strstr(keyword_begin, "."); } keyword_hierarchy = eina_list_append(keyword_hierarchy, eina_strndup(keyword_begin, (keyword_end - keyword_begin))); return keyword_hierarchy; } /* Find base keyword's full name for keyword inheritance. inherit_end indicates the ending position of #inherit expression. Return base keyword's full name. (e.g. collections.group.parts.part) */ static char * inherit_find(const char *text_begin, const char *text_end, char **inherit_end) { if (!text_begin) return NULL; if (!text_end) return NULL; char *base_keyword_full_name = NULL; char *base_keyword_begin = NULL; char *base_keyword_end = NULL; base_keyword_begin = strstr(text_begin, "#inherit \""); if (!base_keyword_begin || (base_keyword_begin > text_end)) return NULL; base_keyword_begin += 10; //Move pointer after "\"". base_keyword_end = strstr(base_keyword_begin, "\";"); if (!base_keyword_end || (base_keyword_end > text_end)) return NULL; base_keyword_end--; base_keyword_full_name = eina_strndup((const char *)base_keyword_begin, (base_keyword_end - base_keyword_begin + 1)); //Indicates the ending position of #inherit expression. *inherit_end = base_keyword_end + 3; //Move pointer after "\";". return base_keyword_full_name; } static void keyword_tree_load_internal(keyword_data *keyword_root, char **ptr, Eina_List **inherit_list) { if (!keyword_root) return; if (!*ptr) return; if (!inherit_list) return; int len = strlen(*ptr); const char *text_end = *ptr + len; while (*ptr < text_end) { keyword_data *keyword = NULL; char *keyword_name = NULL; char *keyword_desc = NULL; char *block_begin = NULL; char *block_end = NULL; //Find beginning position of keyword block to check pointer. block_begin = strstr(*ptr, "{"); if (!block_begin) break; //Find ending position of keyword block to check pointer. block_end = strstr(*ptr, "}"); if (!block_end) break; if (block_begin > block_end) break; //Find keyword name. char *keyword_name_begin = NULL; char *keyword_name_end = NULL; for ( ; *ptr < block_begin; (*ptr)++) { if (!isspace(**ptr)) { if (!keyword_name_begin) { keyword_name_begin = *ptr; keyword_name_end = *ptr; } else keyword_name_end = *ptr; } } if (!keyword_name_begin || !keyword_name_end) break; keyword_name = eina_strndup(keyword_name_begin, (keyword_name_end - keyword_name_begin + 1)); if (!keyword_name) break; (*ptr)++; //Move pointer after "{". //Find keyword description. char *keyword_desc_begin = NULL; char *keyword_desc_end = NULL; keyword_desc_begin = strstr(*ptr, "\""); if (!keyword_desc_begin) { free(keyword_name); break; } keyword_desc_begin++; keyword_desc_end = strstr(keyword_desc_begin, "\";"); if (!keyword_desc_end) { free(keyword_name); break; } keyword_desc_end--; keyword_desc = eina_strndup((const char *)keyword_desc_begin, (keyword_desc_end - keyword_desc_begin + 1)); if (!keyword_desc) { free(keyword_name); break; } *ptr = keyword_desc_end + 3; //Move pointer after "\";". //Create a new keyword data and Append it to children list. keyword = calloc(1, sizeof(keyword_data)); keyword->name = keyword_name; keyword->desc = keyword_desc; keyword->ref_cnt = 1; //Find the next block regardless of the block type (i.e. "{" or "}"). char *next_block = strstr(*ptr, "{"); if (!next_block || (next_block > block_end)) next_block = block_end; //Find #inherit expression for keyword inheritance. inherit_data *inherit = NULL; char *base_keyword_full_name = NULL; char *inherit_end = NULL; base_keyword_full_name = inherit_find(*ptr, next_block, &inherit_end); if (base_keyword_full_name) { inherit = calloc(1, sizeof(inherit_data)); inherit->derived_keyword = keyword; inherit->base_keyword_full_name = base_keyword_full_name; *inherit_list = eina_list_append(*inherit_list, inherit); *ptr = inherit_end; } //Set child keyword node recursively. keyword_tree_load_internal(keyword, ptr, inherit_list); //Find ending position of keyword block to update pointer. block_end = strstr(*ptr, "}"); if (!block_end) { if (inherit) { *inherit_list = eina_list_remove(*inherit_list, inherit); free(inherit->base_keyword_full_name); free(inherit); } keyword_data_free(keyword); break; } *ptr = block_end + 1; //Move pointer after "}". keyword_root->children_list = eina_list_append(keyword_root->children_list, keyword); } } /* Load keywords' names and descriptions into a tree structure. */ static void keyword_tree_load(keyword_data *keyword_root, char **ptr) { if (!keyword_root) return; if (!*ptr) return; Eina_List *inherit_list = NULL; inherit_data *inherit = NULL; //Load keywords' names and descriptions. keyword_tree_load_internal(keyword_root, ptr, &inherit_list); //Update keywords' children lists based on keyword inheritance. EINA_LIST_FREE(inherit_list, inherit) { Eina_List *keyword_hierarchy = NULL; keyword_data *base_keyword = NULL; keyword_hierarchy = keyword_full_name_parse(inherit->base_keyword_full_name); /* Copy base child keyword to derive child keyword if it is not overriding keyword. */ base_keyword = keyword_data_find(keyword_root, keyword_hierarchy); if (base_keyword && (base_keyword->children_list)) { Eina_List **derived_children_list = NULL; Eina_List **base_children_list = NULL; Eina_List *l = NULL; keyword_data *base_child_keyword = NULL; derived_children_list = &(inherit->derived_keyword->children_list); base_children_list = &(base_keyword->children_list); EINA_LIST_FOREACH(*base_children_list, l, base_child_keyword) { keyword_data *overriding_keyword = keyword_data_find_internal(*derived_children_list, base_child_keyword->name); if (!overriding_keyword) { *derived_children_list = eina_list_append(*derived_children_list, base_child_keyword); base_child_keyword->ref_cnt++; } } } char *keyword_hierarchy_name = NULL; EINA_LIST_FREE(keyword_hierarchy, keyword_hierarchy_name) { free(keyword_hierarchy_name); } free(inherit->base_keyword_full_name); free(inherit); } } static void ref_load(ref_data *rd) { if (!rd) return; char buf[PATH_MAX]; snprintf(buf, sizeof(buf), "%s/reference/reference.src", eina_prefix_data_get(PREFIX)); //Open source file. rd->source_file = eina_file_open((const char *)buf, EINA_FALSE); if (!rd->source_file) return; //Load text from source file. char *text = (char *)eina_file_map_all(rd->source_file, EINA_FILE_POPULATE); if (!text) goto end; if (rd->keyword_root) { keyword_data_free(rd->keyword_root); rd->keyword_root = NULL; } //Create keyword root node. keyword_data *keyword_root = calloc(1, sizeof(keyword_data)); keyword_root->name = NULL; keyword_root->desc = NULL; keyword_root->ref_cnt = 1; char *ptr = text; //Load keyword data tree from text. keyword_tree_load(keyword_root, &ptr); rd->keyword_root = keyword_root; end: if (text) eina_file_map_free(rd->source_file, text); eina_file_close(rd->source_file); rd->source_file = NULL; } static void event_rect_mouse_down_cb(void *data EINA_UNUSED, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) { ref_event_rect_delete(); ref_layout_delete(); } static void entry_unfocused_cb(void *data EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) { ref_event_rect_delete(); ref_layout_delete(); } static void entry_move_cb(void *data EINA_UNUSED, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) { ref_event_rect_delete(); ref_layout_delete(); } static void entry_changed_cb(void *data EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) { ref_event_rect_delete(); ref_layout_delete(); } static void entry_changed_user_cb(void *data EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) { ref_event_rect_delete(); ref_layout_delete(); } static void entry_cursor_changed_cb(void *data EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) { ref_event_rect_delete(); ref_layout_delete(); } static void entry_cursor_changed_manual_cb(void *data EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) { ref_event_rect_delete(); ref_layout_delete(); } static void ref_event_rect_delete(void) { ref_data *rd = g_rd; if (!rd) return; if (!rd->event_rect) return; evas_object_del(rd->event_rect); rd->event_rect = NULL; } static void ref_layout_delete(void) { ref_data *rd = g_rd; if (!rd) return; if (!rd->layout) return; evas_object_del(rd->layout); rd->layout = NULL; if (!rd->ed) return; //Delete entry callbacks to delete reference layout. Evas_Object *edit_entry = edit_entry_get(rd->ed); evas_object_event_callback_del(edit_entry, EVAS_CALLBACK_MOVE, entry_move_cb); evas_object_smart_callback_del(edit_entry, "unfocused", entry_unfocused_cb); evas_object_smart_callback_del(edit_entry, "changed", entry_changed_cb); evas_object_smart_callback_del(edit_entry, "changed,user", entry_changed_user_cb); evas_object_smart_callback_del(edit_entry, "cursor,changed", entry_cursor_changed_cb); evas_object_smart_callback_del(edit_entry, "cursor,changed,manual", entry_cursor_changed_manual_cb); } static Evas_Object * ref_event_rect_create(Evas_Object *edit_obj) { if (!edit_obj) return NULL; //event_rect catches mouse down event, which delete reference layout. Evas_Object *win = elm_object_top_widget_get(edit_obj); Evas *e = evas_object_evas_get(win); Evas_Object *rect = evas_object_rectangle_add(e); evas_object_repeat_events_set(rect, EINA_TRUE); evas_object_color_set(rect, 0, 0, 0, 0); evas_object_event_callback_add(rect, EVAS_CALLBACK_MOUSE_DOWN, event_rect_mouse_down_cb, NULL); evas_object_size_hint_weight_set(rect, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); elm_win_resize_object_add(win, rect); evas_object_show(rect); return rect; } static Evas_Object * ref_layout_create(Evas_Object *edit_entry, const char *desc) { if (!edit_entry) return NULL; if (!desc) return NULL; Evas_Object *layout = elm_layout_add(edit_entry); if (!layout) { EINA_LOG_ERR("Failed to allocate Memory!"); return NULL; } elm_layout_file_set(layout, EDJE_PATH, "reference_layout"); //Do not apply edit entry's font scale to reference layout. elm_object_scale_set(layout, 1.0); Evas_Object *entry = elm_entry_add(layout); elm_object_style_set(entry, "enventor"); elm_entry_editable_set(entry, EINA_FALSE); elm_entry_scrollable_set(entry, EINA_TRUE); evas_object_size_hint_weight_set(entry, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); evas_object_size_hint_align_set(entry, EVAS_HINT_FILL, EVAS_HINT_FILL); elm_entry_entry_set(entry, desc); elm_object_part_content_set(layout, "elm.swallow.content", entry); evas_object_show(layout); //Add entry callbacks to delete reference layout. evas_object_event_callback_add(edit_entry, EVAS_CALLBACK_MOVE, entry_move_cb, NULL); evas_object_smart_callback_add(edit_entry, "unfocused", entry_unfocused_cb, NULL); evas_object_smart_callback_add(edit_entry, "changed", entry_changed_cb, NULL); evas_object_smart_callback_add(edit_entry, "changed,user", entry_changed_user_cb, NULL); evas_object_smart_callback_add(edit_entry, "cursor,changed", entry_cursor_changed_cb, NULL); evas_object_smart_callback_add(edit_entry, "cursor,changed,manual", entry_cursor_changed_manual_cb, NULL); return layout; } /*****************************************************************************/ /* Externally accessible calls */ /*****************************************************************************/ void ref_show(edit_data *ed) { ref_data *rd = g_rd; Evas_Object *edit_obj = NULL; Evas_Object *edit_entry = NULL; if (!rd) return; if (!ed) return; rd->ed = ed; //Return if reference layout is already shown. if (rd->event_rect || rd->layout) return; edit_obj = edit_obj_get(ed); edit_entry = edit_entry_get(ed); //Set keyword name. if (rd->keyword_name) free(rd->keyword_name); rd->keyword_name = cursor_keyword_name_find(edit_entry); if (!rd->keyword_name) return; //Set keyword desc. if (rd->keyword_desc) { free(rd->keyword_desc); rd->keyword_desc = NULL; } keyword_data *keyword = cursor_keyword_data_find(edit_entry, rd->keyword_root, rd->keyword_name); if (keyword) rd->keyword_desc = strdup(keyword->desc); if (!rd->keyword_desc) return; //Create event rect which catches mouse down event. rd->event_rect = ref_event_rect_create(edit_obj); //Create reference layout. rd->layout = ref_layout_create(edit_entry, rd->keyword_desc); //Calculate reference layout position. Evas_Coord obj_x, obj_y, obj_w, obj_h; evas_object_geometry_get(edit_obj, &obj_x, &obj_y, &obj_w, &obj_h); Evas_Coord entry_x, entry_y; evas_object_geometry_get(edit_entry, &entry_x, &entry_y, NULL, NULL); Evas_Coord cur_x, cur_y, cur_h; elm_entry_cursor_geometry_get(edit_entry, &cur_x, &cur_y, NULL, &cur_h); char *layout_width = (char *)edje_object_data_get(elm_layout_edje_get(rd->layout), "width"); char *layout_height = (char *)edje_object_data_get(elm_layout_edje_get(rd->layout), "height"); if (!layout_width || !layout_height) return; const Evas_Coord ref_w = ELM_SCALE_SIZE(atoi(layout_width)); const Evas_Coord ref_h = ELM_SCALE_SIZE(atoi(layout_height)); Evas_Coord ref_x = 0; Evas_Coord ref_y = 0; //The center of reference layout is the entry cursor position. if (cur_x < (ref_w / 2)) ref_x = obj_x; else if ((obj_w - (entry_x - obj_x + cur_x)) < (ref_w / 2)) ref_x = obj_x + obj_w - ref_w; else ref_x = entry_x + cur_x - (ref_w / 2); if (((obj_y + obj_h) - (entry_y + cur_y + cur_h)) < ref_h) ref_y = entry_y + cur_y - ref_h; else ref_y = entry_y + cur_y + cur_h; evas_object_move(rd->layout, ref_x, ref_y); } void ref_init(void) { ref_data *rd = calloc(1, sizeof(ref_data)); if (!rd) { EINA_LOG_ERR("Failed to allocate Memory!"); return; } ref_load(rd); g_rd = rd; } void ref_term(void) { ref_data *rd = g_rd; keyword_data_free(rd->keyword_root); if (rd->keyword_name) free(rd->keyword_name); if (rd->keyword_desc) free(rd->keyword_desc); ref_event_rect_delete(); ref_layout_delete(); free(rd); g_rd = NULL; }