enventor/src/lib/auto_comp.c

589 lines
17 KiB
C

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#define ENVENTOR_BETA_API_SUPPORT 1
#include <Enventor.h>
#include "enventor_private.h"
#include "auto_comp_code.h"
#define QUEUE_SIZE 20
#define COMPSET_PAIR_MINIMUM 1
typedef struct comp_set_s
{
Eina_Stringshare *key;
char **txt;
int cursor_offset;
int line_back;
int line_cnt;
} comp_set;
typedef struct autocomp_s
{
char queue[QUEUE_SIZE];
int queue_pos;
comp_set compset[COMPSET_CNT];
edit_data *ed;
Evas_Object *anchor;
Evas_Object *list;
Eina_List *compset_list;
Ecore_Thread *init_thread;
Eina_Bool anchor_visible : 1;
Eina_Bool initialized : 1;
Eina_Bool enabled : 1;
} autocomp_data;
static autocomp_data *g_ad = NULL;
#define COMPDATA_SET(ad, key, txt, cursor_offset, line_back) \
compdata_set(ad, idx++, key, (char **)(&txt), cursor_offset, line_back, txt##_LINE_CNT)
/*****************************************************************************/
/* Internal method implementation */
/*****************************************************************************/
static void
compdata_set(autocomp_data *ad, int idx, char *key, char **txt, int cursor_offset, int line_back, int line_cnt)
{
ad->compset[idx].key = eina_stringshare_add(key);
ad->compset[idx].txt = txt;
ad->compset[idx].cursor_offset = cursor_offset;
ad->compset[idx].line_back = line_back;
ad->compset[idx].line_cnt = line_cnt;
}
static void
init_thread_cb(void *data, Ecore_Thread *thread EINA_UNUSED)
{
autocomp_data *ad = data;
int idx = 0;
COMPDATA_SET(ad, "collections",AUTOCOMP_COLLECTIONS, 2, 1);
COMPDATA_SET(ad, "image",AUTOCOMP_IMAGE, 7, 0);
COMPDATA_SET(ad, "images",AUTOCOMP_IMAGES, 2, 1);
COMPDATA_SET(ad, "group",AUTOCOMP_GROUP, 4, 1);
COMPDATA_SET(ad, "type",AUTOCOMP_TYPE, 0, 0);
COMPDATA_SET(ad, "part",AUTOCOMP_PART, 4, 1);
COMPDATA_SET(ad, "parts",AUTOCOMP_PARTS, 2, 1);
COMPDATA_SET(ad, "description",AUTOCOMP_DESCRIPTION, 8, 1);
COMPDATA_SET(ad, "inherit",AUTOCOMP_INHERIT, 6, 0);
COMPDATA_SET(ad, "program",AUTOCOMP_PROGRAM, 4, 1);
COMPDATA_SET(ad, "programs",AUTOCOMP_PROGRAMS, 2, 1);
COMPDATA_SET(ad, "signal",AUTOCOMP_SIGNAL, 2, 0);
COMPDATA_SET(ad, "source",AUTOCOMP_SOURCE, 2, 0);
COMPDATA_SET(ad, "target",AUTOCOMP_TARGET, 2, 0);
COMPDATA_SET(ad, "scale",AUTOCOMP_SCALE, 1, 0);
COMPDATA_SET(ad, "rel1",AUTOCOMP_REL1, 2, 1);
COMPDATA_SET(ad, "rel2",AUTOCOMP_REL2, 2, 1);
COMPDATA_SET(ad, "relative",AUTOCOMP_RELATIVE, 1, 0);
COMPDATA_SET(ad, "offset", AUTOCOMP_OFFSET, 1, 0);
COMPDATA_SET(ad, "color", AUTOCOMP_COLOR, 1, 0);
COMPDATA_SET(ad, "color2", AUTOCOMP_COLOR2, 1, 0);
COMPDATA_SET(ad, "color3", AUTOCOMP_COLOR3, 1, 0);
COMPDATA_SET(ad, "aspect", AUTOCOMP_ASPECT, 1, 0);
COMPDATA_SET(ad, "aspect_preference", AUTOCOMP_ASPECT_PREFERENCE, 1, 0);
COMPDATA_SET(ad, "normal", AUTOCOMP_NORMAL, 2, 0);
COMPDATA_SET(ad, "effect", AUTOCOMP_EFFECT, 0, 0);
COMPDATA_SET(ad, "text", AUTOCOMP_TEXT, 2, 1);
COMPDATA_SET(ad, "font", AUTOCOMP_FONT, 2, 0);
COMPDATA_SET(ad, "align", AUTOCOMP_ALIGN, 1, 0);
COMPDATA_SET(ad, "size", AUTOCOMP_SIZE, 1, 0);
COMPDATA_SET(ad, "action", AUTOCOMP_ACTION, 6, 0);
COMPDATA_SET(ad, "transition", AUTOCOMP_TRANSITION, 1, 0);
COMPDATA_SET(ad, "after", AUTOCOMP_AFTER, 2, 0);
COMPDATA_SET(ad, "styles", AUTOCOMP_STYLES, 2, 1);
COMPDATA_SET(ad, "style", AUTOCOMP_STYLE, 4, 1);
COMPDATA_SET(ad, "base", AUTOCOMP_BASE, 2, 0);
COMPDATA_SET(ad, "sounds", AUTOCOMP_SOUNDS, 2, 1);
COMPDATA_SET(ad, "sample", AUTOCOMP_SAMPLE, 13, 1);
COMPDATA_SET(ad, "map", AUTOCOMP_MAP, 2, 1);
COMPDATA_SET(ad, "on", AUTOCOMP_ON, 1, 0);
COMPDATA_SET(ad, "visible", AUTOCOMP_VISIBLE, 1, 0);
COMPDATA_SET(ad, "perspective_on", AUTOCOMP_PERSPECTIVE_ON, 1, 0);
COMPDATA_SET(ad, "perspective", AUTOCOMP_PERSPECTIVE, 2, 0);
COMPDATA_SET(ad, "backface_cull", AUTOCOMP_BACKFACE_CULL, 1, 0);
COMPDATA_SET(ad, "rotation", AUTOCOMP_ROTATION, 2, 1);
COMPDATA_SET(ad, "min", AUTOCOMP_MIN, 1, 0);
COMPDATA_SET(ad, "max", AUTOCOMP_MAX, 1, 0);
COMPDATA_SET(ad, "fixed", AUTOCOMP_FIXED, 1, 0);
COMPDATA_SET(ad, "clip_to", AUTOCOMP_CLIP_TO, 2, 0);
COMPDATA_SET(ad, "tween", AUTOCOMP_TWEEN, 2, 0);
}
static void
init_thread_end_cb(void *data, Ecore_Thread *thread EINA_UNUSED)
{
autocomp_data *ad = data;
ad->initialized = EINA_TRUE;
ad->init_thread = NULL;
}
static void
init_thread_cancel_cb(void *data, Ecore_Thread *thread EINA_UNUSED)
{
autocomp_data *ad = data;
ad->init_thread = NULL;
}
static void
entry_anchor_off(autocomp_data *ad)
{
if (ad->anchor_visible)
{
elm_object_tooltip_hide(ad->anchor);
/* Reset content_cb to have guarantee the callback call. If anchor is
changed faster than tooltip hide, the callback won't be called
since tooltip regards the content callback is same with before. */
elm_object_tooltip_content_cb_set(ad->anchor, NULL, NULL, NULL);
}
ad->anchor_visible = EINA_FALSE;
ad->compset_list = eina_list_free(ad->compset_list);
}
static void
anchor_unfocused_cb(void *data, Evas_Object *obj EINA_UNUSED,
void *event_info EINA_UNUSED)
{
autocomp_data *ad = data;
entry_anchor_off(ad);
}
static void
queue_reset(autocomp_data *ad)
{
if (ad->queue_pos == -1) return;
ad->queue_pos = -1;
memset(ad->queue, 0x0, sizeof(ad->queue));
entry_anchor_off(ad);
}
static void
compset_list_update(autocomp_data *ad)
{
if (!ad->enabled || !ad->initialized) return;
if (ad->queue_pos < COMPSET_PAIR_MINIMUM) return;
int i;
ad->compset_list = eina_list_free(ad->compset_list);
for (i = 0; i < COMPSET_CNT; i++)
{
if (ad->queue[0] == ad->compset[i].key[0])
{
if (!strncmp(ad->queue, ad->compset[i].key, ad->queue_pos + 1))
{
ad->compset_list = eina_list_append(ad->compset_list,
&ad->compset[i]);
}
}
}
}
static void
push_char(autocomp_data *ad, char c)
{
ad->queue_pos++;
if (ad->queue_pos == QUEUE_SIZE)
{
memset(ad->queue, 0x0, sizeof(ad->queue));
ad->queue_pos = 0;
}
ad->queue[ad->queue_pos] = c;
compset_list_update(ad);
}
static void
pop_char(autocomp_data *ad, int cnt)
{
if (ad->queue_pos == -1) return;
int i;
for (i = 0; i < cnt; i++)
{
ad->queue[ad->queue_pos] = 0x0;
ad->queue_pos--;
if (ad->queue_pos < 0) break;
}
if (ad->queue_pos == -1) return;
compset_list_update(ad);
}
static void
insert_completed_text(autocomp_data *ad)
{
if (!ad->compset_list) return;
Elm_Object_Item *it = elm_list_selected_item_get(ad->list);
comp_set *compset = elm_object_item_data_get(it);
char **txt = compset->txt;
Evas_Object *entry = edit_entry_get(ad->ed);
int space = edit_cur_indent_depth_get(ad->ed);
int cursor_pos = elm_entry_cursor_pos_get(entry);
//Insert the first line.
elm_entry_entry_insert(entry, txt[0]+ (ad->queue_pos + 1));
//Insert last lines
if (compset->line_cnt > 1)
{
//Alloc Empty spaces
char *p = alloca(space + 1);
memset(p, ' ', space);
p[space] = '\0';
int i;
for (i = 1; i < (compset->line_cnt -1); i++)
{
elm_entry_entry_insert(entry, p);
elm_entry_entry_insert(entry, txt[i]);
}
elm_entry_entry_insert(entry, p);
elm_entry_entry_insert(entry, txt[i]);
}
int cursor_pos2 = elm_entry_cursor_pos_get(entry);
redoundo_data *rd = evas_object_data_get(entry, "redoundo");
redoundo_entry_region_push(rd, cursor_pos, cursor_pos2);
cursor_pos2 -= (compset->cursor_offset + (compset->line_back * space));
elm_entry_cursor_pos_set(entry, cursor_pos2);
edit_line_increase(ad->ed, (compset->line_cnt - 1));
}
static void
list_del_cb(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED,
void *event_info EINA_UNUSED)
{
autocomp_data *ad = data;
ad->list = NULL;
}
static Evas_Object *
entry_tooltip_content_cb(void *data, Evas_Object *obj EINA_UNUSED,
Evas_Object *tt EINA_UNUSED)
{
autocomp_data *ad = data;
ad->list = elm_list_add(obj);
elm_object_focus_allow_set(ad->list, EINA_FALSE);
elm_list_mode_set(ad->list, ELM_LIST_EXPAND);
Eina_List *l;
comp_set *compset;
EINA_LIST_FOREACH(ad->compset_list, l, compset)
elm_list_item_append(ad->list, compset->key, NULL, NULL, NULL, compset);
Elm_Object_Item *it = elm_list_first_item_get(ad->list);
elm_list_item_selected_set(it, EINA_TRUE);
evas_object_smart_callback_add(ad->list, "unfocused", anchor_unfocused_cb,
ad);
evas_object_event_callback_add(ad->list, EVAS_CALLBACK_DEL, list_del_cb, ad);
elm_list_go(ad->list);
evas_object_show(ad->list);
return ad->list;
}
static void
anchor_list_update(autocomp_data *ad)
{
comp_set *compset;
Eina_List *items = (Eina_List *) elm_list_items_get(ad->list);
Eina_List *l, *ll, *l2;
Elm_Object_Item *it;
Eina_Bool found;
const char *it_name;
//Remove the non-candidates
EINA_LIST_FOREACH_SAFE(items, l, ll, it)
{
found = EINA_FALSE;
it_name = elm_object_item_text_get(it);
EINA_LIST_FOREACH(ad->compset_list, l2, compset)
{
if (compset->key == it_name)
{
found = EINA_TRUE;
break;
}
}
if (!found) elm_object_item_del(it);
}
items = (Eina_List *) elm_list_items_get(ad->list);
//Append new candidates
EINA_LIST_FOREACH(ad->compset_list, l, compset)
{
found = EINA_FALSE;
EINA_LIST_FOREACH(items, l2, it)
{
it_name = elm_object_item_text_get(it);
if (it_name != compset->key) continue;
found = EINA_TRUE;
break;
}
if (!found) elm_list_item_append(ad->list, compset->key, NULL,
NULL, NULL, compset);
}
it = elm_list_first_item_get(ad->list);
elm_list_item_selected_set(it, EINA_TRUE);
elm_list_go(ad->list);
}
static void
candidate_list_show(autocomp_data *ad)
{
if (!ad->compset_list)
{
entry_anchor_off(ad);
return;
}
Evas_Object *entry = edit_entry_get(ad->ed);
//Update anchor position
Evas_Coord x, y, h;
Evas_Coord cx, cy, cw, ch;
evas_object_geometry_get(entry, &x, &y, NULL, NULL);
elm_entry_cursor_geometry_get(entry, &cx, &cy, &cw, &ch);
evas_object_move(ad->anchor, cx + x, cy + y);
evas_object_resize(ad->anchor, cw, ch);
//Show the tooltip
if (!ad->anchor_visible)
{
//Decide the Tooltip direction
Elm_Tooltip_Orient tooltip_orient = ELM_TOOLTIP_ORIENT_BOTTOM;
evas_object_geometry_get(edit_obj_get(ad->ed), NULL, NULL, NULL, &h);
if ((cy + y) > (h / 2)) tooltip_orient = ELM_TOOLTIP_ORIENT_TOP;
//Tooltip set
elm_object_tooltip_content_cb_set(ad->anchor,
entry_tooltip_content_cb, ad, NULL);
elm_object_tooltip_orient_set(ad->anchor, tooltip_orient);
elm_object_tooltip_show(ad->anchor);
ad->anchor_visible = EINA_TRUE;
}
//Already tooltip is visible, just update the list item
else anchor_list_update(ad);
}
static void
entry_changed_cb(void *data, Evas_Object *obj EINA_UNUSED, void *event_info)
{
Elm_Entry_Change_Info *info = event_info;
autocomp_data *ad = data;
if (info->insert)
{
if ((strlen(info->change.insert.content) > 1) ||
(info->change.insert.content[0] == ' ') ||
(info->change.insert.content[0] == '.'))
{
entry_anchor_off(ad);
queue_reset(ad);
}
else
{
push_char(ad, info->change.insert.content[0]);
candidate_list_show(ad);
}
}
else
{
if (info->change.del.content[0] != ' ')
{
entry_anchor_off(ad);
int cnt = abs(info->change.del.end - info->change.del.start);
pop_char(ad, cnt);
}
}
}
static void
entry_cursor_changed_manual_cb(void *data, Evas_Object *obj EINA_UNUSED,
void *event_info EINA_UNUSED)
{
autocomp_data *ad = data;
entry_anchor_off(ad);
}
static void
entry_cursor_changed_cb(void *data, Evas_Object *obj,
void *event_info EINA_UNUSED)
{
autocomp_data *ad = data;
//Update anchor position
Evas_Coord x, y, cx, cy, cw, ch;
evas_object_geometry_get(obj, &x, &y, NULL, NULL);
elm_entry_cursor_geometry_get(obj, &cx, &cy, &cw, &ch);
evas_object_move(ad->anchor, cx + x, cy + y);
evas_object_resize(ad->anchor, cw, ch);
}
static void
entry_press_cb(void *data, Evas_Object *obj EINA_UNUSED,
void *event_info EINA_UNUSED)
{
autocomp_data *ad = data;
entry_anchor_off(ad);
}
static void
entry_move_cb(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED,
void *event_info EINA_UNUSED)
{
autocomp_data *ad = data;
entry_anchor_off(ad);
}
static void
list_item_move(autocomp_data *ad, Eina_Bool up)
{
Evas_Object *entry = edit_entry_get(ad->ed);
evas_object_smart_callback_del(entry, "unfocused", anchor_unfocused_cb);
Elm_Object_Item *it = elm_list_selected_item_get(ad->list);
if (up) it = elm_list_item_prev(it);
else it = elm_list_item_next(it);
if (it) elm_list_item_selected_set(it, EINA_TRUE);
evas_object_smart_callback_add(entry, "unfocused", anchor_unfocused_cb, ad);
}
/*****************************************************************************/
/* Externally accessible calls */
/*****************************************************************************/
void
autocomp_target_set(edit_data *ed)
{
autocomp_data *ad = g_ad;
Evas_Object *entry;
queue_reset(ad);
//Unset previous target
if (ad->ed)
{
entry = edit_entry_get(ad->ed);
evas_object_smart_callback_del(entry, "changed,user", entry_changed_cb);
evas_object_smart_callback_del(entry, "cursor,changed",
entry_cursor_changed_cb);
evas_object_smart_callback_del(entry, "cursor,changed,manual",
entry_cursor_changed_manual_cb);
evas_object_smart_callback_del(entry, "unfocused", anchor_unfocused_cb);
evas_object_smart_callback_del(entry, "press", entry_press_cb);
evas_object_event_callback_del(entry, EVAS_CALLBACK_MOVE,
entry_move_cb);
evas_object_del(ad->anchor);
ad->anchor = NULL;
ad->ed = NULL;
}
if (!ed) return;
entry = edit_entry_get(ed);
evas_object_smart_callback_add(entry, "changed,user", entry_changed_cb, ad);
evas_object_smart_callback_add(entry, "cursor,changed,manual",
entry_cursor_changed_manual_cb, ad);
evas_object_smart_callback_add(entry, "cursor,changed",
entry_cursor_changed_cb, ad);
evas_object_smart_callback_add(entry, "unfocused", anchor_unfocused_cb, ad);
evas_object_smart_callback_add(entry, "press", entry_press_cb, ad);
evas_object_event_callback_add(entry, EVAS_CALLBACK_MOVE,
entry_move_cb, ad);
ad->anchor = elm_button_add(edit_obj_get(ed));
ad->ed = ed;
}
Eina_Bool
autocomp_event_dispatch(const char *key)
{
autocomp_data *ad = g_ad;
if (!ad || !ad->anchor_visible) return EINA_FALSE;
//Cancel the auto complete.
if (!strcmp(key, "BackSpace"))
{
queue_reset(ad);
entry_anchor_off(ad);
return EINA_TRUE;
}
if (!strcmp(key, "Return"))
{
insert_completed_text(ad);
queue_reset(ad);
edit_syntax_color_partial_apply(ad->ed, -1);
return EINA_TRUE;
}
if (!strcmp(key, "Up"))
{
list_item_move(ad, EINA_TRUE);
return EINA_TRUE;
}
if (!strcmp(key, "Down"))
{
list_item_move(ad, EINA_FALSE);
return EINA_TRUE;
}
return EINA_FALSE;
}
void
autocomp_init(void)
{
autocomp_data *ad = calloc(1, sizeof(autocomp_data));
if (!ad)
{
EINA_LOG_ERR("Failed to allocate Memory!");
return;
}
ad->init_thread = ecore_thread_run(init_thread_cb, init_thread_end_cb,
init_thread_cancel_cb, ad);
ad->queue_pos = -1;
g_ad = ad;
}
void
autocomp_term(void)
{
autocomp_data *ad = g_ad;
evas_object_del(ad->anchor);
ecore_thread_cancel(ad->init_thread);
int i;
for (i = 0; i < COMPSET_CNT; i++)
eina_stringshare_del(ad->compset[i].key);
free(ad);
g_ad = NULL;
}
void
autocomp_enabled_set(Eina_Bool enabled)
{
autocomp_data *ad = g_ad;
enabled = !!enabled;
ad->enabled = enabled;
}
Eina_Bool
autocomp_enabled_get(void)
{
autocomp_data *ad = g_ad;
return ad->enabled;
}