[editor] begin re-adding the undo feature.

This is provided completely by elm_code so can be reused in other editors.
For now this is just text/newline insertion...
Still have to add undo for deletion and selection
This commit is contained in:
Andy Williams 2016-02-21 18:11:33 +00:00
parent 89cbb9daa0
commit acd7b54473
11 changed files with 241 additions and 89 deletions

View File

@ -1,6 +1,7 @@
2016-02-21 ajwillia.ms (Andy Williams)
* Fix crash when deleting a selection ending in newline
* Begin the re-add of the undo feature (just text insertion now)
2016-02-11 ajwillia.ms (Andy Williams)

1
NEWS
View File

@ -8,6 +8,7 @@ Features:
* Update to EFL 1.17 release for better toolbar handling
* Add file filtering for hunting down files in the list
* Double click to select word, triple click to select line
* Return of the undo feature!
Bug fixes:

View File

@ -987,11 +987,41 @@ _elm_code_widget_delete_selection(Elm_Code_Widget *widget)
return EINA_TRUE;
}
static Elm_Code_Widget_Change_Info *
_elm_code_widget_change_create_insert(unsigned int start_col, unsigned int start_line,
unsigned int end_col, unsigned int end_line,
const char *text, unsigned int length)
{
Elm_Code_Widget_Change_Info *info;
info = calloc(1, sizeof(*info));
info->insert = EINA_TRUE;
info->start_col = start_col;
info->start_line = start_line;
info->end_col = end_col;
info->end_line = end_line;
info->content = malloc((length + 1) * sizeof(char));
strncpy((char *)info->content, text, length + 1);
info->length = length;
return info;
}
static void
_elm_code_widget_change_free(Elm_Code_Widget_Change_Info *info)
{
free((char *)info->content);
free(info);
}
void
_elm_code_widget_text_at_cursor_insert(Elm_Code_Widget *widget, const char *text, int length)
{
Elm_Code *code;
Elm_Code_Line *line;
Elm_Code_Widget_Change_Info *change;
unsigned int row, col, position, col_width;
_elm_code_widget_delete_selection(widget);
@ -1013,15 +1043,19 @@ _elm_code_widget_text_at_cursor_insert(Elm_Code_Widget *widget, const char *text
eo_do(widget,
elm_obj_code_widget_cursor_position_set(col + col_width, row),
// TODO construct and pass a change object
eo_event_callback_call(ELM_CODE_WIDGET_EVENT_CHANGED_USER, NULL));
change = _elm_code_widget_change_create_insert(col, row, col + col_width - 1, row, text, length);
_elm_code_widget_undo_change_add(widget, change);
_elm_code_widget_change_free(change);
}
static void
_elm_code_widget_tab_at_cursor_insert(Elm_Code_Widget *widget)
{
Elm_Code_Widget_Data *pd;
unsigned int col, row;
Elm_Code_Widget_Change_Info *change;
unsigned int col, row, rem;
pd = eo_data_scope_get(widget, ELM_CODE_WIDGET_CLASS);
if (!pd->tab_inserts_spaces)
@ -1032,21 +1066,26 @@ _elm_code_widget_tab_at_cursor_insert(Elm_Code_Widget *widget)
eo_do(widget,
elm_obj_code_widget_cursor_position_get(&col, &row));
col = (col - 1) % pd->tabstop;
rem = (col - 1) % pd->tabstop;
while (col < pd->tabstop)
while (rem < pd->tabstop)
{
_elm_code_widget_text_at_cursor_insert(widget, " ", 1);
col++;
rem++;
}
change = _elm_code_widget_change_create_insert(col, row, (col - 1) % pd ->tabstop, row, "\t", 1);
_elm_code_widget_undo_change_add(widget, change);
_elm_code_widget_change_free(change);
}
static void
void
_elm_code_widget_newline(Elm_Code_Widget *widget)
{
Elm_Code *code;
Elm_Code_Line *line;
unsigned int row, col, position, oldlen, leading;
Elm_Code_Widget_Change_Info *change;
unsigned int row, col, position, oldlen, leading, width, indent;
char *oldtext;
_elm_code_widget_delete_selection(widget);
@ -1065,6 +1104,7 @@ _elm_code_widget_newline(Elm_Code_Widget *widget)
position = elm_code_widget_line_text_position_for_column_get(widget, line, col);
elm_code_line_split_at(line, position);
width = elm_code_widget_line_text_column_width_get(widget, line);
line = elm_code_file_line_get(code->file, row + 1);
leading = elm_code_text_leading_whitespace_length(oldtext, oldlen);
@ -1072,11 +1112,14 @@ _elm_code_widget_newline(Elm_Code_Widget *widget)
elm_code_line_text_insert(line, 0, oldtext, leading);
free(oldtext);
indent = elm_obj_code_widget_line_text_column_width_to_position(line, leading);
eo_do(widget,
elm_obj_code_widget_cursor_position_set(
elm_obj_code_widget_line_text_column_width_to_position(line, leading), row + 1),
// TODO construct and pass a change object
elm_obj_code_widget_cursor_position_set(indent, row + 1),
eo_event_callback_call(ELM_CODE_WIDGET_EVENT_CHANGED_USER, NULL));
change = _elm_code_widget_change_create_insert(width + 1, row, indent - 1, row + 1, "\n", 1);
_elm_code_widget_undo_change_add(widget, change);
_elm_code_widget_change_free(change);
}
static void
@ -1198,9 +1241,11 @@ _elm_code_widget_control_key_down_cb(Elm_Code_Widget *widget, const char *key)
elm_code_widget_selection_paste(widget);
else if (!strcmp("x", key))
elm_code_widget_selection_cut(widget);
else if (!strcmp("z", key))
elm_code_widget_undo(widget);
eo_do(widget,
// TODO construct and pass a change object
// TODO construct and pass a change object for cut and paste
eo_event_callback_call(ELM_CODE_WIDGET_EVENT_CHANGED_USER, NULL));
}
@ -1638,4 +1683,5 @@ _elm_code_widget_evas_object_smart_add(Eo *obj, Elm_Code_Widget_Data *pd)
}
#include "elm_code_widget_text.c"
#include "elm_code_widget_undo.c"
#include "elm_code_widget.eo.c"

