enventor/src/lib/redoundo.c

470 lines
13 KiB
C

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <Enventor.h>
#include "enventor_private.h"
#define DEFAULT_QUEUE_SIZE 200
#define INPUT_SPEED 0.8 //how much time need to input one symbol with speed 75sym/min
typedef struct diff_s
{
Eina_Stringshare *text;
unsigned int length;
unsigned int cursor_pos;
Eina_Bool action : 1; //EINA_TRUE: insert, EINA_FALSE, delete
Eina_Bool relative : 1; //If this change relative to prevision or next step
} diff_data;
struct redoundo_s
{
Evas_Object *entry;
Evas_Object *textblock;
Evas_Textblock_Cursor *cursor;
Eina_List *queue;
Eina_List *current_node;
diff_data *last_diff;
unsigned int queue_max; //Maximum queuing data count 0: unlimited
Eina_Bool internal_change : 1; //Entry change by redoundo
struct {
Eina_Bool enable;
Ecore_Timer *timer;
Eina_Bool continues_input;
double input_delay;
} smart;
};
/*****************************************************************************/
/* Internal method implementation */
/*****************************************************************************/
Eina_Bool
_input_timer_cb(void *data)
{
redoundo_data *rd = (redoundo_data *)data;
if (!rd->smart.continues_input) return ECORE_CALLBACK_CANCEL;
rd->smart.continues_input = EINA_FALSE;
ecore_timer_del(rd->smart.timer);
rd->smart.timer = NULL;
return ECORE_CALLBACK_CANCEL;
}
static diff_data *
smart_analyser(redoundo_data *rd, diff_data *diff)
{
if (!rd->smart.enable) return diff;
if (rd->smart.timer)
{
ecore_timer_del(rd->smart.timer);
rd->smart.timer = NULL;
}
if ((!diff) || (diff->length > 1) || (!rd->last_diff)) return diff;
/* Autoindent. Here need edit_data pointer,
* for check status of autoindent feature.
*
* if (edit_auto_indent_get(edit_obj_get))
* {
* if (strstr(diff->text, "<br/>")) diff->relative = EINA_TRUE;
* else diff->relative = EINA_FALSE;
* }
*/
// Analyse speed of text input and words separates
if ((rd->smart.continues_input) && (!diff->relative) &&
(isalpha(diff->text[0])) && (isalpha(rd->last_diff->text[0])))
{
diff_data *tmp = diff;
const char *text;
diff = rd->last_diff;
diff->length += tmp->length;
text = eina_stringshare_printf("%s%s", diff->text, tmp->text);
eina_stringshare_replace(&diff->text, text);
eina_stringshare_del(text);
rd->last_diff = eina_list_data_get(eina_list_prev(rd->current_node));
rd->queue = eina_list_remove_list(rd->queue, rd->current_node);
eina_stringshare_del(tmp->text);
free(tmp);
}
rd->smart.continues_input = EINA_TRUE;
rd->smart.timer = ecore_timer_add(rd->smart.input_delay, _input_timer_cb, rd);
return diff;
}
static void
untracked_diff_free(redoundo_data *rd)
{
if (!rd->last_diff)
{
redoundo_clear(rd);
return;
}
Eina_List *l;
diff_data *diff;
EINA_LIST_REVERSE_FOREACH(rd->queue, l, diff)
{
if (diff == rd->last_diff) break;
eina_stringshare_del(diff->text);
free(diff);
rd->queue = eina_list_remove_list(rd->queue, l);
}
}
static void
entry_changed_user_cb(void *data, Evas_Object *obj EINA_UNUSED,
const char *emission EINA_UNUSED,
const char *source EINA_UNUSED)
{
redoundo_data *rd = data;
Edje_Entry_Change_Info *info = edje_object_signal_callback_extra_data_get();
if (rd->internal_change)
{
rd->internal_change = EINA_FALSE;
return;
}
diff_data *diff = calloc(1, sizeof(diff_data));
if (!diff)
{
EINA_LOG_ERR("Failed to allocate Memory!");
return;
}
if ((rd->queue_max) && (eina_list_count(rd->queue) >= rd->queue_max))
{
diff_data *old = NULL;
old = eina_list_data_get(rd->queue);
eina_stringshare_del(old->text);
free(old);
rd->queue = eina_list_remove_list(rd->queue, rd->queue);
}
if (info->insert)
{
if (info->change.insert.plain_length == 0) goto nochange;
diff->text = eina_stringshare_add(info->change.insert.content);
char *utf8 = evas_textblock_text_markup_to_utf8(NULL, diff->text);
diff->length = strlen(utf8);
diff->cursor_pos = info->change.insert.pos;
diff->action = EINA_TRUE;
free(utf8);
}
else
{
int length = (info->change.del.end - info->change.del.start);
if (length == 0) goto nochange;
diff->text = eina_stringshare_add(info->change.del.content);
if (length > 0) diff->cursor_pos = info->change.del.start;
else diff->cursor_pos = info->change.del.end;
diff->length = abs(length);
diff->action = EINA_FALSE;
}
diff = smart_analyser(rd, diff);
untracked_diff_free(rd);
rd->queue = eina_list_append(rd->queue, diff);
rd->last_diff = diff;
rd->current_node = eina_list_last(rd->queue);
return;
nochange:
free(diff);
}
/*****************************************************************************/
/* Externally accessible calls */
/*****************************************************************************/
int
redoundo_undo(redoundo_data *rd, Eina_Bool *changed)
{
if (changed) *changed = EINA_FALSE;
if (!rd->last_diff) return 0;
rd->internal_change = EINA_TRUE;
int lines;
if (rd->last_diff->action)
{
//Last change was adding new symbol(s), that mean here need delete it
//Undo one character
if (rd->last_diff->length == 1)
{
evas_textblock_cursor_pos_set(rd->cursor,
rd->last_diff->cursor_pos);
evas_textblock_cursor_char_delete(rd->cursor);
}
//Undo String
else
{
Evas_Textblock_Cursor *cursor =
evas_object_textblock_cursor_new( rd->textblock);
evas_textblock_cursor_pos_set(rd->cursor,
rd->last_diff->cursor_pos);
evas_textblock_cursor_pos_set(cursor,
(rd->last_diff->cursor_pos +
rd->last_diff->length));
evas_textblock_cursor_range_delete(rd->cursor, cursor);
evas_textblock_cursor_free(cursor);
}
lines = -parser_line_cnt_get(NULL, rd->last_diff->text);
elm_entry_cursor_pos_set(rd->entry, rd->last_diff->cursor_pos);
}
else
{
evas_textblock_cursor_pos_set(rd->cursor,
rd->last_diff->cursor_pos);
evas_object_textblock_text_markup_prepend(rd->cursor,
rd->last_diff->text);
lines = parser_line_cnt_get(NULL, rd->last_diff->text);
elm_entry_cursor_pos_set(rd->entry,
(rd->last_diff->cursor_pos + rd->last_diff->length));
}
rd->internal_change = EINA_FALSE;
rd->current_node = eina_list_prev(rd->current_node);
rd->last_diff = eina_list_data_get(rd->current_node);
if (rd->last_diff && rd->last_diff->relative)
lines += redoundo_undo(rd, NULL);
if (changed)
{
elm_entry_calc_force(rd->entry);
*changed = EINA_TRUE;
elm_entry_select_none(rd->entry);
}
return lines;
}
int
redoundo_redo(redoundo_data *rd, Eina_Bool *changed)
{
if (changed) *changed = EINA_FALSE;
if (!rd->queue) return 0;
Eina_List *next;
diff_data *diff;
int lines;
next = eina_list_next(rd->current_node);
diff = eina_list_data_get(next);
if ((!next) && (!rd->last_diff))
{
next = rd->queue;
diff = eina_list_data_get(next);
}
if (!next || !diff)
{
rd->internal_change = EINA_FALSE;
return 0;
}
rd->internal_change = EINA_TRUE;
//Insert
if (diff->action)
{
evas_textblock_cursor_pos_set(rd->cursor, diff->cursor_pos);
evas_object_textblock_text_markup_prepend(rd->cursor, diff->text);
lines = parser_line_cnt_get(NULL, diff->text);
elm_entry_cursor_pos_set(rd->entry, (diff->cursor_pos + diff->length));
}
//Remove
else
{
//One Character
if (diff->length == 1)
{
evas_textblock_cursor_pos_set(rd->cursor, diff->cursor_pos);
evas_textblock_cursor_char_delete(rd->cursor);
}
//String
else
{
Evas_Textblock_Cursor *cursor =
evas_object_textblock_cursor_new(rd->textblock);
evas_textblock_cursor_pos_set(rd->cursor, diff->cursor_pos);
evas_textblock_cursor_pos_set(cursor,
(diff->cursor_pos + diff->length));
evas_textblock_cursor_range_delete(rd->cursor, cursor);
evas_textblock_cursor_free(cursor);
}
lines = -parser_line_cnt_get(NULL, diff->text);
elm_entry_cursor_pos_set(rd->entry, diff->cursor_pos);
}
rd->internal_change = EINA_FALSE;
rd->last_diff = diff;
rd->current_node = next;
if (diff->relative)
lines += redoundo_redo(rd, NULL);
if (changed)
{
elm_entry_calc_force(rd->entry);
*changed = EINA_TRUE;
elm_entry_select_none(rd->entry);
}
return lines;
}
void
redoundo_text_push(redoundo_data *rd, const char *text, int pos, int length,
Eina_Bool insert)
{
if (!text) return;
diff_data *diff = calloc(1, sizeof(diff_data));
if (!diff)
{
EINA_LOG_ERR("Failed to allocate Memory!");
return;
}
if (length) diff->length = length;
else
{
char *utf8 = evas_textblock_text_markup_to_utf8(NULL, text);
diff->length = strlen(utf8);
free(utf8);
if (!diff->length)
{
free(diff);
return;
}
}
diff->text = eina_stringshare_add(text);
diff->cursor_pos = pos;
diff->action = insert;
diff->relative = EINA_FALSE;
untracked_diff_free(rd);
rd->queue = eina_list_append(rd->queue, diff);
rd->last_diff = diff;
rd->current_node = eina_list_last(rd->queue);
}
redoundo_data *
redoundo_init(Evas_Object *entry)
{
if (!entry) return NULL;
redoundo_data *rd = calloc(1, sizeof(redoundo_data));
if (!rd)
{
EINA_LOG_ERR("Failed to allocate Memory!");
return NULL;
}
rd->entry = entry;
rd->textblock = elm_entry_textblock_get(entry);
rd->cursor = evas_object_textblock_cursor_new(rd->textblock);
rd->queue_max = DEFAULT_QUEUE_SIZE;
rd->smart.enable = EINA_FALSE;
rd->smart.input_delay = INPUT_SPEED;
//FIXME: Why signal callback? not smart callback?
elm_object_signal_callback_add(entry, "entry,changed,user", "*",
entry_changed_user_cb, rd);
return rd;
}
void
redoundo_clear(redoundo_data *rd)
{
diff_data *data;
EINA_LIST_FREE(rd->queue, data)
{
eina_stringshare_del(data->text);
free(data);
}
rd->internal_change = EINA_FALSE;
ecore_timer_del(rd->smart.timer);
}
void
redoundo_term(redoundo_data *rd)
{
redoundo_clear(rd);
evas_textblock_cursor_free(rd->cursor);
free(rd);
}
void
redoundo_entry_region_push(redoundo_data *rd, int cursor_pos, int cursor_pos2)
{
elm_entry_select_region_set(rd->entry, cursor_pos, cursor_pos2);
redoundo_text_push(rd, elm_entry_selection_get(rd->entry), cursor_pos,
(cursor_pos2 - cursor_pos), EINA_TRUE);
elm_entry_select_none(rd->entry);
}
void
redoundo_text_relative_push(redoundo_data *rd, const char *text)
{
if (!text) return;
diff_data *diff = malloc(sizeof(diff_data));
if (!diff)
{
EINA_LOG_ERR("Failed to allocate Memory!");
return;
}
diff->text = eina_stringshare_add(text);
char *utf8 = evas_textblock_text_markup_to_utf8(NULL, diff->text);
diff->length = strlen(utf8);
diff->cursor_pos = elm_entry_cursor_pos_get(rd->entry);
diff->action = EINA_TRUE;
diff->relative = EINA_TRUE;
untracked_diff_free(rd);
rd->queue = eina_list_append(rd->queue, diff);
rd->last_diff = diff;
rd->current_node = eina_list_last(rd->queue);
free(utf8);
}
void
redoundo_n_diff_cancel(redoundo_data *rd, unsigned int n)
{
if (!rd || !rd->queue || !n) return;
unsigned int i;
for (i = 0; i < n && rd->current_node; i++)
rd->current_node = eina_list_prev(rd->current_node);
rd->last_diff = (diff_data *)eina_list_data_get(rd->current_node);
untracked_diff_free(rd);
}
void
redoundo_smart_set(redoundo_data *rd, Eina_Bool status)
{
if (!rd) return;
rd->smart.enable = status;
}