From acd7b544736311de775a5a69a3dbd2ba5bd47b67 Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Sun, 21 Feb 2016 18:11:33 +0000 Subject: [PATCH] [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 --- ChangeLog | 1 + NEWS | 1 + elm_code/src/lib/widget/elm_code_widget.c | 68 ++++++++++-- elm_code/src/lib/widget/elm_code_widget.eo | 2 + .../src/lib/widget/elm_code_widget_private.h | 19 ++++ .../src/lib/widget/elm_code_widget_undo.c | 56 ++++++++++ elm_code/src/tests/Makefile.am | 1 + elm_code/src/tests/elm_code_suite.c | 1 + elm_code/src/tests/elm_code_suite.h | 1 + .../tests/widget/elm_code_test_widget_undo.c | 101 ++++++++++++++++++ src/bin/editor/edi_editor.c | 79 +------------- 11 files changed, 241 insertions(+), 89 deletions(-) create mode 100644 elm_code/src/lib/widget/elm_code_widget_undo.c create mode 100644 elm_code/src/tests/widget/elm_code_test_widget_undo.c diff --git a/ChangeLog b/ChangeLog index d8a2285..aac0318 100644 --- a/ChangeLog +++ b/ChangeLog @@ -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) diff --git a/NEWS b/NEWS index 9a9682a..58867fb 100644 --- a/NEWS +++ b/NEWS @@ -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: diff --git a/elm_code/src/lib/widget/elm_code_widget.c b/elm_code/src/lib/widget/elm_code_widget.c index 7fa5653..56f8507 100644 --- a/elm_code/src/lib/widget/elm_code_widget.c +++ b/elm_code/src/lib/widget/elm_code_widget.c @@ -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" diff --git a/elm_code/src/lib/widget/elm_code_widget.eo b/elm_code/src/lib/widget/elm_code_widget.eo index b8ab71d..e07459a 100644 --- a/elm_code/src/lib/widget/elm_code_widget.eo +++ b/elm_code/src/lib/widget/elm_code_widget.eo @@ -202,6 +202,8 @@ class Elm.Code_Widget (Elm.Layout, Elm.Interface_Atspi_Text) } return: uint; } + undo { + } } implements { class.constructor; diff --git a/elm_code/src/lib/widget/elm_code_widget_private.h b/elm_code/src/lib/widget/elm_code_widget_private.h index b9f8ad2..fab43c2 100644 --- a/elm_code/src/lib/widget/elm_code_widget_private.h +++ b/elm_code/src/lib/widget/elm_code_widget_private.h @@ -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 diff --git a/elm_code/src/lib/widget/elm_code_widget_undo.c b/elm_code/src/lib/widget/elm_code_widget_undo.c new file mode 100644 index 0000000..d51cc5f --- /dev/null +++ b/elm_code/src/lib/widget/elm_code_widget_undo.c @@ -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; +} + diff --git a/elm_code/src/tests/Makefile.am b/elm_code/src/tests/Makefile.am index 6256c35..6150c6f 100644 --- a/elm_code/src/tests/Makefile.am +++ b/elm_code/src/tests/Makefile.am @@ -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 = \ diff --git a/elm_code/src/tests/elm_code_suite.c b/elm_code/src/tests/elm_code_suite.c index f562b80..31ea7d9 100644 --- a/elm_code/src/tests/elm_code_suite.c +++ b/elm_code/src/tests/elm_code_suite.c @@ -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) diff --git a/elm_code/src/tests/elm_code_suite.h b/elm_code/src/tests/elm_code_suite.h index d541e9b..750bfd7 100644 --- a/elm_code/src/tests/elm_code_suite.h +++ b/elm_code/src/tests/elm_code_suite.h @@ -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 */ diff --git a/elm_code/src/tests/widget/elm_code_test_widget_undo.c b/elm_code/src/tests/widget/elm_code_test_widget_undo.c new file mode 100644 index 0000000..f6c13f0 --- /dev/null +++ b/elm_code/src/tests/widget/elm_code_test_widget_undo.c @@ -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); +} + diff --git a/src/bin/editor/edi_editor.c b/src/bin/editor/edi_editor.c index 0fc4bb2..236a356 100644 --- a/src/bin/editor/edi_editor.c +++ b/src/bin/editor/edi_editor.c @@ -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); - } } }