View File

@ -202,6 +202,8 @@ class Elm.Code_Widget (Elm.Layout, Elm.Interface_Atspi_Text)
}
return: uint;
}
undo {
}
}
implements {
class.constructor;

View File

@ -27,14 +27,33 @@ typedef struct
Elm_Code_Widget_Selection_Data *selection;
Evas_Object *tooltip;
/* Undo stack */
Eina_List *undo_stack;
Eina_List *undo_stack_ptr;
} Elm_Code_Widget_Data;
typedef struct
{
const char *content;
unsigned int length;
unsigned int start_line, start_col, end_line, end_col;
Eina_Bool insert : 1; /**< True if the change is an insertion */
} Elm_Code_Widget_Change_Info;
/* Private widget methods */
void _elm_code_widget_text_at_cursor_insert(Elm_Code_Widget *widget, const char *text, int length);
void _elm_code_widget_newline(Elm_Code_Widget *widget);
void _elm_code_widget_tooltip_text_set(Evas_Object *widget, const char *text);
void _elm_code_widget_tooltip_add(Evas_Object *widget);
EAPI Elm_Code_Widget_Selection_Data *elm_code_widget_selection_normalized_get(Evas_Object *widget);
void _elm_code_widget_undo_change_add(Evas_Object *widget, Elm_Code_Widget_Change_Info *info);
#endif

View File

