#include "config.h" #include #include #include "mess_header.h" #include "file_utils.h" #include "cfg.h" #include "ui/ui.h" static Eina_Unicode plain_utf8 = EINA_TRUE; static void print_usage(const char *bin); static void editor_font_set(Ecrire_Entry *inst, const char *font, int font_size); /* specific log domain to help debug only ecrire */ int _ecrire_log_dom = -1; static void _init_entry(Ecrire_Entry *inst) { Elm_Entry_Change_Info *inf; editor_font_set(inst, _ent_cfg->font.name, _ent_cfg->font.size); /* Init the undo stack */ EINA_LIST_FREE(inst->undo_stack, inf) { if (inf) { if (inf->insert) { eina_stringshare_del(inf->change.insert.content); } else { eina_stringshare_del(inf->change.del.content); } free(inf); } } inst->undo_stack = inst->undo_stack_ptr = eina_list_append(inst->undo_stack, NULL); inst->last_saved_stack_ptr = inst->undo_stack_ptr; inst->undo_stack_can_merge = EINA_FALSE; elm_object_item_disabled_set(inst->undo_item, EINA_TRUE); elm_object_item_disabled_set(inst->redo_item, EINA_TRUE); } static void _alert_if_need_saving(void (*done)(void *data), Ecrire_Entry *inst) { if (!elm_object_item_disabled_get(inst->save_item)) { ui_alert_need_saving(inst->entry, done, inst); } else { done(inst); } } static void _cb_sel_start(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) { Ecrire_Entry *inst = data; elm_object_item_disabled_set(inst->copy_item, EINA_FALSE); elm_object_item_disabled_set(inst->cut_item, EINA_FALSE); } static void _cb_sel_clear(void *data EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) { Ecrire_Entry *inst = data; elm_object_item_disabled_set(inst->copy_item, EINA_TRUE); elm_object_item_disabled_set(inst->cut_item, EINA_TRUE); } static void _update_cur_file(const char *file, Ecrire_Entry *inst) { const char *saving = (!elm_object_item_disabled_get(inst->save_item)) ? "*" : ""; eina_stringshare_replace(&inst->filename, file); { char buf[1024]; if (inst->filename) snprintf(buf, sizeof(buf), _("%s%s - %s"), saving, inst->filename, PACKAGE_NAME); else snprintf(buf, sizeof(buf), _("%sUntitled %d - %s"), saving, inst->unsaved, PACKAGE_NAME); if (inst->filename) elm_object_text_set(inst->frame, inst->filename); else elm_object_text_set(inst->frame, buf); elm_win_title_set(inst->win, buf); } } static void _cb_cur_changed(void *data, Evas_Object *obj, void *event_info EINA_UNUSED) { char buf[50]; int line; int col; const Evas_Object *tb = elm_entry_textblock_get(obj); const Evas_Textblock_Cursor *mcur = evas_object_textblock_cursor_get(tb); Evas_Textblock_Cursor *cur = evas_object_textblock_cursor_new(tb); line = evas_textblock_cursor_line_geometry_get(mcur, NULL, NULL, NULL, NULL) + 1; evas_textblock_cursor_copy(mcur, cur); evas_textblock_cursor_line_char_first(cur); col = evas_textblock_cursor_pos_get(mcur) - evas_textblock_cursor_pos_get(cur) + 1; evas_textblock_cursor_free(cur); snprintf(buf, sizeof(buf), _("Ln %d, Col %d"), line, col); elm_object_text_set(data, buf); } static void _cb_cur_changed_manual(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) { Ecrire_Entry *inst = data; inst->undo_stack_can_merge = EINA_FALSE; } static void _update_undo_redo_items(Ecrire_Entry *inst) { elm_object_item_disabled_set(inst->undo_item, !eina_list_next(inst->undo_stack_ptr)); elm_object_item_disabled_set(inst->redo_item, !eina_list_prev(inst->undo_stack_ptr)); if (inst->undo_stack_ptr == inst->last_saved_stack_ptr) { elm_object_item_disabled_set(inst->save_item, EINA_TRUE); _update_cur_file(inst->filename, inst); } else if (elm_object_item_disabled_get(inst->save_item)) { elm_object_item_disabled_set(inst->save_item, EINA_FALSE); _update_cur_file(inst->filename, inst); } } static void _undo_stack_add(Ecrire_Entry *inst, Elm_Entry_Change_Info *_info) { Elm_Entry_Change_Info *inf; inst->undo_stack = eina_list_split_list(inst->undo_stack, eina_list_prev(inst->undo_stack_ptr), &inst->undo_stack_ptr); EINA_LIST_FREE(inst->undo_stack, inf) { if (inf->insert) { eina_stringshare_del(inf->change.insert.content); } else { eina_stringshare_del(inf->change.del.content); } free(inf); } /* FIXME: Do a smarter merge, actually merge the structures, not just * mark them to be merged. */ #if 0 inf = (Elm_Entry_Change_Info *) eina_list_data_get(undo_stack_ptr); /* If true, we should merge with the current top */ if (undo_stack_can_merge && (_info->insert == inf->insert)) { } else #endif { Elm_Entry_Change_Info *head_inf = eina_list_data_get(inst->undo_stack_ptr); inf = calloc(1, sizeof(*inf)); memcpy(inf, _info, sizeof(*inf)); if (inf->insert) { eina_stringshare_ref(inf->change.insert.content); } else { eina_stringshare_ref(inf->change.del.content); } if (inst->undo_stack_can_merge && (inf->insert == head_inf->insert)) inf->merge = EINA_TRUE; inst->undo_stack_ptr = eina_list_prepend(inst->undo_stack_ptr, inf); } inst->undo_stack = inst->undo_stack_ptr; inst->undo_stack_can_merge = EINA_TRUE; _update_undo_redo_items(inst); } static void _undo_redo_do(Ecrire_Entry *inst, Elm_Entry_Change_Info *inf, Eina_Bool undo) { DBG("%s: %s", (undo) ? "Undo" : "Redo", inf->change.insert.content); if ((inf->insert && undo) || (!inf->insert && !undo)) { const Evas_Object *tb = elm_entry_textblock_get(inst->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(inst->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(inst->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(inst->entry); } else { if (inf->insert) { elm_entry_cursor_pos_set(inst->entry, inf->change.insert.pos); elm_entry_entry_insert(inst->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(inst->entry, start); elm_entry_entry_insert(inst->entry, inf->change.insert.content); elm_entry_cursor_pos_set(inst->entry, inf->change.del.end); } } /* No matter what, once we did an undo/redo we don't want to merge, * even if we got backt to the top of the stack. */ inst->undo_stack_can_merge = EINA_FALSE; } static void _cb_undo(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) { /* In undo we care about the current item */ Ecrire_Entry *inst = data; Elm_Entry_Change_Info *inf = NULL; if (!eina_list_next(inst->undo_stack_ptr)) return; do { inf = eina_list_data_get(inst->undo_stack_ptr); _undo_redo_do(inst, inf, EINA_TRUE); if (eina_list_next(inst->undo_stack_ptr)) { inst->undo_stack_ptr = eina_list_next(inst->undo_stack_ptr); } else { break; } } while (inf && inf->merge); _update_undo_redo_items(inst); } static void _cb_redo(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) { Ecrire_Entry *inst = data; Elm_Entry_Change_Info *inf = NULL; if (!eina_list_prev(inst->undo_stack_ptr)) return; do { if (eina_list_prev(inst->undo_stack_ptr)) { inst->undo_stack_ptr = eina_list_prev(inst->undo_stack_ptr); /* In redo we care about the NEW item */ inf = eina_list_data_get(inst->undo_stack_ptr); _undo_redo_do(inst, inf, EINA_FALSE); } else { break; } /* Update inf to next for the condition. */ if (eina_list_prev(inst->undo_stack_ptr)) { inf = eina_list_data_get(eina_list_prev(inst->undo_stack_ptr)); } } while (inf && inf->merge); _update_undo_redo_items(inst); } static void _cb_ent_changed(void *data, Evas_Object *obj EINA_UNUSED, void *event_info) { Ecrire_Entry *inst = data; elm_object_item_disabled_set(inst->save_item, EINA_FALSE); _update_cur_file(inst->filename, inst); /* Undo/redo */ _undo_stack_add(inst, event_info); } static void _load_to_entry(Ecrire_Entry *inst, const char *file) { if (!file) _init_entry(inst); else { char *buf; if (plain_utf8) buf = file_plain_load(file); else buf = file_load(file); elm_object_text_set(inst->entry, ""); _init_entry(inst); elm_entry_entry_append(inst->entry, buf); elm_object_item_disabled_set(inst->save_item, EINA_TRUE); free(buf); } _update_cur_file(file, inst); } static void _cb_fs_open_done(void *data, Evas_Object *obj EINA_UNUSED, void *event_info) { Ecrire_Entry *inst; const char *selected = event_info; if (!selected) return; inst = data; if (ecore_file_app_installed("ecrire")) ecore_exe_run(eina_slstr_printf("ecrire %s", selected), NULL); else _load_to_entry(inst, selected); } void save_do(const char *file, Ecrire_Entry *inst) { if (plain_utf8) file_plain_save(file, elm_object_text_get(inst->entry)); else file_save(file, elm_object_text_get(inst->entry)); elm_object_item_disabled_set(inst->save_item, EINA_TRUE); inst->last_saved_stack_ptr = inst->undo_stack_ptr; _update_cur_file(file, inst); } static void _cb_fs_save_done(void *data EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info) { Ecrire_Entry *inst = data; const char *selected = event_info; if (selected) save_do(selected, inst); } static void _open_do(void *data) { Ecrire_Entry *inst = data; ui_file_open_save_dialog_open(inst->win, EINA_FALSE, _cb_fs_open_done, inst); } static void _cb_goto_line(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) { Ecrire_Entry *inst = data; ui_goto_dialog_open(inst->win, inst); } static void _cb_open(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) { Ecrire_Entry *inst = data; _alert_if_need_saving(_open_do, inst); } void editor_save(Ecrire_Entry *inst, void *callback_func) { if (inst->filename) save_do(inst->filename, inst); else ui_file_open_save_dialog_open(inst->win, EINA_TRUE, callback_func, inst); } static void _cb_save(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) { Ecrire_Entry *inst = data; editor_save(inst, _cb_fs_save_done); } static void _cb_save_as(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) { Ecrire_Entry *inst = data; ui_file_open_save_dialog_open(inst->win, EINA_TRUE, _cb_fs_save_done, inst); } static void _win_del_do(void *data EINA_UNUSED) { elm_exit(); } static void _new_do(void *data) { Ecrire_Entry *inst = data; if (ecore_file_app_installed("ecrire")) ecore_exe_run("ecrire", NULL); else { elm_object_text_set(inst->entry, ""); _init_entry(inst); elm_object_item_disabled_set(inst->save_item, EINA_TRUE); _update_cur_file(NULL, inst); } } static void _cb_new(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) { Ecrire_Entry *inst = data; _alert_if_need_saving(_new_do, inst); } static void _app_exit(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) { Ecrire_Entry *inst = data; _alert_if_need_saving(_win_del_do, inst); } static void _cb_cut(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) { Ecrire_Entry *inst = data; elm_entry_selection_cut(inst->entry); } static void _cb_copy(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) { Ecrire_Entry *inst = data; elm_entry_selection_copy(inst->entry); } static void _cb_paste(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) { Ecrire_Entry *inst = data; elm_entry_selection_paste(inst->entry); } static void _cb_find(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) { Ecrire_Entry *inst = data; ui_find_dialog_open(inst->win, inst); } static void _cb_font_settings(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) { Ecrire_Entry *inst = data; ui_font_dialog_open(elm_object_top_widget_get(inst->win), inst, _ent_cfg->font.name, _ent_cfg->font.size); } static void _cb_win_del(void *data, Evas_Object *obj, void *event_info) { Ecrire_Entry *inst = data; (void) data; (void) obj; (void) event_info; _alert_if_need_saving(_win_del_do, inst); } static void _cb_win_focused(void *data, Evas_Object *obj, void *event_info) { Ecrire_Entry *inst = data; (void) data; (void) obj; (void) event_info; elm_object_focus_set(inst->entry, EINA_TRUE); } static void _cb_key_down(void *data, Evas *e EINA_UNUSED, Evas_Object *obj, void *event) { Ecrire_Entry *inst = data; Eina_Bool ctrl, alt, shift; Evas_Event_Key_Down *ev = event; ctrl = evas_key_modifier_is_set(ev->modifiers, "Control"); alt = evas_key_modifier_is_set(ev->modifiers, "Alt"); shift = evas_key_modifier_is_set(ev->modifiers, "Shift"); if ((ctrl) && (!alt) && (!shift)) { if (!strcmp(ev->keyname, "a")) elm_entry_select_all(inst->entry); else if (!strcmp(ev->keyname, "f")) _cb_find(data, obj, event); else if (!strcmp(ev->keyname, "g")) _cb_goto_line(data, obj, event); else if (!strcmp(ev->keyname, "n")) _cb_new(data, obj, event); else if (!strcmp(ev->keyname, "s")) _cb_save(data, obj, event); } } static void editor_font_set(Ecrire_Entry *inst, const char *font, int font_size) { const Evas_Object *tb = elm_entry_textblock_get(inst->entry); Eina_Strbuf *sbuf; eina_stringshare_replace(&_ent_cfg->font.name, font); _ent_cfg->font.size = font_size; sbuf = eina_strbuf_new(); if (_ent_cfg->font.name) { eina_strbuf_append_printf(sbuf, "font=\\'%s\\'", _ent_cfg->font.name); } if (_ent_cfg->font.size > 0) { eina_strbuf_append_printf(sbuf, " font_size=\\'%d\\'", _ent_cfg->font.size); } if (eina_strbuf_length_get(sbuf) > 0) { Evas_Textblock_Style *ts = evas_textblock_style_new(); eina_strbuf_prepend(sbuf, "DEFAULT='"); eina_strbuf_append(sbuf, "'"); evas_textblock_style_set(ts, eina_strbuf_string_get(sbuf)); evas_object_textblock_style_user_push((Evas_Object *) tb, ts); } else { evas_object_textblock_style_user_pop((Evas_Object *) tb); } elm_entry_calc_force(inst->entry); eina_strbuf_free(sbuf); } void editor_font_choose(Ecrire_Entry *inst, const char *font, int size) { editor_font_set(inst, font, size); /* Save the font for future runs */ ecrire_cfg_save(); } static void _ecrire_menu_add(Ecrire_Entry *inst) { Evas_Object *menu; Elm_Object_Item *it; menu = elm_win_main_menu_get(inst->win); it = elm_menu_item_add(menu, NULL, NULL, _("File"), NULL, NULL); elm_menu_item_add(menu, it, "document-new", _("New"), _cb_new, inst); elm_menu_item_add(menu, it, "document-open", _("Open"), _cb_open, inst); inst->save_item = elm_menu_item_add(menu, it, "document-save", _("Save"), _cb_save, inst); elm_menu_item_add(menu, it, "document-save-as", _("Save As"), _cb_save_as, inst); elm_menu_item_separator_add(menu, it); elm_menu_item_add(menu, it, "application-exit", _("Exit"), _app_exit, inst); it = elm_menu_item_add(menu, NULL, NULL, _("Edit"), NULL, NULL); inst->cut_item = elm_menu_item_add(menu, it, "edit-cut", _("Cut"), _cb_cut, inst); inst->copy_item = elm_menu_item_add(menu, it, "edit-copy", _("Copy"), _cb_copy, inst); inst->paste_item = elm_menu_item_add(menu, it, "edit-paste", _("Paste"), _cb_paste, inst); elm_menu_item_separator_add(menu, it); inst->undo_item = elm_menu_item_add(menu, it, "edit-undo", _("Undo"), _cb_undo, inst); inst->redo_item = elm_menu_item_add(menu, it, "edit-redo", _("Redo"), _cb_redo, inst); elm_menu_item_separator_add(menu, it); elm_menu_item_add(menu, it, "edit-find-replace", _("Find"), _cb_find, inst); elm_menu_item_add(menu, it, "go-jump", _("Go to line.."), _cb_goto_line, inst); elm_menu_item_separator_add(menu, it); elm_menu_item_add(menu, it, "preferences-system", _("Settings"), _cb_font_settings, inst); /* We don't have a selection when we start, make the items disabled */ elm_object_item_disabled_set(inst->copy_item, EINA_TRUE); elm_object_item_disabled_set(inst->cut_item, EINA_TRUE); elm_object_item_disabled_set(inst->save_item, EINA_TRUE); } EAPI_MAIN int elm_main(int argc, char **argv) { Evas_Object *win, *pad, *fr, *bx, *entry, *cur_info; Evas_Coord w = 600, h = 600; int c; opterr = 0; _ecrire_log_dom = eina_log_domain_register("ecrire", ECRIRE_DEFAULT_LOG_COLOR); if (_ecrire_log_dom < 0) { EINA_LOG_ERR("Unable to create a log domain."); exit(-1); } while ((c = getopt (argc, argv, "")) != -1) { switch (c) { case '?': print_usage(argv[0]); if (isprint (optopt)) { ERR("Unknown option or requires an argument `-%c'.", optopt); } else { ERR("Unknown option character `\\x%x'.", optopt); } return 1; break; default: abort(); } } elm_app_compile_bin_dir_set(PACKAGE_BIN_DIR); elm_app_compile_lib_dir_set(PACKAGE_LIB_DIR); elm_app_compile_data_dir_set(PACKAGE_DATA_DIR); #ifdef ENABLE_NLS elm_app_compile_locale_set(LOCALEDIR); #endif elm_app_info_set(elm_main, "ecrire", "COPYING"); setlocale(LC_ALL, ""); bindtextdomain(PACKAGE, LOCALE_DIR); textdomain(PACKAGE); ecrire_cfg_init(PACKAGE_NAME); ecrire_cfg_load(); Ecrire_Entry *inst = calloc(1, sizeof(Ecrire_Entry)); inst->unsaved = 1; inst->filename = NULL; inst->last_saved_stack_ptr = NULL; inst->undo_stack_can_merge = EINA_FALSE; if (optind < argc) { inst->filename = eina_stringshare_add(argv[optind]); } DBG("Opening filename: '%s'", inst->filename); inst->win = win = elm_win_util_standard_add("erire", "Ecrire"); elm_win_autodel_set(inst->win, EINA_FALSE); bx = elm_box_add(win); elm_win_resize_object_add(inst->win, bx); evas_object_size_hint_weight_set(bx, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); evas_object_show(bx); inst->frame = fr = elm_frame_add(win); evas_object_size_hint_align_set(fr, EVAS_HINT_FILL, EVAS_HINT_FILL); evas_object_size_hint_weight_set(fr, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); evas_object_show(fr); inst->entry = entry = elm_entry_add(win); elm_entry_scrollable_set(entry, EINA_TRUE); elm_entry_line_wrap_set(entry, _ent_cfg->wrap_type); elm_entry_cnp_mode_set(entry, ELM_CNP_MODE_PLAINTEXT); evas_object_size_hint_align_set(entry, EVAS_HINT_FILL, EVAS_HINT_FILL); evas_object_size_hint_weight_set(entry, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); elm_object_content_set(fr, entry); elm_box_pack_end(bx, fr); evas_object_show(entry); pad = elm_frame_add(win); elm_object_style_set(pad, "pad_medium"); evas_object_size_hint_align_set(pad, EVAS_HINT_FILL, 1.0); evas_object_size_hint_weight_set(pad, EVAS_HINT_EXPAND, 0.0); elm_box_pack_end(bx, pad); evas_object_show(pad); bx = elm_box_add(win); elm_box_horizontal_set(bx, 1); evas_object_size_hint_align_set(bx, EVAS_HINT_FILL, 0.5); evas_object_size_hint_weight_set(bx, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); elm_object_content_set(pad, bx); evas_object_show(bx); cur_info = elm_label_add(win); _cb_cur_changed(cur_info, entry, NULL); evas_object_size_hint_align_set(cur_info, 1.0, 0.5); evas_object_size_hint_weight_set(cur_info, EVAS_HINT_EXPAND, 0.0); elm_box_pack_end(bx, cur_info); evas_object_show(cur_info); evas_object_smart_callback_add(entry, "cursor,changed", _cb_cur_changed, cur_info); evas_object_smart_callback_add(entry, "cursor,changed,manual", _cb_cur_changed_manual, inst); evas_object_smart_callback_add(entry, "changed,user", _cb_ent_changed, inst); evas_object_smart_callback_add(entry, "undo,request", _cb_undo, inst); evas_object_smart_callback_add(entry, "redo,request", _cb_redo, inst); evas_object_smart_callback_add(entry, "selection,start", _cb_sel_start, inst); evas_object_smart_callback_add(entry, "selection,cleared", _cb_sel_clear, inst); evas_object_event_callback_add(entry, EVAS_CALLBACK_KEY_DOWN, _cb_key_down, inst); _ecrire_menu_add(inst); evas_object_smart_callback_add(win, "delete,request", _cb_win_del, inst); evas_object_smart_callback_add(win, "focus,in", _cb_win_focused, inst); evas_object_resize(win, w, h); elm_win_center(win, 1, 1); evas_object_show(win); _load_to_entry(inst, inst->filename); elm_run(); free(inst); ecrire_cfg_shutdown(); eina_log_domain_unregister(_ecrire_log_dom); _ecrire_log_dom = -1; return 0; } ELM_MAIN() static void print_usage(const char *bin) { fprintf(stderr, "Usage: %s [filename]\n", bin); }