/* * ***** 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. * * Contributor(s): Blender Foundation * * ***** END GPL LICENSE BLOCK ***** */ /** \file blender/editors/curve/editfont.c * \ingroup edcurve */ #include #include #include #include #ifndef WIN32 #include #else #include #endif #include "MEM_guardedalloc.h" #include "BLI_blenlib.h" #include "BLI_math.h" #include "BLI_utildefines.h" #include "DNA_curve_types.h" #include "DNA_object_types.h" #include "DNA_vfont_types.h" #include "DNA_scene_types.h" #include "DNA_text_types.h" #include "BKE_context.h" #include "BKE_curve.h" #include "BKE_depsgraph.h" #include "BKE_font.h" #include "BKE_library.h" #include "BKE_object.h" #include "BKE_report.h" #include "RNA_access.h" #include "RNA_define.h" #include "WM_api.h" #include "WM_types.h" #include "ED_curve.h" #include "ED_object.h" #include "ED_screen.h" #include "ED_util.h" #include "UI_interface.h" #include "curve_intern.h" #define MAXTEXT 32766 /************************* utilities ******************************/ static char findaccent(char char1, unsigned int code) { char new = 0; if (char1 == 'a') { if (code == '`') new = 224; else if (code == 39) new = 225; else if (code == '^') new = 226; else if (code == '~') new = 227; else if (code == '"') new = 228; else if (code == 'o') new = 229; else if (code == 'e') new = 230; else if (code == '-') new = 170; } else if (char1 == 'c') { if (code == ',') new = 231; else if (code == '|') new = 162; else if (code == 'o') new = 169; } else if (char1 == 'e') { if (code == '`') new = 232; else if (code == 39) new = 233; else if (code == '^') new = 234; else if (code == '"') new = 235; } else if (char1 == 'i') { if (code == '`') new = 236; else if (code == 39) new = 237; else if (code == '^') new = 238; else if (code == '"') new = 239; } else if (char1 == 'n') { if (code == '~') new = 241; } else if (char1 == 'o') { if (code == '`') new = 242; else if (code == 39) new = 243; else if (code == '^') new = 244; else if (code == '~') new = 245; else if (code == '"') new = 246; else if (code == '/') new = 248; else if (code == '-') new = 186; else if (code == 'e') new = 143; else if (code == 'c') new = 169; else if (code == 'r') new = 174; } else if (char1 == 'r') { if (code == 'o') new = 174; } else if (char1 == 's') { if (code == 's') new = 167; } else if (char1 == 't') { if (code == 'm') new = 153; } else if (char1 == 'u') { if (code == '`') new = 249; else if (code == 39) new = 250; else if (code == '^') new = 251; else if (code == '"') new = 252; } else if (char1 == 'y') { if (code == 39) new = 253; else if (code == '"') new = 255; } else if (char1 == 'A') { if (code == '`') new = 192; else if (code == 39) new = 193; else if (code == '^') new = 194; else if (code == '~') new = 195; else if (code == '"') new = 196; else if (code == 'o') new = 197; else if (code == 'e') new = 198; } else if (char1 == 'C') { if (code == ',') new = 199; } else if (char1 == 'E') { if (code == '`') new = 200; else if (code == 39) new = 201; else if (code == '^') new = 202; else if (code == '"') new = 203; } else if (char1 == 'I') { if (code == '`') new = 204; else if (code == 39) new = 205; else if (code == '^') new = 206; else if (code == '"') new = 207; } else if (char1 == 'N') { if (code == '~') new = 209; } else if (char1 == 'O') { if (code == '`') new = 210; else if (code == 39) new = 211; else if (code == '^') new = 212; else if (code == '~') new = 213; else if (code == '"') new = 214; else if (code == '/') new = 216; else if (code == 'e') new = 141; } else if (char1 == 'U') { if (code == '`') new = 217; else if (code == 39) new = 218; else if (code == '^') new = 219; else if (code == '"') new = 220; } else if (char1 == 'Y') { if (code == 39) new = 221; } else if (char1 == '1') { if (code == '4') new = 188; if (code == '2') new = 189; } else if (char1 == '3') { if (code == '4') new = 190; } else if (char1 == ':') { if (code == '-') new = 247; } else if (char1 == '-') { if (code == ':') new = 247; if (code == '|') new = 135; if (code == '+') new = 177; } else if (char1 == '|') { if (code == '-') new = 135; if (code == '=') new = 136; } else if (char1 == '=') { if (code == '|') new = 136; } else if (char1 == '+') { if (code == '-') new = 177; } if (new) return new; else return char1; } static void update_string(Curve *cu) { EditFont *ef = cu->editfont; int len; /* Free the old curve string */ MEM_freeN(cu->str); /* Calculate the actual string length in UTF-8 variable characters */ len = BLI_wstrlen_utf8(ef->textbuf); /* Alloc memory for UTF-8 variable char length string */ cu->str = MEM_callocN(len + sizeof(wchar_t), "str"); /* Copy the wchar to UTF-8 */ BLI_strncpy_wchar_as_utf8(cu->str, ef->textbuf, len + 1); } static int insert_into_textbuf(Object *obedit, uintptr_t c) { Curve *cu = obedit->data; if (cu->len < MAXTEXT - 1) { EditFont *ef = cu->editfont; int x; for (x = cu->len; x > cu->pos; x--) ef->textbuf[x] = ef->textbuf[x - 1]; for (x = cu->len; x > cu->pos; x--) ef->textbufinfo[x] = ef->textbufinfo[x - 1]; ef->textbuf[cu->pos] = c; ef->textbufinfo[cu->pos] = cu->curinfo; ef->textbufinfo[cu->pos].kern = 0; ef->textbufinfo[cu->pos].mat_nr = obedit->actcol; cu->pos++; cu->len++; ef->textbuf[cu->len] = '\0'; update_string(cu); return 1; } else return 0; } static void text_update_edited(bContext *C, Scene *scene, Object *obedit, int recalc, int mode) { struct Main *bmain = CTX_data_main(C); Curve *cu = obedit->data; EditFont *ef = cu->editfont; cu->curinfo = ef->textbufinfo[cu->pos ? cu->pos - 1 : 0]; if (obedit->totcol > 0) { obedit->actcol = ef->textbufinfo[cu->pos ? cu->pos - 1 : 0].mat_nr; /* since this array is calloc'd, it can be 0 even though we try ensure * (mat_nr > 0) almost everywhere */ if (obedit->actcol < 1) { obedit->actcol = 1; } } if (mode == FO_EDIT) update_string(cu); BKE_vfont_to_curve(bmain, scene, obedit, mode); if (recalc) DAG_id_tag_update(obedit->data, 0); WM_event_add_notifier(C, NC_GEOM | ND_DATA, obedit->data); } /********************** insert lorem operator *********************/ static int insert_lorem_exec(bContext *C, wmOperator *UNUSED(op)) { Object *obedit = CTX_data_edit_object(C); const char *p, *p2; int i; static const char *lastlorem; if (lastlorem) p = lastlorem; else p = ED_lorem; i = rand() / (RAND_MAX / 6) + 4; for (p2 = p; *p2 && i; p2++) { insert_into_textbuf(obedit, *p2); if (*p2 == '.') i--; } lastlorem = p2 + 1; if (strlen(lastlorem) < 5) lastlorem = ED_lorem; insert_into_textbuf(obedit, '\n'); insert_into_textbuf(obedit, '\n'); DAG_id_tag_update(obedit->data, 0); WM_event_add_notifier(C, NC_GEOM | ND_DATA, obedit->data); return OPERATOR_FINISHED; } void FONT_OT_insert_lorem(wmOperatorType *ot) { /* identifiers */ ot->name = "Insert Lorem"; ot->description = "Insert placeholder text"; ot->idname = "FONT_OT_insert_lorem"; /* api callbacks */ ot->exec = insert_lorem_exec; ot->poll = ED_operator_editfont; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } /******************* paste file operator ********************/ /* note this handles both ascii and utf8 unicode, previously * there were 3 functions that did effectively the same thing. */ static int paste_file(bContext *C, ReportList *reports, const char *filename) { Scene *scene = CTX_data_scene(C); Object *obedit = CTX_data_edit_object(C); Curve *cu = obedit->data; EditFont *ef = cu->editfont; FILE *fp; int filelen; char *strp; fp = BLI_fopen(filename, "r"); if (!fp) { if (reports) BKE_reportf(reports, RPT_ERROR, "Failed to open file %s", filename); return OPERATOR_CANCELLED; } fseek(fp, 0L, SEEK_END); filelen = ftell(fp); fseek(fp, 0L, SEEK_SET); strp = MEM_callocN(filelen + 4, "tempstr"); /* fread() instead of read(), because windows read() converts text * to DOS \r\n linebreaks, causing double linebreaks in the 3d text */ filelen = fread(strp, 1, filelen, fp); fclose(fp); strp[filelen] = 0; if (cu->len + filelen < MAXTEXT) { int tmplen; wchar_t *mem = MEM_callocN((sizeof(wchar_t) * filelen) + (4 * sizeof(wchar_t)), "temporary"); tmplen = BLI_strncpy_wchar_from_utf8(mem, strp, filelen + 1); wcscat(ef->textbuf, mem); MEM_freeN(mem); cu->len += tmplen; cu->pos = cu->len; } MEM_freeN(strp); text_update_edited(C, scene, obedit, 1, FO_EDIT); return OPERATOR_FINISHED; } static int paste_file_exec(bContext *C, wmOperator *op) { char *path; int retval; path = RNA_string_get_alloc(op->ptr, "filepath", NULL, 0); retval = paste_file(C, op->reports, path); MEM_freeN(path); return retval; } static int paste_file_invoke(bContext *C, wmOperator *op, wmEvent *UNUSED(event)) { if (RNA_struct_property_is_set(op->ptr, "filepath")) return paste_file_exec(C, op); WM_event_add_fileselect(C, op); return OPERATOR_RUNNING_MODAL; } void FONT_OT_file_paste(wmOperatorType *ot) { /* identifiers */ ot->name = "Paste File"; ot->description = "Paste contents from file"; ot->idname = "FONT_OT_file_paste"; /* api callbacks */ ot->exec = paste_file_exec; ot->invoke = paste_file_invoke; ot->poll = ED_operator_editfont; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; /* properties */ WM_operator_properties_filesel(ot, FOLDERFILE | TEXTFILE, FILE_SPECIAL, FILE_OPENFILE, WM_FILESEL_FILEPATH, FILE_DEFAULTDISPLAY); } /******************* text to object operator ********************/ static void txt_add_object(bContext *C, TextLine *firstline, int totline, float offset[3]) { Scene *scene = CTX_data_scene(C); Curve *cu; Object *obedit; Base *base; struct TextLine *tmp; int nchars = 0, a; float rot[3] = {0.f, 0.f, 0.f}; obedit = BKE_object_add(scene, OB_FONT); base = scene->basact; /* seems to assume view align ? TODO - look into this, could be an operator option */ ED_object_base_init_transform(C, base, NULL, rot); BKE_object_where_is_calc(scene, obedit); obedit->loc[0] += offset[0]; obedit->loc[1] += offset[1]; obedit->loc[2] += offset[2]; cu = obedit->data; cu->vfont = BKE_vfont_builtin_get(); cu->vfont->id.us++; for (tmp = firstline, a = 0; cu->len < MAXTEXT && a < totline; tmp = tmp->next, a++) nchars += strlen(tmp->line) + 1; if (cu->str) MEM_freeN(cu->str); if (cu->strinfo) MEM_freeN(cu->strinfo); cu->str = MEM_callocN(nchars + 4, "str"); cu->strinfo = MEM_callocN((nchars + 4) * sizeof(CharInfo), "strinfo"); cu->str[0] = '\0'; cu->len = 0; cu->pos = 0; for (tmp = firstline, a = 0; cu->len < MAXTEXT && a < totline; tmp = tmp->next, a++) { strcat(cu->str, tmp->line); cu->len += strlen(tmp->line); if (tmp->next) { strcat(cu->str, "\n"); cu->len++; } cu->pos = cu->len; } WM_event_add_notifier(C, NC_OBJECT | NA_ADDED, obedit); } void ED_text_to_object(bContext *C, Text *text, int split_lines) { RegionView3D *rv3d = CTX_wm_region_view3d(C); TextLine *line; float offset[3]; int linenum = 0; if (!text || !text->lines.first) return; if (split_lines) { for (line = text->lines.first; line; line = line->next) { /* skip lines with no text, but still make space for them */ if (line->line[0] == '\0') { linenum++; continue; } /* do the translation */ offset[0] = 0; offset[1] = -linenum; offset[2] = 0; if (rv3d) mul_mat3_m4_v3(rv3d->viewinv, offset); txt_add_object(C, line, 1, offset); linenum++; } } else { offset[0] = 0.0f; offset[1] = 0.0f; offset[2] = 0.0f; txt_add_object(C, text->lines.first, BLI_countlist(&text->lines), offset); } } /********************** utilities ***************************/ static short next_word(Curve *cu) { short s; for (s = cu->pos; ((cu->str[s]) && (cu->str[s] != ' ') && (cu->str[s] != '\n') && (cu->str[s] != 1) && (cu->str[s] != '\r')); s++) { /* pass */ } return cu->str[s] ? (s + 1) : s; } static short prev_word(Curve *cu) { short s; if (cu->pos == 0) return(0); for (s = cu->pos - 2; ((cu->str[s]) && (cu->str[s] != ' ') && (cu->str[s] != '\n') && (cu->str[s] != 1) && (cu->str[s] != '\r')); s--) { /* pass */ } return cu->str[s] ? (s + 1) : s; } static int kill_selection(Object *obedit, int ins) /* 1 == new character */ { Curve *cu = obedit->data; EditFont *ef = cu->editfont; int selend, selstart, direction; int offset = 0; int getfrom; direction = BKE_vfont_select_get(obedit, &selstart, &selend); if (direction) { int size; if (ins) offset = 1; if (cu->pos >= selstart) cu->pos = selstart + offset; if ((direction == -1) && ins) { selstart++; selend++; } getfrom = selend + offset; if (ins == 0) getfrom++; size = (cu->len * sizeof(wchar_t)) - (selstart * sizeof(wchar_t)) + (offset * sizeof(wchar_t)); memmove(ef->textbuf + selstart, ef->textbuf + getfrom, size); memmove(ef->textbufinfo + selstart, ef->textbufinfo + getfrom, ((cu->len - selstart) + offset) * sizeof(CharInfo)); cu->len -= (selend - selstart) + offset; cu->selstart = cu->selend = 0; } return(direction); } /******************* set style operator ********************/ static EnumPropertyItem style_items[] = { {CU_CHINFO_BOLD, "BOLD", 0, "Bold", ""}, {CU_CHINFO_ITALIC, "ITALIC", 0, "Italic", ""}, {CU_CHINFO_UNDERLINE, "UNDERLINE", 0, "Underline", ""}, {CU_CHINFO_SMALLCAPS, "SMALL_CAPS", 0, "Small Caps", ""}, {0, NULL, 0, NULL, NULL} }; static int set_style(bContext *C, const int style, const int clear) { Object *obedit = CTX_data_edit_object(C); Curve *cu = obedit->data; EditFont *ef = cu->editfont; int i, selstart, selend; if (!BKE_vfont_select_get(obedit, &selstart, &selend)) return OPERATOR_CANCELLED; for (i = selstart; i <= selend; i++) { if (clear) ef->textbufinfo[i].flag &= ~style; else ef->textbufinfo[i].flag |= style; } DAG_id_tag_update(obedit->data, 0); WM_event_add_notifier(C, NC_GEOM | ND_DATA, obedit->data); return OPERATOR_FINISHED; } static int set_style_exec(bContext *C, wmOperator *op) { const int style = RNA_enum_get(op->ptr, "style"); const int clear = RNA_boolean_get(op->ptr, "clear"); return set_style(C, style, clear); } void FONT_OT_style_set(wmOperatorType *ot) { /* identifiers */ ot->name = "Set Style"; ot->description = "Set font style"; ot->idname = "FONT_OT_style_set"; /* api callbacks */ ot->exec = set_style_exec; ot->poll = ED_operator_editfont; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; /* properties */ RNA_def_enum(ot->srna, "style", style_items, CU_CHINFO_BOLD, "Style", "Style to set selection to"); RNA_def_boolean(ot->srna, "clear", 0, "Clear", "Clear style rather than setting it"); } /******************* toggle style operator ********************/ static int toggle_style_exec(bContext *C, wmOperator *op) { Object *obedit = CTX_data_edit_object(C); Curve *cu = obedit->data; int style, clear, selstart, selend; if (!BKE_vfont_select_get(obedit, &selstart, &selend)) return OPERATOR_CANCELLED; style = RNA_enum_get(op->ptr, "style"); cu->curinfo.flag ^= style; clear = (cu->curinfo.flag & style) == 0; return set_style(C, style, clear); } void FONT_OT_style_toggle(wmOperatorType *ot) { /* identifiers */ ot->name = "Toggle Style"; ot->description = "Toggle font style"; ot->idname = "FONT_OT_style_toggle"; /* api callbacks */ ot->exec = toggle_style_exec; ot->poll = ED_operator_editfont; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; /* properties */ RNA_def_enum(ot->srna, "style", style_items, CU_CHINFO_BOLD, "Style", "Style to set selection to"); } /******************* copy text operator ********************/ static void copy_selection(Object *obedit) { int selstart, selend; if (BKE_vfont_select_get(obedit, &selstart, &selend)) { Curve *cu = obedit->data; EditFont *ef = cu->editfont; memcpy(ef->copybuf, ef->textbuf + selstart, ((selend - selstart) + 1) * sizeof(wchar_t)); ef->copybuf[(selend - selstart) + 1] = 0; memcpy(ef->copybufinfo, ef->textbufinfo + selstart, ((selend - selstart) + 1) * sizeof(CharInfo)); } } static int copy_text_exec(bContext *C, wmOperator *UNUSED(op)) { Object *obedit = CTX_data_edit_object(C); copy_selection(obedit); return OPERATOR_FINISHED; } void FONT_OT_text_copy(wmOperatorType *ot) { /* identifiers */ ot->name = "Copy Text"; ot->description = "Copy selected text to clipboard"; ot->idname = "FONT_OT_text_copy"; /* api callbacks */ ot->exec = copy_text_exec; ot->poll = ED_operator_editfont; } /******************* cut text operator ********************/ static int cut_text_exec(bContext *C, wmOperator *UNUSED(op)) { Scene *scene = CTX_data_scene(C); Object *obedit = CTX_data_edit_object(C); int selstart, selend; if (!BKE_vfont_select_get(obedit, &selstart, &selend)) return OPERATOR_CANCELLED; copy_selection(obedit); kill_selection(obedit, 0); text_update_edited(C, scene, obedit, 1, FO_EDIT); return OPERATOR_FINISHED; } void FONT_OT_text_cut(wmOperatorType *ot) { /* identifiers */ ot->name = "Cut Text"; ot->description = "Cut selected text to clipboard"; ot->idname = "FONT_OT_text_cut"; /* api callbacks */ ot->exec = cut_text_exec; ot->poll = ED_operator_editfont; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } /******************* paste text operator ********************/ static int paste_selection(Object *obedit, ReportList *reports) { Curve *cu = obedit->data; EditFont *ef = cu->editfont; int len = wcslen(ef->copybuf); /* Verify that the copy buffer => [copy buffer len] + cu->len < MAXTEXT */ if (cu->len + len <= MAXTEXT) { if (len) { int size = (cu->len * sizeof(wchar_t)) - (cu->pos * sizeof(wchar_t)) + sizeof(wchar_t); memmove(ef->textbuf + cu->pos + len, ef->textbuf + cu->pos, size); memcpy(ef->textbuf + cu->pos, ef->copybuf, len * sizeof(wchar_t)); memmove(ef->textbufinfo + cu->pos + len, ef->textbufinfo + cu->pos, (cu->len - cu->pos + 1) * sizeof(CharInfo)); memcpy(ef->textbufinfo + cu->pos, ef->copybufinfo, len * sizeof(CharInfo)); cu->len += len; cu->pos += len; return 1; } } else BKE_report(reports, RPT_WARNING, "Text too long"); return 0; } static int paste_text_exec(bContext *C, wmOperator *op) { Scene *scene = CTX_data_scene(C); Object *obedit = CTX_data_edit_object(C); if (!paste_selection(obedit, op->reports)) return OPERATOR_CANCELLED; text_update_edited(C, scene, obedit, 1, FO_EDIT); return OPERATOR_FINISHED; } void FONT_OT_text_paste(wmOperatorType *ot) { /* identifiers */ ot->name = "Paste Text"; ot->description = "Paste text from clipboard"; ot->idname = "FONT_OT_text_paste"; /* api callbacks */ ot->exec = paste_text_exec; ot->poll = ED_operator_editfont; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } /************************ move operator ************************/ static EnumPropertyItem move_type_items[] = { {LINE_BEGIN, "LINE_BEGIN", 0, "Line Begin", ""}, {LINE_END, "LINE_END", 0, "Line End", ""}, {PREV_CHAR, "PREVIOUS_CHARACTER", 0, "Previous Character", ""}, {NEXT_CHAR, "NEXT_CHARACTER", 0, "Next Character", ""}, {PREV_WORD, "PREVIOUS_WORD", 0, "Previous Word", ""}, {NEXT_WORD, "NEXT_WORD", 0, "Next Word", ""}, {PREV_LINE, "PREVIOUS_LINE", 0, "Previous Line", ""}, {NEXT_LINE, "NEXT_LINE", 0, "Next Line", ""}, {PREV_PAGE, "PREVIOUS_PAGE", 0, "Previous Page", ""}, {NEXT_PAGE, "NEXT_PAGE", 0, "Next Page", ""}, {0, NULL, 0, NULL, NULL}}; static int move_cursor(bContext *C, int type, int select) { Scene *scene = CTX_data_scene(C); Object *obedit = CTX_data_edit_object(C); Curve *cu = obedit->data; EditFont *ef = cu->editfont; int cursmove = -1; switch (type) { case LINE_BEGIN: if ((select) && (cu->selstart == 0)) cu->selstart = cu->selend = cu->pos + 1; while (cu->pos > 0) { if (ef->textbuf[cu->pos - 1] == '\n') break; if (ef->textbufinfo[cu->pos - 1].flag & CU_CHINFO_WRAP) break; cu->pos--; } cursmove = FO_CURS; break; case LINE_END: if ((select) && (cu->selstart == 0)) cu->selstart = cu->selend = cu->pos + 1; while (cu->pos < cu->len) { if (ef->textbuf[cu->pos] == 0) break; if (ef->textbuf[cu->pos] == '\n') break; if (ef->textbufinfo[cu->pos].flag & CU_CHINFO_WRAP) break; cu->pos++; } cursmove = FO_CURS; break; case PREV_WORD: if ((select) && (cu->selstart == 0)) cu->selstart = cu->selend = cu->pos + 1; cu->pos = prev_word(cu); cursmove = FO_CURS; break; case NEXT_WORD: if ((select) && (cu->selstart == 0)) cu->selstart = cu->selend = cu->pos + 1; cu->pos = next_word(cu); cursmove = FO_CURS; break; case PREV_CHAR: if ((select) && (cu->selstart == 0)) cu->selstart = cu->selend = cu->pos + 1; cu->pos--; cursmove = FO_CURS; break; case NEXT_CHAR: if ((select) && (cu->selstart == 0)) cu->selstart = cu->selend = cu->pos + 1; cu->pos++; cursmove = FO_CURS; break; case PREV_LINE: if ((select) && (cu->selstart == 0)) cu->selstart = cu->selend = cu->pos + 1; cursmove = FO_CURSUP; break; case NEXT_LINE: if ((select) && (cu->selstart == 0)) cu->selstart = cu->selend = cu->pos + 1; cursmove = FO_CURSDOWN; break; case PREV_PAGE: if ((select) && (cu->selstart == 0)) cu->selstart = cu->selend = cu->pos + 1; cursmove = FO_PAGEUP; break; case NEXT_PAGE: if ((select) && (cu->selstart == 0)) cu->selstart = cu->selend = cu->pos + 1; cursmove = FO_PAGEDOWN; break; } if (cursmove == -1) return OPERATOR_CANCELLED; if (select == 0) { if (cu->selstart) { struct Main *bmain = CTX_data_main(C); cu->selstart = cu->selend = 0; update_string(cu); BKE_vfont_to_curve(bmain, scene, obedit, FO_SELCHANGE); } } if (cu->pos > cu->len) cu->pos = cu->len; else if (cu->pos >= MAXTEXT) cu->pos = MAXTEXT; else if (cu->pos < 0) cu->pos = 0; text_update_edited(C, scene, obedit, select, cursmove); if (select) cu->selend = cu->pos; return OPERATOR_FINISHED; } static int move_exec(bContext *C, wmOperator *op) { int type = RNA_enum_get(op->ptr, "type"); return move_cursor(C, type, 0); } void FONT_OT_move(wmOperatorType *ot) { /* identifiers */ ot->name = "Move Cursor"; ot->description = "Move cursor to position type"; ot->idname = "FONT_OT_move"; /* api callbacks */ ot->exec = move_exec; ot->poll = ED_operator_editfont; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; /* properties */ RNA_def_enum(ot->srna, "type", move_type_items, LINE_BEGIN, "Type", "Where to move cursor to"); } /******************* move select operator ********************/ static int move_select_exec(bContext *C, wmOperator *op) { int type = RNA_enum_get(op->ptr, "type"); return move_cursor(C, type, 1); } void FONT_OT_move_select(wmOperatorType *ot) { /* identifiers */ ot->name = "Move Select"; ot->description = "Make selection from current cursor position to new cursor position type"; ot->idname = "FONT_OT_move_select"; /* api callbacks */ ot->exec = move_select_exec; ot->poll = ED_operator_editfont; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; /* properties */ RNA_def_enum(ot->srna, "type", move_type_items, LINE_BEGIN, "Type", "Where to move cursor to, to make a selection"); } /************************* change spacing **********************/ static int change_spacing_exec(bContext *C, wmOperator *op) { Scene *scene = CTX_data_scene(C); Object *obedit = CTX_data_edit_object(C); Curve *cu = obedit->data; EditFont *ef = cu->editfont; int kern, delta = RNA_int_get(op->ptr, "delta"); kern = ef->textbufinfo[cu->pos - 1].kern; kern += delta; CLAMP(kern, -20, 20); if (ef->textbufinfo[cu->pos - 1].kern == kern) return OPERATOR_CANCELLED; ef->textbufinfo[cu->pos - 1].kern = kern; text_update_edited(C, scene, obedit, 1, FO_EDIT); return OPERATOR_FINISHED; } void FONT_OT_change_spacing(wmOperatorType *ot) { /* identifiers */ ot->name = "Change Spacing"; ot->description = "Change font spacing"; ot->idname = "FONT_OT_change_spacing"; /* api callbacks */ ot->exec = change_spacing_exec; ot->poll = ED_operator_editfont; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; /* properties */ RNA_def_int(ot->srna, "delta", 1, -20, 20, "Delta", "Amount to decrease or increase character spacing with", -20, 20); } /************************* change character **********************/ static int change_character_exec(bContext *C, wmOperator *op) { Scene *scene = CTX_data_scene(C); Object *obedit = CTX_data_edit_object(C); Curve *cu = obedit->data; EditFont *ef = cu->editfont; int character, delta = RNA_int_get(op->ptr, "delta"); if (cu->pos <= 0) return OPERATOR_CANCELLED; character = ef->textbuf[cu->pos - 1]; character += delta; CLAMP(character, 0, 255); if (character == ef->textbuf[cu->pos - 1]) return OPERATOR_CANCELLED; ef->textbuf[cu->pos - 1] = character; text_update_edited(C, scene, obedit, 1, FO_EDIT); return OPERATOR_FINISHED; } void FONT_OT_change_character(wmOperatorType *ot) { /* identifiers */ ot->name = "Change Character"; ot->description = "Change font character code"; ot->idname = "FONT_OT_change_character"; /* api callbacks */ ot->exec = change_character_exec; ot->poll = ED_operator_editfont; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; /* properties */ RNA_def_int(ot->srna, "delta", 1, -255, 255, "Delta", "Number to increase or decrease character code with", -255, 255); } /******************* line break operator ********************/ static int line_break_exec(bContext *C, wmOperator *op) { Scene *scene = CTX_data_scene(C); Object *obedit = CTX_data_edit_object(C); Curve *cu = obedit->data; EditFont *ef = cu->editfont; const int ctrl = RNA_boolean_get(op->ptr, "ctrl"); if (ctrl) { insert_into_textbuf(obedit, 1); if (ef->textbuf[cu->pos] != '\n') insert_into_textbuf(obedit, '\n'); } else insert_into_textbuf(obedit, '\n'); cu->selstart = cu->selend = 0; text_update_edited(C, scene, obedit, 1, FO_EDIT); return OPERATOR_FINISHED; } void FONT_OT_line_break(wmOperatorType *ot) { /* identifiers */ ot->name = "Line Break"; ot->description = "Insert line break at cursor position"; ot->idname = "FONT_OT_line_break"; /* api callbacks */ ot->exec = line_break_exec; ot->poll = ED_operator_editfont; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; /* properties */ RNA_def_boolean(ot->srna, "ctrl", 0, "Ctrl", ""); // XXX what is this? } /******************* delete operator **********************/ static EnumPropertyItem delete_type_items[] = { {DEL_ALL, "ALL", 0, "All", ""}, {DEL_NEXT_CHAR, "NEXT_CHARACTER", 0, "Next Character", ""}, {DEL_PREV_CHAR, "PREVIOUS_CHARACTER", 0, "Previous Character", ""}, {DEL_SELECTION, "SELECTION", 0, "Selection", ""}, {DEL_NEXT_SEL, "NEXT_OR_SELECTION", 0, "Next or Selection", ""}, {DEL_PREV_SEL, "PREVIOUS_OR_SELECTION", 0, "Previous or Selection", ""}, {0, NULL, 0, NULL, NULL}}; static int delete_exec(bContext *C, wmOperator *op) { Scene *scene = CTX_data_scene(C); Object *obedit = CTX_data_edit_object(C); Curve *cu = obedit->data; EditFont *ef = cu->editfont; int x, selstart, selend, type = RNA_enum_get(op->ptr, "type"); if (cu->len == 0) return OPERATOR_CANCELLED; if (BKE_vfont_select_get(obedit, &selstart, &selend)) { if (type == DEL_NEXT_SEL) type = DEL_SELECTION; else if (type == DEL_PREV_SEL) type = DEL_SELECTION; } else { if (type == DEL_NEXT_SEL) type = DEL_NEXT_CHAR; else if (type == DEL_PREV_SEL) type = DEL_PREV_CHAR; } switch (type) { case DEL_ALL: cu->len = cu->pos = 0; ef->textbuf[0] = 0; break; case DEL_SELECTION: if (!kill_selection(obedit, 0)) return OPERATOR_CANCELLED; break; case DEL_PREV_CHAR: if (cu->pos <= 0) return OPERATOR_CANCELLED; for (x = cu->pos; x <= cu->len; x++) ef->textbuf[x - 1] = ef->textbuf[x]; for (x = cu->pos; x <= cu->len; x++) ef->textbufinfo[x - 1] = ef->textbufinfo[x]; cu->pos--; ef->textbuf[--cu->len] = '\0'; break; case DEL_NEXT_CHAR: if (cu->pos >= cu->len) return OPERATOR_CANCELLED; for (x = cu->pos; x < cu->len; x++) ef->textbuf[x] = ef->textbuf[x + 1]; for (x = cu->pos; x < cu->len; x++) ef->textbufinfo[x] = ef->textbufinfo[x + 1]; ef->textbuf[--cu->len] = '\0'; break; default: return OPERATOR_CANCELLED; } text_update_edited(C, scene, obedit, 1, FO_EDIT); return OPERATOR_FINISHED; } void FONT_OT_delete(wmOperatorType *ot) { /* identifiers */ ot->name = "Delete"; ot->description = "Delete text by cursor position"; ot->idname = "FONT_OT_delete"; /* api callbacks */ ot->exec = delete_exec; ot->poll = ED_operator_editfont; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; /* properties */ RNA_def_enum(ot->srna, "type", delete_type_items, DEL_ALL, "Type", "Which part of the text to delete"); } /*********************** insert text operator *************************/ static int insert_text_exec(bContext *C, wmOperator *op) { Scene *scene = CTX_data_scene(C); Object *obedit = CTX_data_edit_object(C); char *inserted_utf8; wchar_t *inserted_text; int a, len; if (!RNA_struct_property_is_set(op->ptr, "text")) return OPERATOR_CANCELLED; inserted_utf8 = RNA_string_get_alloc(op->ptr, "text", NULL, 0); len = BLI_strlen_utf8(inserted_utf8); inserted_text = MEM_callocN(sizeof(wchar_t) * (len + 1), "FONT_insert_text"); BLI_strncpy_wchar_from_utf8(inserted_text, inserted_utf8, len + 1); for (a = 0; a < len; a++) insert_into_textbuf(obedit, inserted_text[a]); MEM_freeN(inserted_text); MEM_freeN(inserted_utf8); kill_selection(obedit, 1); text_update_edited(C, scene, obedit, 1, FO_EDIT); return OPERATOR_FINISHED; } static int insert_text_invoke(bContext *C, wmOperator *op, wmEvent *evt) { Scene *scene = CTX_data_scene(C); Object *obedit = CTX_data_edit_object(C); Curve *cu = obedit->data; EditFont *ef = cu->editfont; static int accentcode = 0; uintptr_t ascii = evt->ascii; int alt = evt->alt, shift = evt->shift, ctrl = evt->ctrl; int event = evt->type, val = evt->val; wchar_t inserted_text[2] = {0}; if (RNA_struct_property_is_set(op->ptr, "text")) return insert_text_exec(C, op); if (RNA_struct_property_is_set(op->ptr, "accent")) { if (cu->len != 0 && cu->pos > 0) accentcode = 1; return OPERATOR_FINISHED; } /* tab should exit editmode, but we allow it to be typed using modifier keys */ if (event == TABKEY) { if ((alt || ctrl || shift) == 0) return OPERATOR_PASS_THROUGH; else ascii = 9; } if (event == BACKSPACEKEY) { if (alt && cu->len != 0 && cu->pos > 0) accentcode = 1; return OPERATOR_PASS_THROUGH; } if (val && (ascii || evt->utf8_buf[0])) { /* handle case like TAB (== 9) */ if ( (ascii > 31 && ascii < 254 && ascii != 127) || (ascii == 13) || (ascii == 10) || (ascii == 8) || (evt->utf8_buf[0])) { if (accentcode) { if (cu->pos > 0) { inserted_text[0] = findaccent(ef->textbuf[cu->pos - 1], ascii); ef->textbuf[cu->pos - 1] = inserted_text[0]; } accentcode = 0; } else if (evt->utf8_buf[0]) { BLI_strncpy_wchar_from_utf8(inserted_text, evt->utf8_buf, 1); ascii = inserted_text[0]; insert_into_textbuf(obedit, ascii); accentcode = 0; } kill_selection(obedit, 1); text_update_edited(C, scene, obedit, 1, FO_EDIT); } else { inserted_text[0] = ascii; insert_into_textbuf(obedit, ascii); text_update_edited(C, scene, obedit, 1, FO_EDIT); } } else return OPERATOR_PASS_THROUGH; if (inserted_text[0]) { /* store as utf8 in RNA string */ char inserted_utf8[8] = {0}; BLI_strncpy_wchar_as_utf8(inserted_utf8, inserted_text, sizeof(inserted_utf8)); RNA_string_set(op->ptr, "text", inserted_utf8); } /* reset property? */ if (val == 0) accentcode = 0; return OPERATOR_FINISHED; } void FONT_OT_text_insert(wmOperatorType *ot) { /* identifiers */ ot->name = "Insert Text"; ot->description = "Insert text at cursor position"; ot->idname = "FONT_OT_text_insert"; /* api callbacks */ ot->exec = insert_text_exec; ot->invoke = insert_text_invoke; ot->poll = ED_operator_editfont; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; /* properties */ RNA_def_string(ot->srna, "text", "", 0, "Text", "Text to insert at the cursor position"); RNA_def_boolean(ot->srna, "accent", 0, "Accent mode", "Next typed character will strike through previous, for special character input"); } /*********************** textbox add operator *************************/ static int textbox_add_exec(bContext *C, wmOperator *UNUSED(op)) { Object *obedit = CTX_data_active_object(C); Curve *cu = obedit->data; int i; if (cu->totbox < 256) { for (i = cu->totbox; i > cu->actbox; i--) cu->tb[i] = cu->tb[i - 1]; cu->tb[cu->actbox] = cu->tb[cu->actbox - 1]; cu->actbox++; cu->totbox++; } WM_event_add_notifier(C, NC_GEOM | ND_DATA, obedit->data); return OPERATOR_FINISHED; } void FONT_OT_textbox_add(wmOperatorType *ot) { /* identifiers */ ot->name = "Add Textbox"; ot->description = "Add a new text box"; ot->idname = "FONT_OT_textbox_add"; /* api callbacks */ ot->exec = textbox_add_exec; ot->poll = ED_operator_object_active_editable_font; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } /*********************** textbox remove operator *************************/ static int textbox_remove_exec(bContext *C, wmOperator *op) { Object *obedit = CTX_data_active_object(C); Curve *cu = obedit->data; int i; int index = RNA_int_get(op->ptr, "index"); if (cu->totbox > 1) { for (i = index; i < cu->totbox; i++) cu->tb[i] = cu->tb[i + 1]; cu->totbox--; if (cu->actbox >= index) cu->actbox--; } WM_event_add_notifier(C, NC_GEOM | ND_DATA, obedit->data); return OPERATOR_FINISHED; } void FONT_OT_textbox_remove(wmOperatorType *ot) { /* identifiers */ ot->name = "Remove Textbox"; ot->description = "Remove the textbox"; ot->idname = "FONT_OT_textbox_remove"; /* api callbacks */ ot->exec = textbox_remove_exec; ot->poll = ED_operator_object_active_editable_font; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; RNA_def_int(ot->srna, "index", 0, 0, INT_MAX, "Index", "The current text box", 0, INT_MAX); } /***************** editmode enter/exit ********************/ void make_editText(Object *obedit) { Curve *cu = obedit->data; EditFont *ef = cu->editfont; if (ef == NULL) { ef = cu->editfont = MEM_callocN(sizeof(EditFont), "editfont"); ef->textbuf = MEM_callocN((MAXTEXT + 4) * sizeof(wchar_t), "texteditbuf"); ef->textbufinfo = MEM_callocN((MAXTEXT + 4) * sizeof(CharInfo), "texteditbufinfo"); ef->copybuf = MEM_callocN((MAXTEXT + 4) * sizeof(wchar_t), "texteditcopybuf"); ef->copybufinfo = MEM_callocN((MAXTEXT + 4) * sizeof(CharInfo), "texteditcopybufinfo"); ef->oldstr = MEM_callocN((MAXTEXT + 4) * sizeof(wchar_t), "oldstrbuf"); ef->oldstrinfo = MEM_callocN((MAXTEXT + 4) * sizeof(CharInfo), "oldstrbuf"); } /* Convert the original text to wchar_t */ BLI_strncpy_wchar_from_utf8(ef->textbuf, cu->str, MAXTEXT + 4); /* length is bogus */ wcscpy(ef->oldstr, ef->textbuf); cu->len = wcslen(ef->textbuf); memcpy(ef->textbufinfo, cu->strinfo, (cu->len) * sizeof(CharInfo)); memcpy(ef->oldstrinfo, cu->strinfo, (cu->len) * sizeof(CharInfo)); if (cu->pos > cu->len) cu->pos = cu->len; if (cu->pos) cu->curinfo = ef->textbufinfo[cu->pos - 1]; else cu->curinfo = ef->textbufinfo[0]; /* Convert to UTF-8 */ update_string(cu); } void load_editText(Object *obedit) { Curve *cu = obedit->data; EditFont *ef = cu->editfont; MEM_freeN(ef->oldstr); ef->oldstr = NULL; MEM_freeN(ef->oldstrinfo); ef->oldstrinfo = NULL; update_string(cu); if (cu->strinfo) MEM_freeN(cu->strinfo); cu->strinfo = MEM_callocN((cu->len + 4) * sizeof(CharInfo), "texteditinfo"); memcpy(cu->strinfo, ef->textbufinfo, (cu->len) * sizeof(CharInfo)); cu->len = strlen(cu->str); /* this memory system is weak... */ if (cu->selboxes) { MEM_freeN(cu->selboxes); cu->selboxes = NULL; } } void free_editText(Object *obedit) { BKE_curve_editfont_free((Curve *)obedit->data); } /********************** set case operator *********************/ static EnumPropertyItem case_items[] = { {CASE_LOWER, "LOWER", 0, "Lower", ""}, {CASE_UPPER, "UPPER", 0, "Upper", ""}, {0, NULL, 0, NULL, NULL}}; static int set_case(bContext *C, int ccase) { Scene *scene = CTX_data_scene(C); Object *obedit = CTX_data_edit_object(C); Curve *cu = obedit->data; EditFont *ef = cu->editfont; wchar_t *str; int len; len = wcslen(ef->textbuf); str = ef->textbuf; while (len) { if (*str >= 'a' && *str <= 'z') *str -= 32; len--; str++; } if (ccase == CASE_LOWER) { len = wcslen(ef->textbuf); str = ef->textbuf; while (len) { if (*str >= 'A' && *str <= 'Z') { *str += 32; } len--; str++; } } text_update_edited(C, scene, obedit, 1, FO_EDIT); return OPERATOR_FINISHED; } static int set_case_exec(bContext *C, wmOperator *op) { return set_case(C, RNA_enum_get(op->ptr, "case")); } void FONT_OT_case_set(wmOperatorType *ot) { /* identifiers */ ot->name = "Set Case"; ot->description = "Set font case"; ot->idname = "FONT_OT_case_set"; /* api callbacks */ ot->exec = set_case_exec; ot->poll = ED_operator_editfont; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; /* properties */ RNA_def_enum(ot->srna, "case", case_items, CASE_LOWER, "Case", "Lower or upper case"); } /********************** toggle case operator *********************/ static int toggle_case_exec(bContext *C, wmOperator *UNUSED(op)) { Object *obedit = CTX_data_edit_object(C); Curve *cu = obedit->data; EditFont *ef = cu->editfont; wchar_t *str; int len, ccase = CASE_UPPER; len = wcslen(ef->textbuf); str = ef->textbuf; while (len) { if (*str >= 'a' && *str <= 'z') { ccase = CASE_LOWER; break; } len--; str++; } return set_case(C, ccase); } void FONT_OT_case_toggle(wmOperatorType *ot) { /* identifiers */ ot->name = "Toggle Case"; ot->description = "Toggle font case"; ot->idname = "FONT_OT_case_toggle"; /* api callbacks */ ot->exec = toggle_case_exec; ot->poll = ED_operator_editfont; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } /* **************** Open Font ************** */ static void font_ui_template_init(bContext *C, wmOperator *op) { PropertyPointerRNA *pprop; op->customdata = pprop = MEM_callocN(sizeof(PropertyPointerRNA), "OpenPropertyPointerRNA"); uiIDContextProperty(C, &pprop->ptr, &pprop->prop); } static int font_open_cancel(bContext *UNUSED(C), wmOperator *op) { MEM_freeN(op->customdata); op->customdata = NULL; return OPERATOR_CANCELLED; } static int font_open_exec(bContext *C, wmOperator *op) { struct Main *bmain = CTX_data_main(C); VFont *font; PropertyPointerRNA *pprop; PointerRNA idptr; char filepath[FILE_MAX]; RNA_string_get(op->ptr, "filepath", filepath); font = BKE_vfont_load(bmain, filepath); if (!font) { if (op->customdata) MEM_freeN(op->customdata); return OPERATOR_CANCELLED; } if (!op->customdata) font_ui_template_init(C, op); /* hook into UI */ pprop = op->customdata; if (pprop->prop) { /* when creating new ID blocks, use is already 1, but RNA * pointer se also increases user, so this compensates it */ font->id.us--; RNA_id_pointer_create(&font->id, &idptr); RNA_property_pointer_set(&pprop->ptr, pprop->prop, idptr); RNA_property_update(C, &pprop->ptr, pprop->prop); } MEM_freeN(op->customdata); return OPERATOR_FINISHED; } static int open_invoke(bContext *C, wmOperator *op, wmEvent *UNUSED(event)) { VFont *vfont = NULL; char *path; PointerRNA idptr; PropertyPointerRNA *pprop; font_ui_template_init(C, op); /* hook into UI */ pprop = op->customdata; if (pprop->prop) { idptr = RNA_property_pointer_get((PointerRNA *)pprop, pprop->prop); vfont = idptr.id.data; } path = (vfont && !BKE_vfont_is_builtin(vfont)) ? vfont->name : U.fontdir; if (RNA_struct_property_is_set(op->ptr, "filepath")) return font_open_exec(C, op); RNA_string_set(op->ptr, "filepath", path); WM_event_add_fileselect(C, op); return OPERATOR_RUNNING_MODAL; } void FONT_OT_open(wmOperatorType *ot) { /* identifiers */ ot->name = "Open Font"; ot->idname = "FONT_OT_open"; ot->description = "Load a new font from a file"; /* api callbacks */ ot->exec = font_open_exec; ot->invoke = open_invoke; ot->cancel = font_open_cancel; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; /* properties */ WM_operator_properties_filesel(ot, FOLDERFILE | FTFONTFILE, FILE_SPECIAL, FILE_OPENFILE, WM_FILESEL_FILEPATH | WM_FILESEL_RELPATH, FILE_DEFAULTDISPLAY); } /******************* delete operator *********************/ static int font_unlink_exec(bContext *C, wmOperator *op) { VFont *builtin_font; PointerRNA idptr; PropertyPointerRNA pprop; uiIDContextProperty(C, &pprop.ptr, &pprop.prop); if (pprop.prop == NULL) { BKE_report(op->reports, RPT_ERROR, "Incorrect context for running font unlink"); return OPERATOR_CANCELLED; } builtin_font = BKE_vfont_builtin_get(); RNA_id_pointer_create(&builtin_font->id, &idptr); RNA_property_pointer_set(&pprop.ptr, pprop.prop, idptr); RNA_property_update(C, &pprop.ptr, pprop.prop); return OPERATOR_FINISHED; } void FONT_OT_unlink(wmOperatorType *ot) { /* identifiers */ ot->name = "Unlink"; ot->idname = "FONT_OT_unlink"; ot->description = "Unlink active font data block"; /* api callbacks */ ot->exec = font_unlink_exec; } /* **************** undo for font object ************** */ static void undoFont_to_editFont(void *strv, void *ecu, void *UNUSED(obdata)) { Curve *cu = (Curve *)ecu; EditFont *ef = cu->editfont; char *str = strv; cu->pos = *((short *)str); cu->len = *((short *)(str + 2)); memcpy(ef->textbuf, str + 4, (cu->len + 1) * sizeof(wchar_t)); memcpy(ef->textbufinfo, str + 4 + (cu->len + 1) * sizeof(wchar_t), cu->len * sizeof(CharInfo)); cu->selstart = cu->selend = 0; update_string(cu); } static void *editFont_to_undoFont(void *ecu, void *UNUSED(obdata)) { Curve *cu = (Curve *)ecu; EditFont *ef = cu->editfont; char *str; /* The undo buffer includes [MAXTEXT+6]=actual string and [MAXTEXT+4]*sizeof(CharInfo)=charinfo */ str = MEM_callocN((MAXTEXT + 6) * sizeof(wchar_t) + (MAXTEXT + 4) * sizeof(CharInfo), "string undo"); /* Copy the string and string information */ memcpy(str + 4, ef->textbuf, (cu->len + 1) * sizeof(wchar_t)); memcpy(str + 4 + (cu->len + 1) * sizeof(wchar_t), ef->textbufinfo, cu->len * sizeof(CharInfo)); *((short *)str) = cu->pos; *((short *)(str + 2)) = cu->len; return str; } static void free_undoFont(void *strv) { MEM_freeN(strv); } static void *get_undoFont(bContext *C) { Object *obedit = CTX_data_edit_object(C); if (obedit && obedit->type == OB_FONT) { return obedit->data; } return NULL; } /* and this is all the undo system needs to know */ void undo_push_font(bContext *C, const char *name) { undo_editmode_push(C, name, get_undoFont, free_undoFont, undoFont_to_editFont, editFont_to_undoFont, NULL); }