@ -0,0 +1,56 @@
#ifdef HAVE_CONFIG
# include "config.h"
#endif
#include "Elm_Code.h"
#include "elm_code_widget_private.h"
void
_elm_code_widget_undo_change_add(Evas_Object *widget,
Elm_Code_Widget_Change_Info *info)
{
Elm_Code_Widget_Data *pd;
Elm_Code_Widget_Change_Info *info_copy;
info_copy = calloc(1, sizeof(*info));
memcpy(info_copy, info, sizeof(*info));
pd = eo_data_scope_get(widget, ELM_CODE_WIDGET_CLASS);
pd->undo_stack_ptr = eina_list_prepend(pd->undo_stack_ptr, info_copy);
pd->undo_stack = pd->undo_stack_ptr;
}
static void
_elm_code_widget_undo_change(Evas_Object *widget,
Elm_Code_Widget_Change_Info *info)
{
if (info->insert)
{
elm_code_widget_selection_start(widget, info->start_line, info->start_col);
elm_code_widget_selection_end(widget, info->end_line, info->end_col);
elm_code_widget_selection_delete(widget);
}
else
{
}
}
static void
_elm_code_widget_undo(Eo *obj EINA_UNUSED, Elm_Code_Widget_Data *pd)
{
Elm_Code_Widget_Change_Info *info;
if (!pd->undo_stack_ptr)
return;
info = eina_list_data_get(pd->undo_stack_ptr);
_elm_code_widget_undo_change(obj, info);
if (eina_list_next(pd->undo_stack_ptr))
pd->undo_stack_ptr = eina_list_next(pd->undo_stack_ptr);
else
pd->undo_stack_ptr = NULL;
}

View File

@ -15,6 +15,7 @@ elm_code_test_text.c \
widget/elm_code_test_widget.c \
widget/elm_code_test_widget_text.c \
widget/elm_code_test_widget_selection.c \
widget/elm_code_test_widget_undo.c \
elm_code_suite.c
elm_code_suite_CPPFLAGS = \

View File

@ -21,6 +21,7 @@ static const struct {
{ "widget", elm_code_test_widget },
{ "widget_text", elm_code_test_widget_text },
{ "widget_selection", elm_code_test_widget_selection },
{ "widget_undo", elm_code_test_widget_undo },
};
START_TEST(elm_code_initialization)

View File

@ -26,5 +26,6 @@ void elm_code_test_text(TCase *tc);
void elm_code_test_widget(TCase *tc);
void elm_code_test_widget_text(TCase *tc);
void elm_code_test_widget_selection(TCase *tc);
void elm_code_test_widget_undo(TCase *tc);
#endif /* _EDLM_CODE_SUITE_H */

View File

@ -0,0 +1,101 @@
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "elm_code_suite.h"
#include "elm_code_widget_private.h"
START_TEST (elm_code_test_widget_undo_text_insert)
{
Elm_Code *code;
Elm_Code_File *file;
Elm_Code_Line *line;
Elm_Code_Widget *widget;
Evas_Object *win;
unsigned int length;
const char *content;
elm_init(1, NULL);
code = elm_code_create();
file = elm_code_file_new(code);
elm_code_file_line_append(file, "test", 4, NULL);
win = elm_win_add(NULL, "entry", ELM_WIN_BASIC);
widget = elm_code_widget_add(win, code);
_elm_code_widget_text_at_cursor_insert(widget, "a", 1);
line = elm_code_file_line_get(file, 1);
content = elm_code_line_text_get(line, &length);
ck_assert_strn_eq("atest", content, length);
elm_code_widget_undo(widget);
content = elm_code_line_text_get(line, &length);
ck_assert_strn_eq("test", content, length);
elm_code_widget_cursor_position_set(widget, 3, 1);
_elm_code_widget_text_at_cursor_insert(widget, "r", 1);
content = elm_code_line_text_get(line, &length);
ck_assert_strn_eq("terst", content, length);
elm_code_widget_undo(widget);
content = elm_code_line_text_get(line, &length);
ck_assert_strn_eq("test", content, length);
elm_code_widget_cursor_position_set(widget, 4, 1);
_elm_code_widget_text_at_cursor_insert(widget, "\t", 1);
content = elm_code_line_text_get(line, &length);
ck_assert_strn_eq("tes\tt", content, length);
elm_code_widget_undo(widget);
content = elm_code_line_text_get(line, &length);
ck_assert_strn_eq("test", content, length);
elm_code_free(code);
elm_shutdown();
}
END_TEST
START_TEST (elm_code_test_widget_undo_newline)
{
Elm_Code *code;
Elm_Code_File *file;
Elm_Code_Line *line;
Elm_Code_Widget *widget;
Evas_Object *win;
unsigned int length;
const char *content;
elm_init(1, NULL);
code = elm_code_create();
file = elm_code_file_new(code);
elm_code_file_line_append(file, "test", 4, NULL);
win = elm_win_add(NULL, "entry", ELM_WIN_BASIC);
widget = elm_code_widget_add(win, code);
elm_code_widget_cursor_position_set(widget, 5, 1);
_elm_code_widget_newline(widget);
ck_assert_int_eq(2, elm_code_file_lines_get(file));
line = elm_code_file_line_get(file, 1);
content = elm_code_line_text_get(line, &length);
ck_assert_strn_eq("test", content, 1);
elm_code_widget_undo(widget);
ck_assert_int_eq(1, elm_code_file_lines_get(file));
line = elm_code_file_line_get(file, 1);
content = elm_code_line_text_get(line, &length);
ck_assert_strn_eq("test", content, 4);
elm_code_free(code);
elm_shutdown();
}
END_TEST
void elm_code_test_widget_undo(TCase *tc)
{
tcase_add_test(tc, elm_code_test_widget_undo_text_insert);
tcase_add_test(tc, elm_code_test_widget_undo_newline);
}

