/* * $Id$ * * ***** 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 * 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_draw.c * \ingroup sptext */ #include #include #include #include #include "MEM_guardedalloc.h" #include "BLF_api.h" #include "BLI_blenlib.h" #include "BLI_utildefines.h" #include "DNA_text_types.h" #include "DNA_space_types.h" #include "DNA_screen_types.h" #include "DNA_userdef_types.h" #include "BKE_context.h" #include "BKE_suggestions.h" #include "BKE_text.h" #include "BIF_gl.h" #include "ED_datafiles.h" #include "UI_interface.h" #include "UI_resources.h" #include "text_intern.h" /******************** text font drawing ******************/ // XXX, fixme #define mono blf_mono_font static void text_font_begin(SpaceText *st) { BLF_size(mono, st->lheight, 72); } static void text_font_end(SpaceText *UNUSED(st)) { } static int text_font_draw(SpaceText *UNUSED(st), int x, int y, char *str) { BLF_position(mono, x, y, 0); BLF_draw(mono, str, 65535); /* XXX, use real length */ return BLF_width(mono, str); } static int text_font_draw_character(SpaceText *st, int x, int y, char c) { char str[2]; str[0]= c; str[1]= '\0'; BLF_position(mono, x, y, 0); BLF_draw(mono, str, 1); return st->cwidth; } int text_font_width(SpaceText *UNUSED(st), const char *str) { return BLF_width(mono, str); } /****************** flatten string **********************/ static void flatten_string_append(FlattenString *fs, char c, int accum) { if(fs->pos>=fs->len && fs->pos>=sizeof(fs->fixedbuf)-1) { char *nbuf; int *naccum; if(fs->len) fs->len*= 2; else fs->len= sizeof(fs->fixedbuf) * 2; nbuf= MEM_callocN(sizeof(*fs->buf)*fs->len, "fs->buf"); naccum= MEM_callocN(sizeof(*fs->accum)*fs->len, "fs->accum"); memcpy(nbuf, fs->buf, fs->pos * sizeof(*fs->buf)); memcpy(naccum, fs->accum, fs->pos * sizeof(*fs->accum)); if(fs->buf != fs->fixedbuf) { MEM_freeN(fs->buf); MEM_freeN(fs->accum); } fs->buf= nbuf; fs->accum= naccum; } fs->buf[fs->pos]= c; fs->accum[fs->pos]= accum; fs->pos++; } int flatten_string(SpaceText *st, FlattenString *fs, const char *in) { int r = 0, i = 0; memset(fs, 0, sizeof(FlattenString)); fs->buf= fs->fixedbuf; fs->accum= fs->fixedaccum; for(r=0, i=0; *in; r++, in++) { if(*in=='\t') { if(fs->pos && *(in-1)=='\t') i= st->tabnumber; else if(st->tabnumber > 0) i= st->tabnumber - (fs->pos%st->tabnumber); while(i--) flatten_string_append(fs, ' ', r); } else flatten_string_append(fs, *in, r); } return fs->pos; } void flatten_string_free(FlattenString *fs) { if(fs->buf != fs->fixedbuf) MEM_freeN(fs->buf); if(fs->accum != fs->fixedaccum) MEM_freeN(fs->accum); } /* Checks the specified source string for a Python built-in function name. This name must start at the beginning of the source string and must be followed by a non-identifier (see text_check_identifier(char)) or null character. If a built-in function is found, the length of the matching name is returned. Otherwise, -1 is returned. */ static int find_builtinfunc(char *string) { int a, i; char builtinfuncs[][9] = {"and", "as", "assert", "break", "class", "continue", "def", "del", "elif", "else", "except", "exec", "finally", "for", "from", "global", "if", "import", "in", "is", "lambda", "not", "or", "pass", "print", "raise", "return", "try", "while", "yield", "with"}; for(a=0; a < sizeof(builtinfuncs)/sizeof(builtinfuncs[0]); a++) { i = 0; while(1) { /* If we hit the end of a keyword... (eg. "def") */ if(builtinfuncs[a][i]=='\0') { /* If we still have identifier chars in the source (eg. "definate") */ if(text_check_identifier(string[i])) i = -1; /* No match */ break; /* Next keyword if no match, otherwise we're done */ /* If chars mismatch, move on to next keyword */ } else if(string[i]!=builtinfuncs[a][i]) { i = -1; break; /* Break inner loop, start next keyword */ } i++; } if(i>0) break; /* If we have a match, we're done */ } return i; } /* Checks the specified source string for a Python special name. This name must start at the beginning of the source string and must be followed by a non- identifier (see text_check_identifier(char)) or null character. If a special name is found, the length of the matching name is returned. Otherwise, -1 is returned. */ static int find_specialvar(char *string) { int i = 0; /* Check for "def" */ if(string[0]=='d' && string[1]=='e' && string[2]=='f') i = 3; /* Check for "class" */ else if(string[0]=='c' && string[1]=='l' && string[2]=='a' && string[3]=='s' && string[4]=='s') i = 5; /* If next source char is an identifier (eg. 'i' in "definate") no match */ if(i==0 || text_check_identifier(string[i])) return -1; return i; } static int find_decorator(char *string) { if(string[0] == '@') { int i = 1; while(text_check_identifier(string[i])) { i++; } return i; } return -1; } static int find_bool(char *string) { int i = 0; /* Check for "False" */ if(string[0]=='F' && string[1]=='a' && string[2]=='l' && string[3]=='s' && string[4]=='e') i = 5; /* Check for "True" */ else if(string[0]=='T' && string[1]=='r' && string[2]=='u' && string[3]=='e') i = 4; /* Check for "None" */ else if(string[0]=='N' && string[1]=='o' && string[2]=='n' && string[3]=='e') i = 4; /* If next source char is an identifier (eg. 'i' in "definate") no match */ if(i==0 || text_check_identifier(string[i])) return -1; return i; } /* Ensures the format string for the given line is long enough, reallocating as needed. Allocation is done here, alone, to ensure consistency. */ static int text_check_format_len(TextLine *line, unsigned int len) { if(line->format) { if(strlen(line->format) < len) { MEM_freeN(line->format); line->format = MEM_mallocN(len+2, "SyntaxFormat"); if(!line->format) return 0; } } else { line->format = MEM_mallocN(len+2, "SyntaxFormat"); if(!line->format) return 0; } return 1; } /* Formats the specified line. If do_next is set, the process will move on to the succeeding line if it is affected (eg. multiline strings). Format strings may contain any of the following characters: '_' Whitespace '#' Comment text '!' Punctuation and other symbols 'n' Numerals 'l' String letters 'v' Special variables (class, def) 'b' Built-in names (print, for, etc.) 'q' Other text (identifiers, etc.) It is terminated with a null-terminator '\0' followed by a continuation flag indicating whether the line is part of a multi-line string. */ static void txt_format_line(SpaceText *st, TextLine *line, int do_next) { FlattenString fs; char *str, *fmt, orig, cont, find, prev = ' '; int len, i; /* Get continuation from previous line */ if(line->prev && line->prev->format != NULL) { fmt= line->prev->format; cont = fmt[strlen(fmt)+1]; /* Just after the null-terminator */ } else cont = 0; /* Get original continuation from this line */ if(line->format != NULL) { fmt= line->format; orig = fmt[strlen(fmt)+1]; /* Just after the null-terminator */ } else orig = 0xFF; flatten_string(st, &fs, line->line); str = fs.buf; len = strlen(str); if(!text_check_format_len(line, len)) { flatten_string_free(&fs); return; } fmt = line->format; while(*str) { /* Handle escape sequences by skipping both \ and next char */ if(*str == '\\') { *fmt = prev; fmt++; str++; if(*str == '\0') break; *fmt = prev; fmt++; str++; continue; } /* Handle continuations */ else if(cont) { /* Triple strings ("""...""" or '''...''') */ if(cont & TXT_TRISTR) { find = (cont & TXT_DBLQUOTSTR) ? '"' : '\''; if(*str==find && *(str+1)==find && *(str+2)==find) { *fmt = 'l'; fmt++; str++; *fmt = 'l'; fmt++; str++; cont = 0; } /* Handle other strings */ } else { find = (cont & TXT_DBLQUOTSTR) ? '"' : '\''; if(*str == find) cont = 0; } *fmt = 'l'; } /* Not in a string... */ else { /* Deal with comments first */ if(prev == '#' || *str == '#') *fmt = '#'; /* Strings */ else if(*str == '"' || *str == '\'') { find = *str; cont = (*str== '"') ? TXT_DBLQUOTSTR : TXT_SNGQUOTSTR; if(*(str+1) == find && *(str+2) == find) { *fmt = 'l'; fmt++; str++; *fmt = 'l'; fmt++; str++; cont |= TXT_TRISTR; } *fmt = 'l'; } /* Whitespace (all ws. has been converted to spaces) */ else if(*str == ' ') *fmt = '_'; /* Numbers (digits not part of an identifier and periods followed by digits) */ else if((prev != 'q' && text_check_digit(*str)) || (*str == '.' && text_check_digit(*(str+1)))) *fmt = 'n'; /* Booleans */ else if(prev != 'q' && (i=find_bool(str)) != -1) if(i>0) { while(i>1) { *fmt = 'n'; fmt++; str++; i--; } *fmt = 'n'; } else *fmt = 'q'; /* Punctuation */ else if(text_check_delim(*str)) *fmt = '!'; /* Identifiers and other text (no previous ws. or delims. so text continues) */ else if(prev == 'q') *fmt = 'q'; /* Not ws, a digit, punct, or continuing text. Must be new, check for special words */ else { /* Special vars(v) or built-in keywords(b) */ if((i=find_specialvar(str)) != -1) prev = 'v'; else if((i=find_builtinfunc(str)) != -1) prev = 'b'; else if((i=find_decorator(str)) != -1) prev = 'v'; /* could have a new color for this */ if(i>0) { while(i>1) { *fmt = prev; fmt++; str++; i--; } *fmt = prev; } else *fmt = 'q'; } } prev = *fmt; fmt++; str++; } /* Terminate and add continuation char */ *fmt = '\0'; fmt++; *fmt = cont; /* Debugging */ //print_format(st, line); /* If continuation has changed and we're allowed, process the next line */ if(cont!=orig && do_next && line->next) { txt_format_line(st, line->next, do_next); } flatten_string_free(&fs); } #if 0 /* Formats every line of the current text */ static void txt_format_text(SpaceText *st) { TextLine *linep; if(!st->text) return; for(linep=st->text->lines.first; linep; linep=linep->next) txt_format_line(st, linep, 0); } #endif /* Sets the current drawing color based on the format character specified */ static void format_draw_color(char formatchar) { switch (formatchar) { case '_': /* Whitespace */ break; case '!': /* Symbols */ UI_ThemeColorBlend(TH_TEXT, TH_BACK, 0.5f); break; case '#': /* Comments */ UI_ThemeColor(TH_SYNTAX_C); break; case 'n': /* Numerals */ UI_ThemeColor(TH_SYNTAX_N); break; case 'l': /* Strings */ UI_ThemeColor(TH_SYNTAX_L); break; case 'v': /* Specials: class, def */ UI_ThemeColor(TH_SYNTAX_V); break; case 'b': /* Keywords: for, print, etc. */ UI_ThemeColor(TH_SYNTAX_B); break; case 'q': /* Other text (identifiers) */ default: UI_ThemeColor(TH_TEXT); break; } } /************************** draw text *****************************/ /***********************/ /* Notes on word-wrap -- All word-wrap functions follow the algorithm below to maintain consistency. line The line to wrap (tabs converted to spaces) view_width The maximum number of characters displayable in the region This equals region_width/font_width for the region wrap_chars Characters that allow wrapping. This equals [' ', '\t', '-'] def wrap(line, view_width, wrap_chars): draw_start = 0 draw_end = view_width pos = 0 for c in line: if pos-draw_start >= view_width: print line[draw_start:draw_end] draw_start = draw_end draw_end += view_width elif c in wrap_chars: draw_end = pos+1 pos += 1 print line[draw_start:] */ /***********************/ int wrap_width(SpaceText *st, ARegion *ar) { int winx= ar->winx - TXT_SCROLL_WIDTH; int x, max; x= st->showlinenrs ? TXT_OFFSET + TEXTXLOC : TXT_OFFSET; max= st->cwidth ? (winx-x)/st->cwidth : 0; return max>8 ? max : 8; } /* Sets (offl, offc) for transforming (line, curs) to its wrapped position */ void wrap_offset(SpaceText *st, ARegion *ar, TextLine *linein, int cursin, int *offl, int *offc) { Text *text; TextLine *linep; int i, j, start, end, chars, max, chop; char ch; *offl= *offc= 0; if(!st->text) return; if(!st->wordwrap) return; text= st->text; /* Move pointer to first visible line (top) */ linep= text->lines.first; i= st->top; while(i>0 && linep) { int lines= text_get_visible_lines(st, ar, linep->line); /* Line before top */ if(linep == linein) { if(lines <= i) /* no visible part of line */ return; } if (i-lines<0) { break; } else { linep= linep->next; (*offl)+= lines-1; i-= lines; } } max= wrap_width(st, ar); while(linep) { start= 0; end= max; chop= 1; chars= 0; *offc= 0; for(i=0, j=0; linep->line[j]!='\0'; j++) { /* Mimic replacement of tabs */ ch= linep->line[j]; if(ch=='\t') { chars= st->tabnumber-i%st->tabnumber; if(linep==linein && i=max) { if(chop && linep==linein && i >= cursin) { if (i==cursin) { (*offl)++; *offc -= end-start; } return; } (*offl)++; *offc -= end-start; start= end; end += max; chop= 1; } else if(ch==' ' || ch=='-') { end = i+1; chop= 0; if(linep==linein && i >= cursin) return; } i++; } } if(linep==linein) break; linep= linep->next; } } void wrap_offset_in_line(SpaceText *st, ARegion *ar, TextLine *linein, int cursin, int *offl, int *offc) { int i, j, start, end, chars, max, chop; char ch; *offl= *offc= 0; if(!st->text) return; if(!st->wordwrap) return; max= wrap_width(st, ar); start= 0; end= max; chop= 1; *offc= 0; for(i=0, j=0; linein->line[j]!='\0'; j++) { /* Mimic replacement of tabs */ ch= linein->line[j]; if(ch=='\t') { chars= st->tabnumber-i%st->tabnumber; if(i=max) { if(chop && i >= cursin) { if (i==cursin) { (*offl)++; *offc -= end-start; } return; } (*offl)++; *offc -= end-start; start= end; end += max; chop= 1; } else if(ch==' ' || ch=='-') { end = i+1; chop= 0; if(i >= cursin) return; } i++; } } } int text_get_char_pos(SpaceText *st, const char *line, int cur) { int a=0, i; for(i=0; itabnumber-a%st->tabnumber; else a++; } return a; } static int text_draw_wrapped(SpaceText *st, char *str, int x, int y, int w, char *format, int skip) { FlattenString fs; int basex, i, a, len, start, end, max, lines; len= flatten_string(st, &fs, str); str= fs.buf; max= w/st->cwidth; if(max<8) max= 8; basex= x; lines= 1; start= 0; end= max; for(i=0; i= max) { /* skip hidden part of line */ if(skip) { skip--; start= end; end += max; continue; } /* Draw the visible portion of text on the overshot line */ for(a=start; ashowsyntax && format) format_draw_color(format[a]); x += text_font_draw_character(st, x, y, str[a]); } y -= st->lheight; x= basex; lines++; start= end; end += max; if(y<=0) break; } else if(str[i]==' ' || str[i]=='-') { end = i+1; } } /* Draw the remaining text */ for(a=start; a 0; a++) { if(st->showsyntax && format) format_draw_color(format[a]); x += text_font_draw_character(st, x, y, str[a]); } flatten_string_free(&fs); return lines; } static int text_draw(SpaceText *st, char *str, int cshift, int maxwidth, int draw, int x, int y, char *format) { FlattenString fs; int r=0, w= 0, amount; int *acc; char *in; w= flatten_string(st, &fs, str); if(w < cshift) { flatten_string_free(&fs); return 0; /* String is shorter than shift */ } in= fs.buf+cshift; acc= fs.accum+cshift; w= w-cshift; if(draw) { if(st->showsyntax && format) { int a; format = format+cshift; amount = strlen(in); if(maxwidth) amount= MIN2(amount, maxwidth); for(a = 0; a < amount; a++) { format_draw_color(format[a]); x += text_font_draw_character(st, x, y, in[a]); } } else { amount = strlen(in); if(maxwidth) amount= MIN2(amount, maxwidth); in[amount]= 0; text_font_draw(st, x, y, in); } } else { while(w-- && *acc++ < maxwidth) r+= st->cwidth; } flatten_string_free(&fs); if(cshift && r==0) return 0; else if(st->showlinenrs) return r+TXT_OFFSET+TEXTXLOC; else return r+TXT_OFFSET; } /************************ cache utilities *****************************/ typedef struct DrawCache { int *line_height; int total_lines, nlines; /* this is needed to check cache relevance */ int winx, wordwrap, showlinenrs, tabnumber; short lheight; char cwidth; char text_id[MAX_ID_NAME]; /* for partial lines recalculation */ short update_flag; int valid_head, valid_tail; /* amount of unchanged lines */ } DrawCache; static void text_drawcache_init(SpaceText *st) { DrawCache *drawcache= MEM_callocN(sizeof (DrawCache), "text draw cache"); drawcache->winx= -1; drawcache->nlines= BLI_countlist(&st->text->lines); drawcache->text_id[0]= '\0'; st->drawcache= drawcache; } static void text_update_drawcache(SpaceText *st, ARegion *ar) { DrawCache *drawcache; int full_update= 0, nlines= 0; Text *txt= st->text; if(!st->drawcache) text_drawcache_init(st); text_update_character_width(st); drawcache= (DrawCache *)st->drawcache; nlines= drawcache->nlines; /* check if full cache update is needed */ full_update|= drawcache->winx != ar->winx; /* area was resized */ full_update|= drawcache->wordwrap != st->wordwrap; /* word-wrapping option was toggled */ full_update|= drawcache->showlinenrs != st->showlinenrs; /* word-wrapping option was toggled */ full_update|= drawcache->tabnumber != st->tabnumber; /* word-wrapping option was toggled */ full_update|= drawcache->lheight != st->lheight; /* word-wrapping option was toggled */ full_update|= drawcache->cwidth != st->cwidth; /* word-wrapping option was toggled */ full_update|= strncmp(drawcache->text_id, txt->id.name, MAX_ID_NAME); /* text datablock was changed */ if(st->wordwrap) { /* update line heights */ if(full_update || !drawcache->line_height) { drawcache->valid_head = 0; drawcache->valid_tail = 0; drawcache->update_flag = 1; } if(drawcache->update_flag) { TextLine *line= st->text->lines.first; int lineno= 0, size, lines_count; int *fp= drawcache->line_height, *new_tail, *old_tail; nlines= BLI_countlist(&txt->lines); size= sizeof(int)*nlines; if(fp) fp= MEM_reallocN(fp, size); else fp= MEM_callocN(size, "text drawcache line_height"); drawcache->valid_tail= drawcache->valid_head= 0; old_tail= fp + drawcache->nlines - drawcache->valid_tail; new_tail= fp + nlines - drawcache->valid_tail; memmove(new_tail, old_tail, drawcache->valid_tail); drawcache->total_lines= 0; if(st->showlinenrs) st->linenrs_tot= (int)floor(log10((float)nlines)) + 1; while(line) { if(drawcache->valid_head) { /* we're inside valid head lines */ lines_count= fp[lineno]; drawcache->valid_head--; } else if (lineno > new_tail - fp) { /* we-re inside valid tail lines */ lines_count= fp[lineno]; } else { lines_count= text_get_visible_lines(st, ar, line->line); } fp[lineno]= lines_count; line= line->next; lineno++; drawcache->total_lines+= lines_count; } drawcache->line_height= fp; } } else { if(drawcache->line_height) { MEM_freeN(drawcache->line_height); drawcache->line_height= NULL; } if(full_update || drawcache->update_flag) { nlines= BLI_countlist(&txt->lines); if(st->showlinenrs) st->linenrs_tot= (int)floor(log10((float)nlines)) + 1; } drawcache->total_lines= nlines; } drawcache->nlines= nlines; /* store settings */ drawcache->winx = ar->winx; drawcache->wordwrap = st->wordwrap; drawcache->lheight = st->lheight; drawcache->cwidth = st->cwidth; drawcache->showlinenrs = st->showlinenrs; drawcache->tabnumber = st->tabnumber; strncpy(drawcache->text_id, txt->id.name, MAX_ID_NAME); /* clear update flag */ drawcache->update_flag = 0; drawcache->valid_head = 0; drawcache->valid_tail = 0; } void text_drawcache_tag_update(SpaceText *st, int full) { DrawCache *drawcache= (DrawCache *)st->drawcache; if(drawcache) { Text *txt= st->text; if(drawcache->update_flag) { /* happens when tagging update from space listener */ /* should do nothing to prevent locally tagged cache be fully recalculated */ return; } if(!full) { int sellno= BLI_findindex(&txt->lines, txt->sell); int curlno= BLI_findindex(&txt->lines, txt->curl); if(curlno < sellno) { drawcache->valid_head= curlno; drawcache->valid_tail= drawcache->nlines - sellno - 1; } else { drawcache->valid_head= sellno; drawcache->valid_tail= drawcache->nlines - curlno - 1; } /* quick cache recalculation is also used in delete operator, which could merge lines which are adjusent to current selection lines expand recalculate area to this lines */ if(drawcache->valid_head>0) drawcache->valid_head--; if(drawcache->valid_tail>0) drawcache->valid_tail--; } else { drawcache->valid_head= 0; drawcache->valid_tail= 0; } drawcache->update_flag= 1; } } void text_free_caches(SpaceText *st) { DrawCache *drawcache= (DrawCache *)st->drawcache; if(drawcache) { if(drawcache->line_height) MEM_freeN(drawcache->line_height); MEM_freeN(drawcache); } } /************************ word-wrap utilities *****************************/ /* cache should be updated in caller */ static int text_get_visible_lines_no(SpaceText *st, int lineno) { DrawCache *drawcache= (DrawCache *)st->drawcache; return drawcache->line_height[lineno]; } int text_get_visible_lines(SpaceText *st, ARegion *ar, const char *str) { int i, j, start, end, max, lines, chars; char ch; max= wrap_width(st, ar); lines= 1; start= 0; end= max; for(i= 0, j= 0; str[j] != '\0'; j++) { /* Mimic replacement of tabs */ ch= str[j]; if(ch=='\t') { chars= st->tabnumber-i%st->tabnumber; ch= ' '; } else chars= 1; while(chars--) { if(i-start >= max) { lines++; start= end; end += max; } else if(ch==' ' || ch=='-') { end= i+1; } i++; } } return lines; } int text_get_span_wrap(SpaceText *st, ARegion *ar, TextLine *from, TextLine *to) { if(st->wordwrap) { int ret=0; TextLine *tmp= from; /* Look forwards */ while (tmp) { if (tmp == to) return ret; ret+= text_get_visible_lines(st, ar, tmp->line); tmp= tmp->next; } return ret; } else return txt_get_span(from, to); } int text_get_total_lines(SpaceText *st, ARegion *ar) { DrawCache *drawcache; text_update_drawcache(st, ar); drawcache= (DrawCache *)st->drawcache; return drawcache->total_lines; } /* Move pointer to first visible line (top) */ static TextLine *first_visible_line(SpaceText *st, ARegion *ar, int *wrap_top) { Text *text= st->text; TextLine* pline= text->lines.first; int i= st->top, lineno= 0; text_update_drawcache(st, ar); if(wrap_top) *wrap_top= 0; if(st->wordwrap) { while(i>0 && pline) { int lines= text_get_visible_lines_no(st, lineno); if (i-lines<0) { if(wrap_top) *wrap_top= i; break; } else { pline= pline->next; i-= lines; lineno++; } } } else { for(i=st->top; pline->next && i>0; i--) pline= pline->next; } return pline; } /************************ draw scrollbar *****************************/ static void calc_text_rcts(SpaceText *st, ARegion *ar, rcti *scroll, rcti *back) { int lhlstart, lhlend, ltexth, sell_off, curl_off; short barheight, barstart, hlstart, hlend, blank_lines; short pix_available, pix_top_margin, pix_bottom_margin, pix_bardiff; pix_top_margin = 8; pix_bottom_margin = 4; pix_available = ar->winy - pix_top_margin - pix_bottom_margin; ltexth= text_get_total_lines(st, ar); blank_lines = st->viewlines / 2; /* nicer code: use scroll rect for entire bar */ back->xmin= ar->winx -18; back->xmax= ar->winx; back->ymin= 0; back->ymax= ar->winy; scroll->xmin= ar->winx - 17; scroll->xmax= ar->winx - 5; scroll->ymin= 4; scroll->ymax= 4+pix_available; /* when resizing a vieport with the bar at the bottom to a greater height more blank lines will be added */ if(ltexth + blank_lines < st->top + st->viewlines) { blank_lines = st->top + st->viewlines - ltexth; } ltexth += blank_lines; barheight = (ltexth > 0)? (st->viewlines*pix_available)/ltexth: 0; pix_bardiff = 0; if(barheight < 20) { pix_bardiff = 20 - barheight; /* take into account the now non-linear sizing of the bar */ barheight = 20; } barstart = (ltexth > 0)? ((pix_available - pix_bardiff) * st->top)/ltexth: 0; st->txtbar= *scroll; st->txtbar.ymax -= barstart; st->txtbar.ymin = st->txtbar.ymax - barheight; CLAMP(st->txtbar.ymin, pix_bottom_margin, ar->winy - pix_top_margin); CLAMP(st->txtbar.ymax, pix_bottom_margin, ar->winy - pix_top_margin); st->pix_per_line= (pix_available > 0)? (float) ltexth/pix_available: 0; if(st->pix_per_line < 0.1f) st->pix_per_line=0.1f; curl_off= text_get_span_wrap(st, ar, st->text->lines.first, st->text->curl); sell_off= text_get_span_wrap(st, ar, st->text->lines.first, st->text->sell); lhlstart = MIN2(curl_off, sell_off); lhlend = MAX2(curl_off, sell_off); if(ltexth > 0) { hlstart = (lhlstart * pix_available)/ltexth; hlend = (lhlend * pix_available)/ltexth; /* the scrollbar is non-linear sized */ if(pix_bardiff > 0) { /* the start of the highlight is in the current viewport */ if(ltexth && st->viewlines && lhlstart >= st->top && lhlstart <= st->top + st->viewlines) { /* speed the progresion of the start of the highlight through the scrollbar */ hlstart = ( ( (pix_available - pix_bardiff) * lhlstart) / ltexth) + (pix_bardiff * (lhlstart - st->top) / st->viewlines); } else if(lhlstart > st->top + st->viewlines && hlstart < barstart + barheight && hlstart > barstart) { /* push hl start down */ hlstart = barstart + barheight; } else if(lhlend > st->top && lhlstart < st->top && hlstart > barstart) { /*fill out start */ hlstart = barstart; } if(hlend <= hlstart) { hlend = hlstart + 2; } /* the end of the highlight is in the current viewport */ if(ltexth && st->viewlines && lhlend >= st->top && lhlend <= st->top + st->viewlines) { /* speed the progresion of the end of the highlight through the scrollbar */ hlend = (((pix_available - pix_bardiff )*lhlend)/ltexth) + (pix_bardiff * (lhlend - st->top)/st->viewlines); } else if(lhlend < st->top && hlend >= barstart - 2 && hlend < barstart + barheight) { /* push hl end up */ hlend = barstart; } else if(lhlend > st->top + st->viewlines && lhlstart < st->top + st->viewlines && hlend < barstart + barheight) { /* fill out end */ hlend = barstart + barheight; } if(hlend <= hlstart) { hlstart = hlend - 2; } } } else { hlstart = 0; hlend = 0; } if(hlend - hlstart < 2) { hlend = hlstart + 2; } st->txtscroll= *scroll; st->txtscroll.ymax= ar->winy - pix_top_margin - hlstart; st->txtscroll.ymin= ar->winy - pix_top_margin - hlend; CLAMP(st->txtscroll.ymin, pix_bottom_margin, ar->winy - pix_top_margin); CLAMP(st->txtscroll.ymax, pix_bottom_margin, ar->winy - pix_top_margin); } static void draw_textscroll(SpaceText *st, rcti *scroll, rcti *back) { bTheme *btheme= U.themes.first; uiWidgetColors wcol= btheme->tui.wcol_scroll; unsigned char col[4]; float rad; UI_ThemeColor(TH_BACK); glRecti(back->xmin, back->ymin, back->xmax, back->ymax); uiWidgetScrollDraw(&wcol, scroll, &st->txtbar, (st->flags & ST_SCROLL_SELECT)?UI_SCROLL_PRESSED:0); uiSetRoundBox(15); rad= 0.4f*MIN2(st->txtscroll.xmax - st->txtscroll.xmin, st->txtscroll.ymax - st->txtscroll.ymin); UI_GetThemeColor3ubv(TH_HILITE, col); col[3]= 48; glColor4ubv(col); glEnable(GL_BLEND); uiRoundBox(st->txtscroll.xmin+1, st->txtscroll.ymin, st->txtscroll.xmax-1, st->txtscroll.ymax, rad); glDisable(GL_BLEND); } /************************** draw markers **************************/ static void draw_markers(SpaceText *st, ARegion *ar) { Text *text= st->text; TextMarker *marker, *next; TextLine *top, *line; int offl, offc, i, x1, x2, y1, y2, x, y; int topi, topy; /* Move pointer to first visible line (top) */ top= first_visible_line(st, ar, NULL); topi= BLI_findindex(&text->lines, top); topy= txt_get_span(text->lines.first, top); for(marker= text->markers.first; marker; marker= next) { next= marker->next; /* invisible line (before top) */ if(marker->linenolines, marker->lineno); /* Remove broken markers */ if(marker->end>line->len || marker->start>marker->end) { BLI_freelinkN(&text->markers, marker); continue; } wrap_offset(st, ar, line, marker->start, &offl, &offc); y1 = txt_get_span(top, line) - st->top + offl + topy; x1 = text_get_char_pos(st, line->line, marker->start) - st->left + offc; wrap_offset(st, ar, line, marker->end, &offl, &offc); y2 = txt_get_span(top, line) - st->top + offl + topy; x2 = text_get_char_pos(st, line->line, marker->end) - st->left + offc; /* invisible part of line (before top, after last visible line) */ if(y2 < 0 || y1 > st->top+st->viewlines) continue; glColor3ubv(marker->color); x= st->showlinenrs ? TXT_OFFSET + TEXTXLOC : TXT_OFFSET; y= ar->winy-3; if(y1==y2) { y -= y1*st->lheight; glBegin(GL_LINE_LOOP); glVertex2i(x+x2*st->cwidth+1, y); glVertex2i(x+x1*st->cwidth-2, y); glVertex2i(x+x1*st->cwidth-2, y-st->lheight); glVertex2i(x+x2*st->cwidth+1, y-st->lheight); glEnd(); } else { y -= y1*st->lheight; glBegin(GL_LINE_STRIP); glVertex2i(ar->winx, y); glVertex2i(x+x1*st->cwidth-2, y); glVertex2i(x+x1*st->cwidth-2, y-st->lheight); glVertex2i(ar->winx, y-st->lheight); glEnd(); y-=st->lheight; for(i=y1+1; iwinx, y); glVertex2i(x, y-st->lheight); glVertex2i(ar->winx, y-st->lheight); glEnd(); y-=st->lheight; } glBegin(GL_LINE_STRIP); glVertex2i(x, y); glVertex2i(x+x2*st->cwidth+1, y); glVertex2i(x+x2*st->cwidth+1, y-st->lheight); glVertex2i(x, y-st->lheight); glEnd(); } } } /*********************** draw documentation *******************************/ static void draw_documentation(SpaceText *st, ARegion *ar) { TextLine *tmp; char *docs, buf[DOC_WIDTH+1], *p; int i, br, lines; int boxw, boxh, l, x, y, top; if(!st || !st->text) return; if(!texttool_text_is_active(st->text)) return; docs = texttool_docs_get(); if(!docs) return; /* Count the visible lines to the cursor */ for(tmp=st->text->curl, l=-st->top; tmp; tmp=tmp->prev, l++); if(l<0) return; 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; } if(texttool_suggest_first()) { x += SUGG_LIST_WIDTH*st->cwidth + 50; } top= y= ar->winy - st->lheight*l - 2; boxw= DOC_WIDTH*st->cwidth + 20; boxh= (DOC_HEIGHT+1)*st->lheight; /* Draw panel */ UI_ThemeColor(TH_BACK); glRecti(x, y, x+boxw, y-boxh); UI_ThemeColor(TH_SHADE1); glBegin(GL_LINE_LOOP); glVertex2i(x, y); glVertex2i(x+boxw, y); glVertex2i(x+boxw, y-boxh); glVertex2i(x, y-boxh); glEnd(); glBegin(GL_LINE_LOOP); glVertex2i(x+boxw-10, y-7); glVertex2i(x+boxw-4, y-7); glVertex2i(x+boxw-7, y-2); glEnd(); glBegin(GL_LINE_LOOP); glVertex2i(x+boxw-10, y-boxh+7); glVertex2i(x+boxw-4, y-boxh+7); glVertex2i(x+boxw-7, y-boxh+2); glEnd(); UI_ThemeColor(TH_TEXT); i= 0; br= DOC_WIDTH; lines= 0; // XXX -doc_scroll; for(p=docs; *p; p++) { if(*p == '\r' && *(++p) != '\n') *(--p)= '\n'; /* Fix line endings */ if(*p == ' ' || *p == '\t') br= i; else if(*p == '\n') { buf[i]= '\0'; if(lines>=0) { y -= st->lheight; text_draw(st, buf, 0, 0, 1, x+4, y-3, NULL); } i= 0; br= DOC_WIDTH; lines++; } buf[i++]= *p; if(i == DOC_WIDTH) { /* Reached the width, go to last break and wrap there */ buf[br]= '\0'; if(lines>=0) { y -= st->lheight; text_draw(st, buf, 0, 0, 1, x+4, y-3, NULL); } p -= i-br-1; /* Rewind pointer to last break */ i= 0; br= DOC_WIDTH; lines++; } if(lines >= DOC_HEIGHT) break; } if(0 /* XXX doc_scroll*/ > 0 && lines < DOC_HEIGHT) { // XXX doc_scroll--; draw_documentation(st, ar); } } /*********************** draw suggestion list *******************************/ static void draw_suggestion_list(SpaceText *st, ARegion *ar) { SuggItem *item, *first, *last, *sel; TextLine *tmp; char str[SUGG_LIST_WIDTH+1]; int w, boxw=0, boxh, i, l, x, y, b, *top; if(!st || !st->text) return; if(!texttool_text_is_active(st->text)) return; first = texttool_suggest_first(); last = texttool_suggest_last(); if(!first || !last) return; text_pop_suggest_list(); sel = texttool_suggest_selected(); top = texttool_suggest_top(); /* Count the visible lines to the cursor */ for(tmp=st->text->curl, l=-st->top; tmp; tmp=tmp->prev, l++); if(l<0) return; 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*l - 2; boxw = SUGG_LIST_WIDTH*st->cwidth + 20; boxh = SUGG_LIST_SIZE*st->lheight + 8; UI_ThemeColor(TH_SHADE1); glRecti(x-1, y+1, x+boxw+1, y-boxh-1); UI_ThemeColor(TH_BACK); glRecti(x, y, x+boxw, y-boxh); /* Set the top 'item' of the visible list */ for(i=0, item=first; i<*top && item->next; i++, item=item->next); for(i=0; inext) { y -= st->lheight; strncpy(str, item->name, SUGG_LIST_WIDTH); str[SUGG_LIST_WIDTH] = '\0'; w = text_font_width(st, str); if(item == sel) { UI_ThemeColor(TH_SHADE2); glRecti(x+16, y-3, x+16+w, y+st->lheight-3); } b=1; /* b=1 color block, text is default. b=0 no block, color text */ switch (item->type) { case 'k': UI_ThemeColor(TH_SYNTAX_B); b=0; break; case 'm': UI_ThemeColor(TH_TEXT); break; case 'f': UI_ThemeColor(TH_SYNTAX_L); break; case 'v': UI_ThemeColor(TH_SYNTAX_N); break; case '?': UI_ThemeColor(TH_TEXT); b=0; break; } if(b) { glRecti(x+8, y+2, x+11, y+5); UI_ThemeColor(TH_TEXT); } text_draw(st, str, 0, 0, 1, x+16, y-1, NULL); if(item == last) break; } } /*********************** draw cursor ************************/ static void draw_cursor(SpaceText *st, ARegion *ar) { Text *text= st->text; int vcurl, vcurc, vsell, vselc, hidden=0; int x, y, w, i; /* Draw the selection */ if(text->curl!=text->sell || text->curc!=text->selc) { int offl, offc; /* Convert all to view space character coordinates */ wrap_offset(st, ar, text->curl, text->curc, &offl, &offc); vcurl = txt_get_span(text->lines.first, text->curl) - st->top + offl; vcurc = text_get_char_pos(st, text->curl->line, text->curc) - st->left + offc; wrap_offset(st, ar, text->sell, text->selc, &offl, &offc); vsell = txt_get_span(text->lines.first, text->sell) - st->top + offl; vselc = text_get_char_pos(st, text->sell->line, text->selc) - st->left + offc; if(vcurc<0) vcurc=0; if(vselc<0) vselc=0, hidden=1; UI_ThemeColor(TH_SHADE2); x= st->showlinenrs ? TXT_OFFSET + TEXTXLOC : TXT_OFFSET; y= ar->winy-2; if(vcurl==vsell) { y -= vcurl*st->lheight; if(vcurc < vselc) glRecti(x+vcurc*st->cwidth-1, y, x+vselc*st->cwidth, y-st->lheight); else glRecti(x+vselc*st->cwidth-1, y, x+vcurc*st->cwidth, y-st->lheight); } else { int froml, fromc, tol, toc; if(vcurl < vsell) { froml= vcurl; tol= vsell; fromc= vcurc; toc= vselc; } else { froml= vsell; tol= vcurl; fromc= vselc; toc= vcurc; } y -= froml*st->lheight; glRecti(x+fromc*st->cwidth-1, y, ar->winx, y-st->lheight); y-=st->lheight; for(i=froml+1; iwinx, y-st->lheight), y-=st->lheight; glRecti(x-4, y, x+toc*st->cwidth, y-st->lheight); y-=st->lheight; } } else { int offl, offc; wrap_offset(st, ar, text->sell, text->selc, &offl, &offc); vsell = txt_get_span(text->lines.first, text->sell) - st->top + offl; vselc = text_get_char_pos(st, text->sell->line, text->selc) - st->left + offc; if(vselc<0) { vselc= 0; hidden= 1; } } if(st->line_hlight) { int x1, x2, y1, y2; if(st->wordwrap) { int visible_lines = text_get_visible_lines(st, ar, text->sell->line); int offl, offc; wrap_offset_in_line(st, ar, text->sell, text->selc, &offl, &offc); y1= ar->winy-2 - (vsell-offl)*st->lheight; y2= y1-st->lheight*visible_lines+1; } else { y1= ar->winy-2 - vsell*st->lheight; y2= y1-st->lheight+1; } if(!(y1<0 || y2 > ar->winy)) { /* check we need to draw */ x1= st->showlinenrs ? TXT_OFFSET + TEXTXLOC : TXT_OFFSET; x2= x1 + ar->winx; glColor4ub(255, 255, 255, 32); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_BLEND); glRecti(x1-4, y1, x2, y2); glDisable(GL_BLEND); } } if(!hidden) { /* Draw the cursor itself (we draw the sel. cursor as this is the leading edge) */ x= st->showlinenrs ? TXT_OFFSET + TEXTXLOC : TXT_OFFSET; x += vselc*st->cwidth; y= ar->winy-2 - vsell*st->lheight; if(st->overwrite) { char ch= text->sell->line[text->selc]; w= st->cwidth; if(ch=='\t') w*= st->tabnumber-(vselc+st->left)%st->tabnumber; UI_ThemeColor(TH_HILITE); glRecti(x, y-st->lheight-1, x+w, y-st->lheight+1); } else { UI_ThemeColor(TH_HILITE); glRecti(x-1, y, x+1, y-st->lheight); } } } /******************* draw matching brackets *********************/ static void draw_brackets(SpaceText *st, ARegion *ar) { TextLine *startl, *endl, *linep; Text *text = st->text; int b, c, startc, endc, find, stack; int viewc, viewl, offl, offc, x, y; char ch; // showsyntax must be on or else the format string will be null if(!text->curl || !st->showsyntax) return; startl= text->curl; startc= text->curc; b= text_check_bracket(startl->line[startc]); if(b==0 && startc>0) b = text_check_bracket(startl->line[--startc]); if(b==0) return; linep= startl; c= startc; endl= NULL; endc= -1; find= -b; stack= 0; /* Dont highlight backets if syntax HL is off or bracket in string or comment. */ if(!linep->format || linep->format[c] == 'l' || linep->format[c] == '#') return; if(b>0) { /* opening bracket, search forward for close */ c++; while(linep) { while(clen) { if(linep->format && linep->format[c] != 'l' && linep->format[c] != '#') { b= text_check_bracket(linep->line[c]); if(b==find) { if(stack==0) { endl= linep; endc= c; break; } stack--; } else if(b==-find) { stack++; } } c++; } if(endl) break; linep= linep->next; c= 0; } } else { /* closing bracket, search backward for open */ c--; while(linep) { while(c>=0) { if(linep->format && linep->format[c] != 'l' && linep->format[c] != '#') { b= text_check_bracket(linep->line[c]); if(b==find) { if(stack==0) { endl= linep; endc= c; break; } stack--; } else if(b==-find) { stack++; } } c--; } if(endl) break; linep= linep->prev; if(linep) c= linep->len-1; } } if(!endl || endc==-1) return; UI_ThemeColor(TH_HILITE); x= st->showlinenrs ? TXT_OFFSET + TEXTXLOC : TXT_OFFSET; y= ar->winy - st->lheight; /* draw opening bracket */ ch= startl->line[startc]; wrap_offset(st, ar, startl, startc, &offl, &offc); viewc= text_get_char_pos(st, startl->line, startc) - st->left + offc; if(viewc >= 0){ viewl= txt_get_span(text->lines.first, startl) - st->top + offl; text_font_draw_character(st, x+viewc*st->cwidth, y-viewl*st->lheight, ch); text_font_draw_character(st, x+viewc*st->cwidth+1, y-viewl*st->lheight, ch); } /* draw closing bracket */ ch= endl->line[endc]; wrap_offset(st, ar, endl, endc, &offl, &offc); viewc= text_get_char_pos(st, endl->line, endc) - st->left + offc; if(viewc >= 0) { viewl= txt_get_span(text->lines.first, endl) - st->top + offl; text_font_draw_character(st, x+viewc*st->cwidth, y-viewl*st->lheight, ch); text_font_draw_character(st, x+viewc*st->cwidth+1, y-viewl*st->lheight, ch); } } /*********************** main area drawing *************************/ void draw_text_main(SpaceText *st, ARegion *ar) { Text *text= st->text; TextLine *tmp; rcti scroll, back; char linenr[12]; int i, x, y, winx, linecount= 0, lineno= 0; int wraplinecount= 0, wrap_skip= 0; /* if no text, nothing to do */ if(!text) return; text_update_drawcache(st, ar); /* make sure all the positional pointers exist */ if(!text->curl || !text->sell || !text->lines.first || !text->lines.last) txt_clean_text(text); if(st->lheight) st->viewlines= (int)ar->winy/st->lheight; else st->viewlines= 0; /* update rects for scroll */ calc_text_rcts(st, ar, &scroll, &back); /* scroll will hold the entire bar size */ /* update syntax formatting if needed */ tmp= text->lines.first; lineno= 0; for(i= 0; itop && tmp; i++) { if(st->showsyntax && !tmp->format) txt_format_line(st, tmp, 0); if(st->wordwrap) { int lines= text_get_visible_lines_no(st, lineno); if (wraplinecount+lines>st->top) { wrap_skip= st->top-wraplinecount; break; } else { wraplinecount+= lines; tmp= tmp->next; linecount++; } } else { tmp= tmp->next; linecount++; } lineno++; } text_font_begin(st); st->cwidth= BLF_fixed_width(mono); st->cwidth= MAX2(st->cwidth, 1); /* draw line numbers background */ if(st->showlinenrs) { x= TXT_OFFSET + TEXTXLOC; UI_ThemeColor(TH_GRID); glRecti((TXT_OFFSET-12), 0, (TXT_OFFSET-5) + TEXTXLOC, ar->winy - 2); } else { st->linenrs_tot= 0; /* not used */ x= TXT_OFFSET; } y= ar->winy-st->lheight; winx= ar->winx - TXT_SCROLL_WIDTH; /* draw cursor */ draw_cursor(st, ar); /* draw the text */ UI_ThemeColor(TH_TEXT); for(i=0; y>0 && iviewlines && tmp; i++, tmp= tmp->next) { if(st->showsyntax && !tmp->format) txt_format_line(st, tmp, 0); if(st->showlinenrs && !wrap_skip) { /* draw line number */ if(tmp == text->curl) UI_ThemeColor(TH_HILITE); else UI_ThemeColor(TH_TEXT); sprintf(linenr, "%*d", st->linenrs_tot, i + linecount + 1); /* itoa(i + linecount + 1, linenr, 10); */ /* not ansi-c :/ */ text_font_draw(st, TXT_OFFSET - 7, y, linenr); UI_ThemeColor(TH_TEXT); } if(st->wordwrap) { /* draw word wrapped text */ int lines = text_draw_wrapped(st, tmp->line, x, y, winx-x, tmp->format, wrap_skip); y -= lines*st->lheight; } else { /* draw unwrapped text */ text_draw(st, tmp->line, st->left, ar->winx/st->cwidth, 1, x, y, tmp->format); y -= st->lheight; } wrap_skip= 0; } if(st->flags&ST_SHOW_MARGIN) { UI_ThemeColor(TH_HILITE); glBegin(GL_LINES); glVertex2i(x+st->cwidth*st->margin_column, 0); glVertex2i(x+st->cwidth*st->margin_column, ar->winy - 2); glEnd(); } /* draw other stuff */ draw_brackets(st, ar); draw_markers(st, ar); glTranslatef(0.375f, 0.375f, 0.0f); /* XXX scroll requires exact pixel space */ draw_textscroll(st, &scroll, &back); draw_documentation(st, ar); draw_suggestion_list(st, ar); text_font_end(st); } /************************** update ***************************/ void text_update_character_width(SpaceText *st) { text_font_begin(st); st->cwidth= BLF_fixed_width(mono); st->cwidth= MAX2(st->cwidth, 1); text_font_end(st); } /* Moves the view to the cursor location, also used to make sure the view isnt outside the file */ void text_update_cursor_moved(bContext *C) { ScrArea *sa= CTX_wm_area(C); SpaceText *st= CTX_wm_space_text(C); Text *text; ARegion *ar; int i, x, winx= 0; if(ELEM3(NULL, st, st->text, st->text->curl)) return; text= st->text; for(ar=sa->regionbase.first; ar; ar= ar->next) if(ar->regiontype==RGN_TYPE_WINDOW) winx= ar->winx; winx -= TXT_SCROLL_WIDTH; text_update_character_width(st); i= txt_get_span(text->lines.first, text->sell); if(st->wordwrap) { int offl, offc; wrap_offset(st, CTX_wm_region(C), text->sell, text->selc, &offl, &offc); i+= offl; } if(st->top+st->viewlines <= i || st->top > i) st->top= i - st->viewlines/2; if(st->wordwrap) { st->left= 0; } else { x= text_draw(st, text->sell->line, st->left, text->selc, 0, 0, 0, NULL); if(x==0 || x>winx) st->left= text->curc-0.5*winx/st->cwidth; } if(st->top < 0) st->top= 0; if(st->left <0) st->left= 0; }