diff options
Diffstat (limited to 'source/blender/editors/space_text/text_autocomplete.c')
-rw-r--r-- | source/blender/editors/space_text/text_autocomplete.c | 541 |
1 files changed, 541 insertions, 0 deletions
diff --git a/source/blender/editors/space_text/text_autocomplete.c b/source/blender/editors/space_text/text_autocomplete.c new file mode 100644 index 00000000000..e406a1b7166 --- /dev/null +++ b/source/blender/editors/space_text/text_autocomplete.c @@ -0,0 +1,541 @@ +/* + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV. + * All rights reserved. + * + * The Original Code is: all of this file. + * + * Contributor(s): none yet. + * + * ***** END GPL LICENSE BLOCK ***** + */ + +/** \file blender/editors/space_text/text_autocomplete.c + * \ingroup sptext + */ + +#include <ctype.h> +#include <string.h> + +#include "MEM_guardedalloc.h" + +#include "DNA_text_types.h" + +#include "BLI_blenlib.h" +#include "BLI_ghash.h" + +#include "BKE_context.h" +#include "BKE_text.h" +#include "BKE_screen.h" +#include "BKE_suggestions.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "ED_screen.h" +#include "UI_interface.h" + +#include "text_format.h" +#include "text_intern.h" /* own include */ + + +/* -------------------------------------------------------------------- */ +/* Public API */ + +int text_do_suggest_select(SpaceText *st, ARegion *ar) +{ + SuggItem *item, *first, *last /* , *sel */ /* UNUSED */; + TextLine *tmp; + int l, x, y, w, h, i; + int tgti, *top; + int mval[2] = {0, 0}; + + if (!st || !st->text) return 0; + if (!texttool_text_is_active(st->text)) return 0; + + first = texttool_suggest_first(); + last = texttool_suggest_last(); + /* sel = texttool_suggest_selected(); */ /* UNUSED */ + top = texttool_suggest_top(); + + if (!last || !first) + return 0; + + /* Count the visible lines to the cursor */ + for (tmp = st->text->curl, l = -st->top; tmp; tmp = tmp->prev, l++) ; + if (l < 0) return 0; + + text_update_character_width(st); + + if (st->showlinenrs) { + x = st->cwidth * (st->text->curc - st->left) + TXT_OFFSET + TEXTXLOC - 4; + } + else { + x = st->cwidth * (st->text->curc - st->left) + TXT_OFFSET - 4; + } + y = ar->winy - st->lheight_dpi * l - 2; + + w = SUGG_LIST_WIDTH * st->cwidth + U.widget_unit; + h = SUGG_LIST_SIZE * st->lheight_dpi + 0.4f * U.widget_unit; + + // XXX getmouseco_areawin(mval); + + if (mval[0] < x || x + w < mval[0] || mval[1] < y - h || y < mval[1]) + return 0; + + /* Work out which of the items is at the top of the visible list */ + for (i = 0, item = first; i < *top && item->next; i++, item = item->next) ; + + /* Work out the target item index in the visible list */ + tgti = (y - mval[1] - 4) / st->lheight_dpi; + if (tgti < 0 || tgti > SUGG_LIST_SIZE) + return 1; + + for (i = tgti; i > 0 && item->next; i--, item = item->next) ; + if (item) + texttool_suggest_select(item); + return 1; +} + +void text_pop_suggest_list(void) +{ + SuggItem *item, *sel; + int *top, i; + + item = texttool_suggest_first(); + sel = texttool_suggest_selected(); + top = texttool_suggest_top(); + + i = 0; + while (item && item != sel) { + item = item->next; + i++; + } + if (i > *top + SUGG_LIST_SIZE - 1) + *top = i - SUGG_LIST_SIZE + 1; + else if (i < *top) + *top = i; +} + +/* -------------------------------------------------------------------- */ +/* Private API */ + +static void text_autocomplete_free(bContext *C, wmOperator *op); + +static GHash *text_autocomplete_build(Text *text) +{ + GHash *gh; + int seek_len = 0; + const char *seek; + texttool_text_clear(); + + texttool_text_set_active(text); + + /* first get the word we're at */ + { + const int i = text_find_identifier_start(text->curl->line, text->curc); + seek_len = text->curc - i; + seek = text->curl->line + i; + + // BLI_strncpy(seek, seek_ptr, seek_len); + } + + /* now walk over entire doc and suggest words */ + { + TextLine *linep; + + gh = BLI_ghash_str_new(__func__); + + for (linep = text->lines.first; linep; linep = linep->next) { + int i_start = 0; + int i_end = 0; + + while (i_start < linep->len) { + /* seek identifier beginning */ + while (i_start < linep->len && !text_check_identifier(linep->line[i_start])) { + i_start++; + } + i_end = i_start; + while (i_end < linep->len && text_check_identifier(linep->line[i_end])) { + i_end++; + } + + if (i_start != i_end) { + char *str_sub = &linep->line[i_start]; + const int choice_len = i_end - i_start; + + if ((choice_len > seek_len) && + (seek_len == 0 || strncmp(seek, str_sub, seek_len) == 0) && + (seek != str_sub)) + { + // printf("Adding: %s\n", s); + char str_sub_last = str_sub[choice_len]; + str_sub[choice_len] = '\0'; + if (!BLI_ghash_lookup(gh, str_sub)) { + char *str_dup = BLI_strdupn(str_sub, choice_len); + BLI_ghash_insert(gh, str_dup, str_dup); /* A 'set' would make more sense here */ + } + str_sub[choice_len] = str_sub_last; + } + } + i_start = i_end; + } + } + + { + GHashIterator *iter = BLI_ghashIterator_new(gh); + + /* get the formatter for highlighting */ + TextFormatType *tft; + tft = ED_text_format_get(text); + + for (; !BLI_ghashIterator_isDone(iter); BLI_ghashIterator_step(iter)) { + const char *s = BLI_ghashIterator_getValue(iter); + texttool_suggest_add(s, tft->format_identifier(s)); + } + BLI_ghashIterator_free(iter); + + } + } + + texttool_suggest_prefix(seek, seek_len); + + return gh; +} + +/* -- */ + +static void get_suggest_prefix(Text *text, int offset) +{ + int i, len; + char *line; + + if (!text) return; + if (!texttool_text_is_active(text)) return; + + line = text->curl->line; + i = text_find_identifier_start(line, text->curc + offset); + len = text->curc - i + offset; + texttool_suggest_prefix(line + i, len); +} + +static void confirm_suggestion(Text *text) +{ + SuggItem *sel; + int i, over = 0; + const char *line; + + if (!text) return; + if (!texttool_text_is_active(text)) return; + + sel = texttool_suggest_selected(); + if (!sel) return; + + line = text->curl->line; + i = text_find_identifier_start(line, text->curc /* - skipleft */); + over = text->curc - i; + +// for (i = 0; i < skipleft; i++) +// txt_move_left(text, 0); + for (i = 0; i < over; i++) + txt_move_left(text, 1); + + txt_insert_buf(text, sel->name); + +// for (i = 0; i < skipleft; i++) +// txt_move_right(text, 0); + + texttool_text_clear(); +} + +/* -- */ + + +static int text_autocomplete_invoke(bContext *C, wmOperator *op, wmEvent *UNUSED(event)) +{ + SpaceText *st = CTX_wm_space_text(C); + Text *text = CTX_data_edit_text(C); + + st->doplugins = TRUE; + op->customdata = text_autocomplete_build(text); + + if (texttool_suggest_first()) { + + ED_area_tag_redraw(CTX_wm_area(C)); + + if (texttool_suggest_first() == texttool_suggest_last()) { + confirm_suggestion(st->text); + text_update_line_edited(st->text->curl); + text_autocomplete_free(C, op); + return OPERATOR_FINISHED; + } + else { + WM_event_add_modal_handler(C, op); + return OPERATOR_RUNNING_MODAL; + } + } + else { + text_autocomplete_free(C, op); + return OPERATOR_CANCELLED; + } +} + +static int doc_scroll = 0; + +static int text_autocomplete_modal(bContext *C, wmOperator *op, wmEvent *event) +{ + SpaceText *st = CTX_wm_space_text(C); + ScrArea *sa = CTX_wm_area(C); + ARegion *ar = BKE_area_find_region_type(sa, RGN_TYPE_WINDOW); + + int draw = 0, tools = 0, swallow = 0, scroll = 1; + Text *text = CTX_data_edit_text(C); + int retval = OPERATOR_RUNNING_MODAL; + + (void)text; + + if (st->doplugins && texttool_text_is_active(st->text)) { + if (texttool_suggest_first()) tools |= TOOL_SUGG_LIST; + if (texttool_docs_get()) tools |= TOOL_DOCUMENT; + } + + switch (event->type) { + case LEFTMOUSE: + if (event->val == KM_PRESS) { + if (text_do_suggest_select(st, ar)) + swallow = 1; + else { + if (tools & TOOL_SUGG_LIST) texttool_suggest_clear(); + if (tools & TOOL_DOCUMENT) texttool_docs_clear(), doc_scroll = 0; + retval = OPERATOR_FINISHED; + } + draw = 1; + } + break; + case MIDDLEMOUSE: + if (event->val == KM_PRESS) { + if (text_do_suggest_select(st, ar)) { + confirm_suggestion(st->text); + text_update_line_edited(st->text->curl); + swallow = 1; + } + else { + if (tools & TOOL_SUGG_LIST) texttool_suggest_clear(); + if (tools & TOOL_DOCUMENT) texttool_docs_clear(), doc_scroll = 0; + retval = OPERATOR_FINISHED; + } + draw = 1; + } + break; + case ESCKEY: + if (event->val == KM_PRESS) { + draw = swallow = 1; + if (tools & TOOL_SUGG_LIST) texttool_suggest_clear(); + else if (tools & TOOL_DOCUMENT) texttool_docs_clear(), doc_scroll = 0; + else draw = swallow = 0; + retval = OPERATOR_CANCELLED; + } + break; + case RETKEY: + if (event->val == KM_PRESS) { + if (tools & TOOL_SUGG_LIST) { + confirm_suggestion(st->text); + text_update_line_edited(st->text->curl); + swallow = 1; + draw = 1; + } + if (tools & TOOL_DOCUMENT) texttool_docs_clear(), doc_scroll = 0, draw = 1; + retval = OPERATOR_FINISHED; + } + break; + case LEFTARROWKEY: + case BACKSPACEKEY: + if (event->val == KM_PRESS) { + if (tools & TOOL_SUGG_LIST) { + if (event->ctrl) { + texttool_suggest_clear(); + retval = OPERATOR_CANCELLED; + } + else { + /* Work out which char we are about to delete/pass */ + if (st->text->curl && st->text->curc > 0) { + char ch = st->text->curl->line[st->text->curc - 1]; + if ((ch == '_' || !ispunct(ch)) && !text_check_whitespace(ch)) { + get_suggest_prefix(st->text, -1); + text_pop_suggest_list(); + } + else { + texttool_suggest_clear(); + retval = OPERATOR_CANCELLED; + } + } + else { + texttool_suggest_clear(); + retval = OPERATOR_CANCELLED; + } + } + } + if (tools & TOOL_DOCUMENT) texttool_docs_clear(), doc_scroll = 0; + } + break; + case RIGHTARROWKEY: + if (event->val == KM_PRESS) { + if (tools & TOOL_SUGG_LIST) { + if (event->ctrl) { + texttool_suggest_clear(); + retval = OPERATOR_CANCELLED; + } + else { + /* Work out which char we are about to pass */ + if (st->text->curl && st->text->curc < st->text->curl->len) { + char ch = st->text->curl->line[st->text->curc + 1]; + if ((ch == '_' || !ispunct(ch)) && !text_check_whitespace(ch)) { + get_suggest_prefix(st->text, 1); + text_pop_suggest_list(); + } + else { + texttool_suggest_clear(); + retval = OPERATOR_CANCELLED; + } + } + else { + texttool_suggest_clear(); + retval = OPERATOR_CANCELLED; + } + } + } + if (tools & TOOL_DOCUMENT) texttool_docs_clear(), doc_scroll = 0; + } + break; + case PAGEDOWNKEY: + scroll = SUGG_LIST_SIZE - 1; + case WHEELDOWNMOUSE: + case DOWNARROWKEY: + if (event->val == KM_PRESS) { + if (tools & TOOL_DOCUMENT) { + doc_scroll++; + swallow = 1; + draw = 1; + } + else if (tools & TOOL_SUGG_LIST) { + SuggItem *sel = texttool_suggest_selected(); + if (!sel) { + texttool_suggest_select(texttool_suggest_first()); + } + else { + while (sel && sel != texttool_suggest_last() && sel->next && scroll--) { + texttool_suggest_select(sel->next); + sel = sel->next; + } + } + text_pop_suggest_list(); + swallow = 1; + draw = 1; + } + } + break; + case PAGEUPKEY: + scroll = SUGG_LIST_SIZE - 1; + case WHEELUPMOUSE: + case UPARROWKEY: + if (event->val == KM_PRESS) { + if (tools & TOOL_DOCUMENT) { + if (doc_scroll > 0) doc_scroll--; + swallow = 1; + draw = 1; + } + else if (tools & TOOL_SUGG_LIST) { + SuggItem *sel = texttool_suggest_selected(); + while (sel && sel != texttool_suggest_first() && sel->prev && scroll--) { + texttool_suggest_select(sel->prev); + sel = sel->prev; + } + text_pop_suggest_list(); + swallow = 1; + draw = 1; + } + } + break; + case RIGHTSHIFTKEY: + case LEFTSHIFTKEY: + break; +#if 0 + default: + if (tools & TOOL_SUGG_LIST) texttool_suggest_clear(), draw = 1; + if (tools & TOOL_DOCUMENT) texttool_docs_clear(), doc_scroll = 0, draw = 1; +#endif + } + + if (draw) { + ED_area_tag_redraw(CTX_wm_area(C)); + } + +// if (swallow) { +// retval = OPERATOR_RUNNING_MODAL; +// } + + if (texttool_suggest_first()) { + if (retval != OPERATOR_RUNNING_MODAL) { + text_autocomplete_free(C, op); + } + return retval; + } + else { + text_autocomplete_free(C, op); + return OPERATOR_FINISHED; + } +} + +static void text_autocomplete_free(bContext *C, wmOperator *op) +{ + GHash *gh = op->customdata; + if (gh) { + BLI_ghash_free(gh, NULL, (GHashValFreeFP)MEM_freeN); + op->customdata = NULL; + } + + /* other stuff */ + { + SpaceText *st = CTX_wm_space_text(C); + st->doplugins = FALSE; + texttool_text_clear(); + } +} + +static int text_autocomplete_cancel(bContext *C, wmOperator *op) +{ + text_autocomplete_free(C, op); + return OPERATOR_CANCELLED; +} + +void TEXT_OT_autocomplete(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Text Auto Complete"; + ot->description = "Show a list of used text in the open document"; + ot->idname = "TEXT_OT_autocomplete"; + + /* api callbacks */ + ot->invoke = text_autocomplete_invoke; + ot->cancel = text_autocomplete_cancel; + ot->modal = text_autocomplete_modal; + ot->poll = text_space_edit_poll; + + /* flags */ + ot->flag = OPTYPE_BLOCKING; +} |