View File

@ -52,86 +52,13 @@ _edi_editor_autosave_cb(void *data)
return ECORE_CALLBACK_CANCEL;
}
static void
_undo_do(Edi_Editor *editor, Elm_Entry_Change_Info *inf)
{
if (inf->insert)
{
const Evas_Object *tb = elm_entry_textblock_get(editor->entry);
Evas_Textblock_Cursor *mcur, *end;
mcur = (Evas_Textblock_Cursor *) evas_object_textblock_cursor_get(tb);
end = evas_object_textblock_cursor_new(tb);
if (inf->insert)
{
elm_entry_cursor_pos_set(editor->entry, inf->change.insert.pos);
evas_textblock_cursor_pos_set(end, inf->change.insert.pos +
inf->change.insert.plain_length);
}
else
{
elm_entry_cursor_pos_set(editor->entry, inf->change.del.start);
evas_textblock_cursor_pos_set(end, inf->change.del.end);
}
evas_textblock_cursor_range_delete(mcur, end);
evas_textblock_cursor_free(end);
elm_entry_calc_force(editor->entry);
}
else
{
if (inf->insert)
{
elm_entry_cursor_pos_set(editor->entry, inf->change.insert.pos);
elm_entry_entry_insert(editor->entry, inf->change.insert.content);
}
else
{
size_t start;
start = (inf->change.del.start < inf->change.del.end) ?
inf->change.del.start : inf->change.del.end;
elm_entry_cursor_pos_set(editor->entry, start);
elm_entry_entry_insert(editor->entry, inf->change.insert.content);
elm_entry_cursor_pos_set(editor->entry, inf->change.del.end);
}
}
}
static void
_undo_cb(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
_changed_cb(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
Elm_Entry_Change_Info *change;
Edi_Editor *editor = data;
if (!eina_list_next(editor->undo_stack))
return;
change = eina_list_data_get(editor->undo_stack);
_undo_do(editor, change);
editor->undo_stack = eina_list_next(editor->undo_stack);
}
static void
_changed_cb(void *data, Evas_Object *obj EINA_UNUSED, void *event_info)
{
Elm_Entry_Change_Info *change;
Edi_Editor *editor = data;
/*
TODO move this code into elm_code for undo/redo
change = calloc(1, sizeof(*change));
memcpy(change, event_info, sizeof(*change));
if (change->insert)
{
eina_stringshare_ref(change->change.insert.content);
}
else
{
eina_stringshare_ref(change->change.del.content);
}
editor->undo_stack = eina_list_prepend(editor->undo_stack, change);
*/
editor->modified = EINA_TRUE;
if (editor->save_timer)
@ -184,10 +111,6 @@ _smart_cb_key_down(void *data EINA_UNUSED, Evas *e EINA_UNUSED,
{
edi_mainview_goto_popup_show();
}
else if (!strcmp(ev->key, "z"))
{
_undo_cb(editor, obj, event);
}
}
}