ee00ffa9f87002baca38c8b68c01e88c573467b9
[blender.git] / source / blender / editors / space_text / text_draw.c
1 /**
2  * $Id$
3  *
4  * ***** BEGIN GPL LICENSE BLOCK *****
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software Foundation,
17  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18  *
19  * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
20  * All rights reserved.
21  *
22  * The Original Code is: all of this file.
23  *
24  * Contributor(s): none yet.
25  *
26  * ***** END GPL LICENSE BLOCK *****
27  */
28
29 #include <math.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <sys/stat.h>
33
34 #include "MEM_guardedalloc.h"
35
36 #include "BLF_api.h"
37
38 #include "BLI_blenlib.h"
39
40 #include "DNA_text_types.h"
41 #include "DNA_space_types.h"
42 #include "DNA_screen_types.h"
43 #include "DNA_userdef_types.h"
44
45 #include "BKE_context.h"
46 #include "BKE_suggestions.h"
47 #include "BKE_text.h"
48 #include "BKE_utildefines.h"
49
50 #include "BIF_gl.h"
51
52 #include "ED_datafiles.h"
53 #include "UI_interface.h"
54 #include "UI_resources.h"
55
56 #include "text_intern.h"
57
58 /******************** text font drawing ******************/
59 static int mono= -1; // XXX needs proper storage and change all the BLF_* here
60
61 static void text_font_begin(SpaceText *st)
62 {
63         if(mono == -1)
64                 mono= BLF_load_mem("monospace", (unsigned char*)datatoc_bmonofont_ttf, datatoc_bmonofont_ttf_size);
65
66         BLF_aspect(mono, 1.0);
67         BLF_size(mono, st->lheight, 72);
68 }
69
70 static void text_font_end(SpaceText *UNUSED(st))
71 {
72 }
73
74 static int text_font_draw(SpaceText *UNUSED(st), int x, int y, char *str)
75 {
76         BLF_position(mono, x, y, 0);
77         BLF_draw(mono, str, 65535); /* XXX, use real length */
78
79         return BLF_width(mono, str);
80 }
81
82 static int text_font_draw_character(SpaceText *st, int x, int y, char c)
83 {
84         char str[1];
85         str[0]= c;
86
87         BLF_position(mono, x, y, 0);
88         BLF_draw(mono, str, 1);
89
90         return st->cwidth;
91 }
92
93 int text_font_width(SpaceText *UNUSED(st), char *str)
94 {
95         return BLF_width(mono, str);
96 }
97
98 /****************** flatten string **********************/
99
100 static void flatten_string_append(FlattenString *fs, char c, int accum) 
101 {
102         if(fs->pos>=fs->len && fs->pos>=sizeof(fs->fixedbuf)-1) {
103                 char *nbuf; int *naccum;
104                 if(fs->len) fs->len*= 2;
105                 else fs->len= sizeof(fs->fixedbuf) * 2;
106
107                 nbuf= MEM_callocN(sizeof(*fs->buf)*fs->len, "fs->buf");
108                 naccum= MEM_callocN(sizeof(*fs->accum)*fs->len, "fs->accum");
109
110                 memcpy(nbuf, fs->buf, fs->pos * sizeof(*fs->buf));
111                 memcpy(naccum, fs->accum, fs->pos * sizeof(*fs->accum));
112                 
113                 if(fs->buf != fs->fixedbuf) {
114                         MEM_freeN(fs->buf);
115                         MEM_freeN(fs->accum);
116                 }
117                 
118                 fs->buf= nbuf;
119                 fs->accum= naccum;
120         }
121         
122         fs->buf[fs->pos]= c;    
123         fs->accum[fs->pos]= accum;
124         
125         fs->pos++;
126 }
127
128 int flatten_string(SpaceText *st, FlattenString *fs, char *in)
129 {
130         int r = 0, i = 0;
131
132         memset(fs, 0, sizeof(FlattenString));
133         fs->buf= fs->fixedbuf;
134         fs->accum= fs->fixedaccum;
135         
136         for(r=0, i=0; *in; r++, in++) {
137                 if(*in=='\t') {
138                         if(fs->pos && *(in-1)=='\t')
139                                 i= st->tabnumber;
140                         else if(st->tabnumber > 0)
141                                 i= st->tabnumber - (fs->pos%st->tabnumber);
142
143                         while(i--)
144                                 flatten_string_append(fs, ' ', r);
145                 }
146                 else
147                         flatten_string_append(fs, *in, r);
148         }
149
150         return fs->pos;
151 }
152
153 void flatten_string_free(FlattenString *fs)
154 {
155         if(fs->buf != fs->fixedbuf)
156                 MEM_freeN(fs->buf);
157         if(fs->accum != fs->fixedaccum)
158                 MEM_freeN(fs->accum);
159 }
160
161 /* Checks the specified source string for a Python built-in function name. This
162  name must start at the beginning of the source string and must be followed by
163  a non-identifier (see text_check_identifier(char)) or null character.
164  
165  If a built-in function is found, the length of the matching name is returned.
166  Otherwise, -1 is returned. */
167
168 static int find_builtinfunc(char *string)
169 {
170         int a, i;
171         char builtinfuncs[][9] = {"and", "as", "assert", "break", "class", "continue", "def",
172                                                                 "del", "elif", "else", "except", "exec", "finally",
173                                                                 "for", "from", "global", "if", "import", "in",
174                                                                 "is", "lambda", "not", "or", "pass", "print",
175                                                                 "raise", "return", "try", "while", "yield", "with"};
176
177         for(a=0; a < sizeof(builtinfuncs)/sizeof(builtinfuncs[0]); a++) {
178                 i = 0;
179                 while(1) {
180                         /* If we hit the end of a keyword... (eg. "def") */
181                         if(builtinfuncs[a][i]=='\0') {
182                                 /* If we still have identifier chars in the source (eg. "definate") */
183                                 if(text_check_identifier(string[i]))
184                                         i = -1; /* No match */
185                                 break; /* Next keyword if no match, otherwise we're done */
186                                 
187                         /* If chars mismatch, move on to next keyword */
188                         }
189                         else if(string[i]!=builtinfuncs[a][i]) {
190                                 i = -1;
191                                 break; /* Break inner loop, start next keyword */
192                         }
193                         i++;
194                 }
195                 if(i>0) break; /* If we have a match, we're done */
196         }
197         return i;
198 }
199
200 /* Checks the specified source string for a Python special name. This name must
201  start at the beginning of the source string and must be followed by a non-
202  identifier (see text_check_identifier(char)) or null character.
203  
204  If a special name is found, the length of the matching name is returned.
205  Otherwise, -1 is returned. */
206
207 static int find_specialvar(char *string) 
208 {
209         int i = 0;
210         /* Check for "def" */
211         if(string[0]=='d' && string[1]=='e' && string[2]=='f')
212                 i = 3;
213         /* Check for "class" */
214         else if(string[0]=='c' && string[1]=='l' && string[2]=='a' && string[3]=='s' && string[4]=='s')
215                 i = 5;
216         /* If next source char is an identifier (eg. 'i' in "definate") no match */
217         if(i==0 || text_check_identifier(string[i]))
218                 return -1;
219         return i;
220 }
221
222 static int find_decorator(char *string) 
223 {
224         if(string[0] == '@') {
225                 int i = 1;
226                 while(text_check_identifier(string[i])) {
227                         i++;
228                 }
229                 return i;
230         }
231         return -1;
232 }
233
234 static int find_bool(char *string) 
235 {
236         int i = 0;
237         /* Check for "False" */
238         if(string[0]=='F' && string[1]=='a' && string[2]=='l' && string[3]=='s' && string[4]=='e')
239                 i = 5;
240         /* Check for "True" */
241         else if(string[0]=='T' && string[1]=='r' && string[2]=='u' && string[3]=='e')
242                 i = 4;
243         /* Check for "None" */
244         else if(string[0]=='N' && string[1]=='o' && string[2]=='n' && string[3]=='e')
245                 i = 4;
246         /* If next source char is an identifier (eg. 'i' in "definate") no match */
247         if(i==0 || text_check_identifier(string[i]))
248                 return -1;
249         return i;
250 }
251
252 /* Ensures the format string for the given line is long enough, reallocating
253  as needed. Allocation is done here, alone, to ensure consistency. */
254 int text_check_format_len(TextLine *line, unsigned int len)
255 {
256         if(line->format) {
257                 if(strlen(line->format) < len) {
258                         MEM_freeN(line->format);
259                         line->format = MEM_mallocN(len+2, "SyntaxFormat");
260                         if(!line->format) return 0;
261                 }
262         }
263         else {
264                 line->format = MEM_mallocN(len+2, "SyntaxFormat");
265                 if(!line->format) return 0;
266         }
267
268         return 1;
269 }
270
271 /* Formats the specified line. If do_next is set, the process will move on to
272  the succeeding line if it is affected (eg. multiline strings). Format strings
273  may contain any of the following characters:
274          '_'            Whitespace
275          '#'            Comment text
276          '!'            Punctuation and other symbols
277          'n'            Numerals
278          'l'            String letters
279          'v'            Special variables (class, def)
280          'b'            Built-in names (print, for, etc.)
281          'q'            Other text (identifiers, etc.)
282  It is terminated with a null-terminator '\0' followed by a continuation
283  flag indicating whether the line is part of a multi-line string. */
284
285 static void txt_format_line(SpaceText *st, TextLine *line, int do_next)
286 {
287         FlattenString fs;
288         char *str, *fmt, orig, cont, find, prev = ' ';
289         int len, i;
290
291         /* Get continuation from previous line */
292         if(line->prev && line->prev->format != NULL) {
293                 fmt= line->prev->format;
294                 cont = fmt[strlen(fmt)+1]; /* Just after the null-terminator */
295         }
296         else cont = 0;
297
298         /* Get original continuation from this line */
299         if(line->format != NULL) {
300                 fmt= line->format;
301                 orig = fmt[strlen(fmt)+1]; /* Just after the null-terminator */
302         }
303         else orig = 0xFF;
304
305         flatten_string(st, &fs, line->line);
306         str = fs.buf;
307         len = strlen(str);
308         if(!text_check_format_len(line, len)) {
309                 flatten_string_free(&fs);
310                 return;
311         }
312         fmt = line->format;
313
314         while(*str) {
315                 /* Handle escape sequences by skipping both \ and next char */
316                 if(*str == '\\') {
317                         *fmt = prev; fmt++; str++;
318                         if(*str == '\0') break;
319                         *fmt = prev; fmt++; str++;
320                         continue;
321                 }
322                 /* Handle continuations */
323                 else if(cont) {
324                         /* Triple strings ("""...""" or '''...''') */
325                         if(cont & TXT_TRISTR) {
326                                 find = (cont & TXT_DBLQUOTSTR) ? '"' : '\'';
327                                 if(*str==find && *(str+1)==find && *(str+2)==find) {
328                                         *fmt = 'l'; fmt++; str++;
329                                         *fmt = 'l'; fmt++; str++;
330                                         cont = 0;
331                                 }
332                         /* Handle other strings */
333                         }
334                         else {
335                                 find = (cont & TXT_DBLQUOTSTR) ? '"' : '\'';
336                                 if(*str == find) cont = 0;
337                         }
338
339                         *fmt = 'l';
340                 }
341                 /* Not in a string... */
342                 else {
343                         /* Deal with comments first */
344                         if(prev == '#' || *str == '#')
345                                 *fmt = '#';
346                         /* Strings */
347                         else if(*str == '"' || *str == '\'') {
348                                 find = *str;
349                                 cont = (*str== '"') ? TXT_DBLQUOTSTR : TXT_SNGQUOTSTR;
350                                 if(*(str+1) == find && *(str+2) == find) {
351                                         *fmt = 'l'; fmt++; str++;
352                                         *fmt = 'l'; fmt++; str++;
353                                         cont |= TXT_TRISTR;
354                                 }
355                                 *fmt = 'l';
356                         }
357                         /* Whitespace (all ws. has been converted to spaces) */
358                         else if(*str == ' ')
359                                 *fmt = '_';
360                         /* Numbers (digits not part of an identifier and periods followed by digits) */
361                         else if((prev != 'q' && text_check_digit(*str)) || (*str == '.' && text_check_digit(*(str+1))))
362                                 *fmt = 'n';
363                         /* Booleans */
364                         else if(prev != 'q' && (i=find_bool(str)) != -1)
365                                 if(i>0) {
366                                         while(i>1) {
367                                                 *fmt = 'n'; fmt++; str++;
368                                                 i--;
369                                         }
370                                         *fmt = 'n';
371                                 }
372                                 else
373                                         *fmt = 'q';
374                         /* Punctuation */
375                         else if(text_check_delim(*str))
376                                 *fmt = '!';
377                         /* Identifiers and other text (no previous ws. or delims. so text continues) */
378                         else if(prev == 'q')
379                                 *fmt = 'q';
380                         /* Not ws, a digit, punct, or continuing text. Must be new, check for special words */
381                         else {
382                                 /* Special vars(v) or built-in keywords(b) */
383                                 if((i=find_specialvar(str)) != -1)
384                                         prev = 'v';
385                                 else if((i=find_builtinfunc(str)) != -1)
386                                         prev = 'b';
387                                 else if((i=find_decorator(str)) != -1)
388                                         prev = 'v'; /* could have a new color for this */
389                                 if(i>0) {
390                                         while(i>1) {
391                                                 *fmt = prev; fmt++; str++;
392                                                 i--;
393                                         }
394                                         *fmt = prev;
395                                 }
396                                 else
397                                         *fmt = 'q';
398                         }
399                 }
400                 prev = *fmt;
401                 fmt++;
402                 str++;
403         }
404
405         /* Terminate and add continuation char */
406         *fmt = '\0'; fmt++;
407         *fmt = cont;
408
409         /* Debugging */
410         //print_format(st, line);
411
412         /* If continuation has changed and we're allowed, process the next line */
413         if(cont!=orig && do_next && line->next) {
414                 txt_format_line(st, line->next, do_next);
415         }
416
417         flatten_string_free(&fs);
418 }
419
420 #if 0
421 /* Formats every line of the current text */
422 static void txt_format_text(SpaceText *st) 
423 {
424         TextLine *linep;
425
426         if(!st->text) return;
427
428         for(linep=st->text->lines.first; linep; linep=linep->next)
429                 txt_format_line(st, linep, 0);
430 }
431 #endif
432
433 /* Sets the current drawing color based on the format character specified */
434 static void format_draw_color(char formatchar)
435 {
436         switch (formatchar) {
437                 case '_': /* Whitespace */
438                         break;
439                 case '!': /* Symbols */
440                         UI_ThemeColorBlend(TH_TEXT, TH_BACK, 0.5f);
441                         break;
442                 case '#': /* Comments */
443                         UI_ThemeColor(TH_SYNTAX_C);
444                         break;
445                 case 'n': /* Numerals */
446                         UI_ThemeColor(TH_SYNTAX_N);
447                         break;
448                 case 'l': /* Strings */
449                         UI_ThemeColor(TH_SYNTAX_L);
450                         break;
451                 case 'v': /* Specials: class, def */
452                         UI_ThemeColor(TH_SYNTAX_V);
453                         break;
454                 case 'b': /* Keywords: for, print, etc. */
455                         UI_ThemeColor(TH_SYNTAX_B);
456                         break;
457                 case 'q': /* Other text (identifiers) */
458                 default:
459                         UI_ThemeColor(TH_TEXT);
460                         break;
461         }
462 }
463
464 /************************** draw text *****************************/
465
466 /***********************/ /*
467
468 Notes on word-wrap
469 --
470 All word-wrap functions follow the algorithm below to maintain consistency.
471         line            The line to wrap (tabs converted to spaces)
472         view_width      The maximum number of characters displayable in the region
473                                 This equals region_width/font_width for the region
474         wrap_chars      Characters that allow wrapping. This equals [' ', '\t', '-']
475
476 def wrap(line, view_width, wrap_chars):
477         draw_start = 0
478         draw_end = view_width
479         pos = 0
480         for c in line:
481                 if pos-draw_start >= view_width:
482                         print line[draw_start:draw_end]
483                         draw_start = draw_end
484                         draw_end += view_width
485                 elif c in wrap_chars:
486                         draw_end = pos+1
487                 pos += 1
488         print line[draw_start:]
489
490 */ /***********************/
491
492 int wrap_width(SpaceText *st, ARegion *ar)
493 {
494         int winx= ar->winx - TXT_SCROLL_WIDTH;
495         int x, max;
496         
497         x= st->showlinenrs ? TXT_OFFSET + TEXTXLOC : TXT_OFFSET;
498         max= st->cwidth ? (winx-x)/st->cwidth : 0;
499         return max>8 ? max : 8;
500 }
501
502 /* Sets (offl, offc) for transforming (line, curs) to its wrapped position */
503 void wrap_offset(SpaceText *st, ARegion *ar, TextLine *linein, int cursin, int *offl, int *offc)
504 {
505         Text *text;
506         TextLine *linep;
507         int i, j, start, end, chars, max, chop;
508         char ch;
509
510         *offl= *offc= 0;
511
512         if(!st->text) return;
513         if(!st->wordwrap) return;
514
515         text= st->text;
516
517         /* Move pointer to first visible line (top) */
518         linep= text->lines.first;
519         i= st->top;
520         while(i>0 && linep) {
521                 int lines= text_get_visible_lines(st, ar, linep->line);
522
523                 /* Line before top */
524                 if(linep == linein) {
525                         if(lines <= i)
526                                 /* no visible part of line */
527                                 return;
528                 }
529
530                 if (i-lines<0) {
531                         break;
532                 } else {
533                         linep= linep->next;
534                         (*offl)+= lines-1;
535                         i-= lines;
536                 }
537         }
538
539         max= wrap_width(st, ar);
540
541         while(linep) {
542                 start= 0;
543                 end= max;
544                 chop= 1;
545                 chars= 0;
546                 *offc= 0;
547                 for(i=0, j=0; linep->line[j]!='\0'; j++) {
548
549                         /* Mimic replacement of tabs */
550                         ch= linep->line[j];
551                         if(ch=='\t') {
552                                 chars= st->tabnumber-i%st->tabnumber;
553                                 if(linep==linein && i<cursin) cursin += chars-1;
554                                 ch= ' ';
555                         }
556                         else
557                                 chars= 1;
558
559                         while(chars--) {
560                                 if(i-start>=max) {
561                                         if(chop && linep==linein && i >= cursin) {
562                                                 if (i==cursin) {
563                                                         (*offl)++;
564                                                         *offc -= end-start;
565                                                 }
566
567                                                 return;
568                                         }
569
570                                         (*offl)++;
571                                         *offc -= end-start;
572
573                                         start= end;
574                                         end += max;
575                                         chop= 1;
576                                 }
577                                 else if(ch==' ' || ch=='-') {
578                                         end = i+1;
579                                         chop= 0;
580                                         if(linep==linein && i >= cursin)
581                                                 return;
582                                 }
583                                 i++;
584                         }
585                 }
586                 if(linep==linein) break;
587                 linep= linep->next;
588         }
589 }
590
591 void wrap_offset_in_line(SpaceText *st, ARegion *ar, TextLine *linein, int cursin, int *offl, int *offc)
592 {
593         int i, j, start, end, chars, max, chop;
594         char ch;
595
596         *offl= *offc= 0;
597
598         if(!st->text) return;
599         if(!st->wordwrap) return;
600
601         max= wrap_width(st, ar);
602
603         start= 0;
604         end= max;
605         chop= 1;
606         chars= 0;
607         *offc= 0;
608
609         for(i=0, j=0; linein->line[j]!='\0'; j++) {
610
611                 /* Mimic replacement of tabs */
612                 ch= linein->line[j];
613                 if(ch=='\t') {
614                         chars= st->tabnumber-i%st->tabnumber;
615                         if(i<cursin) cursin += chars-1;
616                         ch= ' ';
617                 }
618                 else
619                         chars= 1;
620
621                 while(chars--) {
622                         if(i-start>=max) {
623                                 if(chop && i >= cursin) {
624                                         if (i==cursin) {
625                                                 (*offl)++;
626                                                 *offc -= end-start;
627                                         }
628
629                                         return;
630                                 }
631
632                                 (*offl)++;
633                                 *offc -= end-start;
634
635                                 start= end;
636                                 end += max;
637                                 chop= 1;
638                         }
639                         else if(ch==' ' || ch=='-') {
640                                 end = i+1;
641                                 chop= 0;
642                                 if(i >= cursin)
643                                         return;
644                         }
645                         i++;
646                 }
647         }
648 }
649
650 int text_get_char_pos(SpaceText *st, char *line, int cur)
651 {
652         int a=0, i;
653         
654         for(i=0; i<cur && line[i]; i++) {
655                 if(line[i]=='\t')
656                         a += st->tabnumber-a%st->tabnumber;
657                 else
658                         a++;
659         }
660         return a;
661 }
662
663 static int text_draw_wrapped(SpaceText *st, char *str, int x, int y, int w, char *format, int skip)
664 {
665         FlattenString fs;
666         int basex, i, a, len, start, end, max, lines;
667         
668         len= flatten_string(st, &fs, str);
669         str= fs.buf;
670         max= w/st->cwidth;
671         if(max<8) max= 8;
672         basex= x;
673
674         lines= 1;
675         start= 0;
676         end= max;
677         for(i=0; i<len; i++) {
678                 if(i-start >= max) {
679                         /* skip hidden part of line */
680                         if(skip) {
681                                 skip--;
682                                 start= end;
683                                 end += max;
684                                 continue;
685                         }
686
687                         /* Draw the visible portion of text on the overshot line */
688                         for(a=start; a<end; a++) {
689                                 if(st->showsyntax && format) format_draw_color(format[a]);
690                                 x += text_font_draw_character(st, x, y, str[a]);
691                         }
692                         y -= st->lheight;
693                         x= basex;
694                         lines++;
695                         start= end;
696                         end += max;
697
698                         if(y<=0) break;
699                 }
700                 else if(str[i]==' ' || str[i]=='-') {
701                         end = i+1;
702                 }
703         }
704
705         /* Draw the remaining text */
706         for(a=start; a<len && y > 0; a++) {
707                 if(st->showsyntax && format)
708                         format_draw_color(format[a]);
709
710                 x += text_font_draw_character(st, x, y, str[a]);
711         }
712
713         flatten_string_free(&fs);
714
715         return lines;
716 }
717
718 static int text_draw(SpaceText *st, char *str, int cshift, int maxwidth, int draw, int x, int y, char *format)
719 {
720         FlattenString fs;
721         int r=0, w= 0, amount;
722         int *acc;
723         char *in;
724
725         w= flatten_string(st, &fs, str);
726         if(w < cshift) {
727                 flatten_string_free(&fs);
728                 return 0; /* String is shorter than shift */
729         }
730         
731         in= fs.buf+cshift;
732         acc= fs.accum+cshift;
733         w= w-cshift;
734
735         if(draw) {
736                 if(st->showsyntax && format) {
737                         int a;
738                         format = format+cshift;
739                 
740                         amount = strlen(in);
741                         if(maxwidth)
742                                 amount= MIN2(amount, maxwidth);
743                         
744                         for(a = 0; a < amount; a++) {
745                                 format_draw_color(format[a]);
746                                 x += text_font_draw_character(st, x, y, in[a]);
747                         }
748                 }
749                 else {
750                         amount = strlen(in);
751                         if(maxwidth)
752                                 amount= MIN2(amount, maxwidth);
753
754                         in[amount]= 0;
755                         text_font_draw(st, x, y, in);
756                 }
757         }
758         else {
759                 while(w-- && *acc++ < maxwidth)
760                         r+= st->cwidth;
761         }
762
763         flatten_string_free(&fs);
764
765         if(cshift && r==0)
766                 return 0;
767         else if(st->showlinenrs)
768                 return r+TXT_OFFSET+TEXTXLOC;
769         else
770                 return r+TXT_OFFSET;
771 }
772
773 /************************ cache utilities *****************************/
774
775 typedef struct DrawCache {
776         int *line_height;
777         int total_lines, nlines;
778
779         /* this is needed to check cache relevance */
780         int winx, wordwrap, showlinenrs, tabnumber;
781         short lheight;
782         char cwidth;
783         char text_id[MAX_ID_NAME];
784
785         /* for partial lines recalculation */
786         short update_flag;
787         int valid_head, valid_tail; /* amount of unchanged lines */
788 } DrawCache;
789
790 static void text_drawcache_init(SpaceText *st)
791 {
792         DrawCache *drawcache= MEM_callocN(sizeof (DrawCache), "text draw cache");
793
794         drawcache->winx= -1;
795         drawcache->nlines= BLI_countlist(&st->text->lines);
796         drawcache->text_id[0]= '\0';
797
798         st->drawcache= drawcache;
799 }
800
801 static void text_update_drawcache(SpaceText *st, ARegion *ar)
802 {
803         DrawCache *drawcache;
804         int full_update= 0, nlines= 0;
805         Text *txt= st->text;
806
807         if(!st->drawcache) text_drawcache_init(st);
808
809         text_update_character_width(st);
810
811         drawcache= (DrawCache *)st->drawcache;
812         nlines= drawcache->nlines;
813
814         /* check if full cache update is needed */
815         full_update|= drawcache->winx != ar->winx;                 /* area was resized */
816         full_update|= drawcache->wordwrap != st->wordwrap;         /* word-wrapping option was toggled */
817         full_update|= drawcache->showlinenrs != st->showlinenrs; /* word-wrapping option was toggled */
818         full_update|= drawcache->tabnumber != st->tabnumber;  /* word-wrapping option was toggled */
819         full_update|= drawcache->lheight != st->lheight;      /* word-wrapping option was toggled */
820         full_update|= drawcache->cwidth != st->cwidth;        /* word-wrapping option was toggled */
821         full_update|= strncmp(drawcache->text_id, txt->id.name, MAX_ID_NAME); /* text datablock was changed */
822
823         if(st->wordwrap) {
824                 /* update line heights */
825                 if(full_update || !drawcache->line_height) {
826                         drawcache->valid_head  = 0;
827                         drawcache->valid_tail  = 0;
828                         drawcache->update_flag = 1;
829                 }
830
831                 if(drawcache->update_flag) {
832                         TextLine *line= st->text->lines.first;
833                         int lineno= 0, size, lines_count;
834                         int *fp= drawcache->line_height, *new_tail, *old_tail;
835
836                         nlines= BLI_countlist(&txt->lines);
837                         size= sizeof(int)*nlines;
838
839                         if(fp) fp= MEM_reallocN(fp, size);
840                         else fp= MEM_callocN(size, "text drawcache line_height");
841
842                         drawcache->valid_tail= drawcache->valid_head= 0;
843                         old_tail= fp + drawcache->nlines - drawcache->valid_tail;
844                         new_tail= fp + nlines - drawcache->valid_tail;
845                         memmove(new_tail, old_tail, drawcache->valid_tail);
846
847                         drawcache->total_lines= 0;
848
849                         if(st->showlinenrs)
850                                 st->linenrs_tot= (int)floor(log10((float)nlines)) + 1;
851
852                         while(line) {
853                                 if(drawcache->valid_head) { /* we're inside valid head lines */
854                                         lines_count= fp[lineno];
855                                         drawcache->valid_head--;
856                                 } else if (lineno > new_tail - fp) {  /* we-re inside valid tail lines */
857                                         lines_count= fp[lineno];
858                                 } else {
859                                         lines_count= text_get_visible_lines(st, ar, line->line);
860                                 }
861
862                                 fp[lineno]= lines_count;
863
864                                 line= line->next;
865                                 lineno++;
866                                 drawcache->total_lines+= lines_count;
867                         }
868
869                         drawcache->line_height= fp;
870                 }
871         } else {
872                 if(drawcache->line_height) {
873                         MEM_freeN(drawcache->line_height);
874                         drawcache->line_height= NULL;
875                 }
876
877                 if(full_update || drawcache->update_flag) {
878                         nlines= BLI_countlist(&txt->lines);
879
880                         if(st->showlinenrs)
881                                 st->linenrs_tot= (int)floor(log10((float)nlines)) + 1;
882                 }
883
884                 drawcache->total_lines= nlines;
885         }
886
887         drawcache->nlines= nlines;
888
889         /* store settings */
890         drawcache->winx        = ar->winx;
891         drawcache->wordwrap    = st->wordwrap;
892         drawcache->lheight     = st->lheight;
893         drawcache->cwidth      = st->cwidth;
894         drawcache->showlinenrs = st->showlinenrs;
895         drawcache->tabnumber   = st->tabnumber;
896
897         strncpy(drawcache->text_id, txt->id.name, MAX_ID_NAME);
898
899         /* clear update flag */
900         drawcache->update_flag = 0;
901         drawcache->valid_head  = 0;
902         drawcache->valid_tail  = 0;
903 }
904
905 void text_drawcache_tag_update(SpaceText *st, int full)
906 {
907         DrawCache *drawcache= (DrawCache *)st->drawcache;
908
909         if(drawcache) {
910                 Text *txt= st->text;
911
912                 if(drawcache->update_flag) {
913                         /* happens when tagging update from space listener */
914                         /* should do nothing to prevent locally tagged cache be fully recalculated */
915                         return;
916                 }
917
918                 if(!full) {
919                         int sellno= BLI_findindex(&txt->lines, txt->sell);
920                         int curlno= BLI_findindex(&txt->lines, txt->curl);
921
922                         if(curlno < sellno) {
923                                 drawcache->valid_head= curlno;
924                                 drawcache->valid_tail= drawcache->nlines - sellno - 1;
925                         } else {
926                                 drawcache->valid_head= sellno;
927                                 drawcache->valid_tail= drawcache->nlines - curlno - 1;
928                         }
929
930                         /* quick cache recalculation is also used in delete operator,
931                            which could merge lines which are adjusent to current selection lines
932                            expand recalculate area to this lines */
933                         if(drawcache->valid_head>0) drawcache->valid_head--;
934                         if(drawcache->valid_tail>0) drawcache->valid_tail--;
935                 } else {
936                         drawcache->valid_head= 0;
937                         drawcache->valid_tail= 0;
938                 }
939
940                 drawcache->update_flag= 1;
941         }
942 }
943
944 void text_free_caches(SpaceText *st)
945 {
946         DrawCache *drawcache= (DrawCache *)st->drawcache;
947
948         if(drawcache) {
949                 if(drawcache->line_height)
950                         MEM_freeN(drawcache->line_height);
951
952                 MEM_freeN(drawcache);
953         }
954 }
955
956 /************************ word-wrap utilities *****************************/
957
958 /* cache should be updated in caller */
959 int text_get_visible_lines_no(SpaceText *st, int lineno)
960 {
961         DrawCache *drawcache= (DrawCache *)st->drawcache;
962
963         return drawcache->line_height[lineno];
964 }
965
966 int text_get_visible_lines(SpaceText *st, ARegion *ar, char *str)
967 {
968         int i, j, start, end, max, lines, chars;
969         char ch;
970
971         max= wrap_width(st, ar);
972         lines= 1;
973         start= 0;
974         end= max;
975         for(i= 0, j= 0; str[j] != '\0'; j++) {
976                 /* Mimic replacement of tabs */
977                 ch= str[j];
978                 if(ch=='\t') {
979                         chars= st->tabnumber-i%st->tabnumber;
980                         ch= ' ';
981                 }
982                 else chars= 1;
983
984                 while(chars--) {
985                         if(i-start >= max) {
986                                 lines++;
987                                 start= end;
988                                 end += max;
989                         }
990                         else if(ch==' ' || ch=='-') {
991                                 end= i+1;
992                         }
993
994                         i++;
995                 }
996         }
997
998         return lines;
999 }
1000
1001 int text_get_span_wrap(SpaceText *st, ARegion *ar, TextLine *from, TextLine *to)
1002 {
1003         if(st->wordwrap) {
1004                 int ret=0;
1005                 TextLine *tmp= from;
1006
1007                 /* Look forwards */
1008                 while (tmp) {
1009                         if (tmp == to) return ret;
1010                         ret+= text_get_visible_lines(st, ar, tmp->line);
1011                         tmp= tmp->next;
1012                 }
1013
1014                 return ret;
1015         } else return txt_get_span(from, to);
1016 }
1017
1018 int text_get_total_lines(SpaceText *st, ARegion *ar)
1019 {
1020         DrawCache *drawcache;
1021
1022         text_update_drawcache(st, ar);
1023         drawcache= (DrawCache *)st->drawcache;
1024
1025         return drawcache->total_lines;
1026 }
1027
1028 /* Move pointer to first visible line (top) */
1029 static TextLine *first_visible_line(SpaceText *st, ARegion *ar, int *wrap_top)
1030 {
1031         Text *text= st->text;
1032         TextLine* pline= text->lines.first;
1033         int i= st->top, lineno= 0;
1034         DrawCache *drawcache;
1035
1036         text_update_drawcache(st, ar);
1037         drawcache= (DrawCache *)st->drawcache;
1038
1039         if(wrap_top) *wrap_top= 0;
1040
1041         if(st->wordwrap) {
1042                 while(i>0 && pline) {
1043                         int lines= text_get_visible_lines_no(st, lineno);
1044
1045                         if (i-lines<0) {
1046                                 if(wrap_top) *wrap_top= i;
1047                                 break;
1048                         } else {
1049                                 pline= pline->next;
1050                                 i-= lines;
1051                                 lineno++;
1052                         }
1053                 }
1054         } else {
1055                 for(i=st->top, pline= text->lines.first; pline->next && i>0; i--)
1056                         pline= pline->next;
1057         }
1058
1059         return pline;
1060 }
1061
1062 /************************ draw scrollbar *****************************/
1063
1064 static void calc_text_rcts(SpaceText *st, ARegion *ar, rcti *scroll)
1065 {
1066         int lhlstart, lhlend, ltexth, sell_off, curl_off;
1067         short barheight, barstart, hlstart, hlend, blank_lines;
1068         short pix_available, pix_top_margin, pix_bottom_margin, pix_bardiff;
1069
1070         pix_top_margin = 8;
1071         pix_bottom_margin = 4;
1072         pix_available = ar->winy - pix_top_margin - pix_bottom_margin;
1073         ltexth= text_get_total_lines(st, ar);
1074         blank_lines = st->viewlines / 2;
1075         
1076         /* nicer code: use scroll rect for entire bar */
1077         //scroll->xmin= 5;
1078         //scroll->xmax= 17;
1079         scroll->xmin= ar->winx - 17;
1080         scroll->xmax= ar->winx - 5;
1081         scroll->ymin= 4;
1082         scroll->ymax= 4+pix_available;
1083         
1084         /* when resizing a vieport with the bar at the bottom to a greater height more blank lines will be added */
1085         if(ltexth + blank_lines < st->top + st->viewlines) {
1086                 blank_lines = st->top + st->viewlines - ltexth;
1087         }
1088         
1089         ltexth += blank_lines;
1090
1091         barheight = (ltexth > 0)? (st->viewlines*pix_available)/ltexth: 0;
1092         pix_bardiff = 0;
1093         if(barheight < 20) {
1094                 pix_bardiff = 20 - barheight; /* take into account the now non-linear sizing of the bar */      
1095                 barheight = 20;
1096         }
1097         barstart = (ltexth > 0)? ((pix_available - pix_bardiff) * st->top)/ltexth: 0;
1098
1099         st->txtbar= *scroll;
1100         st->txtbar.ymax -= barstart;
1101         st->txtbar.ymin = st->txtbar.ymax - barheight;
1102
1103         CLAMP(st->txtbar.ymin, pix_bottom_margin, ar->winy - pix_top_margin);
1104         CLAMP(st->txtbar.ymax, pix_bottom_margin, ar->winy - pix_top_margin);
1105
1106         st->pix_per_line= (pix_available > 0)? (float) ltexth/pix_available: 0;
1107         if(st->pix_per_line<.1) st->pix_per_line=.1f;
1108
1109         curl_off= text_get_span_wrap(st, ar, st->text->lines.first, st->text->curl);
1110         sell_off= text_get_span_wrap(st, ar, st->text->lines.first, st->text->sell);
1111         lhlstart = MIN2(curl_off, sell_off);
1112         lhlend = MAX2(curl_off, sell_off);
1113
1114         if(ltexth > 0) {
1115                 hlstart = (lhlstart * pix_available)/ltexth;
1116                 hlend = (lhlend * pix_available)/ltexth;
1117
1118                 /* the scrollbar is non-linear sized */
1119                 if(pix_bardiff > 0) {
1120                         /* the start of the highlight is in the current viewport */
1121                         if(ltexth && st->viewlines && lhlstart >= st->top && lhlstart <= st->top + st->viewlines) { 
1122                                 /* speed the progresion of the start of the highlight through the scrollbar */
1123                                 hlstart = ( ( (pix_available - pix_bardiff) * lhlstart) / ltexth) + (pix_bardiff * (lhlstart - st->top) / st->viewlines);       
1124                         }
1125                         else if(lhlstart > st->top + st->viewlines && hlstart < barstart + barheight && hlstart > barstart) {
1126                                 /* push hl start down */
1127                                 hlstart = barstart + barheight;
1128                         }
1129                         else if(lhlend > st->top  && lhlstart < st->top && hlstart > barstart) {
1130                                 /*fill out start */
1131                                 hlstart = barstart;
1132                         }
1133
1134                         if(hlend <= hlstart) { 
1135                                 hlend = hlstart + 2;
1136                         }
1137
1138                         /* the end of the highlight is in the current viewport */
1139                         if(ltexth && st->viewlines && lhlend >= st->top && lhlend <= st->top + st->viewlines) { 
1140                                 /* speed the progresion of the end of the highlight through the scrollbar */
1141                                 hlend = (((pix_available - pix_bardiff )*lhlend)/ltexth) + (pix_bardiff * (lhlend - st->top)/st->viewlines);    
1142                         }
1143                         else if(lhlend < st->top && hlend >= barstart - 2 && hlend < barstart + barheight) {
1144                                 /* push hl end up */
1145                                 hlend = barstart;
1146                         }                                       
1147                         else if(lhlend > st->top + st->viewlines && lhlstart < st->top + st->viewlines && hlend < barstart + barheight) {
1148                                 /* fill out end */
1149                                 hlend = barstart + barheight;
1150                         }
1151
1152                         if(hlend <= hlstart) { 
1153                                 hlstart = hlend - 2;
1154                         }       
1155                 }       
1156         }
1157         else {
1158                 hlstart = 0;
1159                 hlend = 0;
1160         }
1161
1162         if(hlend - hlstart < 2) { 
1163                 hlend = hlstart + 2;
1164         }
1165         
1166         st->txtscroll= *scroll;
1167         st->txtscroll.ymax= ar->winy - pix_top_margin - hlstart;
1168         st->txtscroll.ymin= ar->winy - pix_top_margin - hlend;
1169
1170         CLAMP(st->txtscroll.ymin, pix_bottom_margin, ar->winy - pix_top_margin);
1171         CLAMP(st->txtscroll.ymax, pix_bottom_margin, ar->winy - pix_top_margin);
1172 }
1173
1174 static void draw_textscroll(SpaceText *st, rcti *scroll)
1175 {
1176         bTheme *btheme= U.themes.first;
1177         uiWidgetColors wcol= btheme->tui.wcol_scroll;
1178         char col[3];
1179         float rad;
1180         
1181         uiWidgetScrollDraw(&wcol, scroll, &st->txtbar, (st->flags & ST_SCROLL_SELECT)?UI_SCROLL_PRESSED:0);
1182
1183         uiSetRoundBox(15);
1184         rad= 0.4f*MIN2(st->txtscroll.xmax - st->txtscroll.xmin, st->txtscroll.ymax - st->txtscroll.ymin);
1185         UI_GetThemeColor3ubv(TH_HILITE, col);
1186         glColor4ub(col[0], col[1], col[2], 48);
1187         glEnable(GL_BLEND);
1188         uiRoundBox(st->txtscroll.xmin+1, st->txtscroll.ymin, st->txtscroll.xmax-1, st->txtscroll.ymax, rad);
1189         glDisable(GL_BLEND);
1190 }
1191
1192 /************************** draw markers **************************/
1193
1194 static void draw_markers(SpaceText *st, ARegion *ar)
1195 {
1196         Text *text= st->text;
1197         TextMarker *marker, *next;
1198         TextLine *top, *line;
1199         int offl, offc, i, x1, x2, y1, y2, x, y;
1200         int topi, topy;
1201
1202         /* Move pointer to first visible line (top) */
1203         top= first_visible_line(st, ar, NULL);
1204         topi= BLI_findindex(&text->lines, top);
1205
1206         topy= txt_get_span(text->lines.first, top);
1207
1208         for(marker= text->markers.first; marker; marker= next) {
1209                 next= marker->next;
1210
1211                 /* invisible line (before top) */
1212                 if(marker->lineno<topi) continue;
1213
1214                 line= BLI_findlink(&text->lines, marker->lineno);
1215
1216                 /* Remove broken markers */
1217                 if(marker->end>line->len || marker->start>marker->end) {
1218                         BLI_freelinkN(&text->markers, marker);
1219                         continue;
1220                 }
1221
1222                 wrap_offset(st, ar, line, marker->start, &offl, &offc);
1223                 y1 = txt_get_span(top, line) - st->top + offl + topy;
1224                 x1 = text_get_char_pos(st, line->line, marker->start) - st->left + offc;
1225
1226                 wrap_offset(st, ar, line, marker->end, &offl, &offc);
1227                 y2 = txt_get_span(top, line) - st->top + offl + topy;
1228                 x2 = text_get_char_pos(st, line->line, marker->end) - st->left + offc;
1229
1230                 /* invisible part of line (before top, after last visible line) */
1231                 if(y2 < 0 || y1 > st->top+st->viewlines) continue;
1232
1233                 glColor3ub(marker->color[0], marker->color[1], marker->color[2]);
1234                 x= st->showlinenrs ? TXT_OFFSET + TEXTXLOC : TXT_OFFSET;
1235                 y= ar->winy-3;
1236
1237                 if(y1==y2) {
1238                         y -= y1*st->lheight;
1239                         glBegin(GL_LINE_LOOP);
1240                         glVertex2i(x+x2*st->cwidth+1, y);
1241                         glVertex2i(x+x1*st->cwidth-2, y);
1242                         glVertex2i(x+x1*st->cwidth-2, y-st->lheight);
1243                         glVertex2i(x+x2*st->cwidth+1, y-st->lheight);
1244                         glEnd();
1245                 }
1246                 else {
1247                         y -= y1*st->lheight;
1248                         glBegin(GL_LINE_STRIP);
1249                         glVertex2i(ar->winx, y);
1250                         glVertex2i(x+x1*st->cwidth-2, y);
1251                         glVertex2i(x+x1*st->cwidth-2, y-st->lheight);
1252                         glVertex2i(ar->winx, y-st->lheight);
1253                         glEnd();
1254                         y-=st->lheight;
1255
1256                         for(i=y1+1; i<y2; i++) {
1257                                 glBegin(GL_LINES);
1258                                 glVertex2i(x, y);
1259                                 glVertex2i(ar->winx, y);
1260                                 glVertex2i(x, y-st->lheight);
1261                                 glVertex2i(ar->winx, y-st->lheight);
1262                                 glEnd();
1263                                 y-=st->lheight;
1264                         }
1265
1266                         glBegin(GL_LINE_STRIP);
1267                         glVertex2i(x, y);
1268                         glVertex2i(x+x2*st->cwidth+1, y);
1269                         glVertex2i(x+x2*st->cwidth+1, y-st->lheight);
1270                         glVertex2i(x, y-st->lheight);
1271                         glEnd();
1272                 }
1273         }
1274 }
1275
1276 /*********************** draw documentation *******************************/
1277
1278 static void draw_documentation(SpaceText *st, ARegion *ar)
1279 {
1280         TextLine *tmp;
1281         char *docs, buf[DOC_WIDTH+1], *p;
1282         int len, i, br, lines;
1283         int boxw, boxh, l, x, y, top;
1284         
1285         if(!st || !st->text) return;
1286         if(!texttool_text_is_active(st->text)) return;
1287         
1288         docs = texttool_docs_get();
1289
1290         if(!docs) return;
1291
1292         /* Count the visible lines to the cursor */
1293         for(tmp=st->text->curl, l=-st->top; tmp; tmp=tmp->prev, l++);
1294         if(l<0) return;
1295         
1296         if(st->showlinenrs) {
1297                 x= st->cwidth*(st->text->curc-st->left) + TXT_OFFSET + TEXTXLOC - 4;
1298         }
1299         else {
1300                 x= st->cwidth*(st->text->curc-st->left) + TXT_OFFSET - 4;
1301         }
1302         if(texttool_suggest_first()) {
1303                 x += SUGG_LIST_WIDTH*st->cwidth + 50;
1304         }
1305
1306         top= y= ar->winy - st->lheight*l - 2;
1307         len= strlen(docs);
1308         boxw= DOC_WIDTH*st->cwidth + 20;
1309         boxh= (DOC_HEIGHT+1)*st->lheight;
1310
1311         /* Draw panel */
1312         UI_ThemeColor(TH_BACK);
1313         glRecti(x, y, x+boxw, y-boxh);
1314         UI_ThemeColor(TH_SHADE1);
1315         glBegin(GL_LINE_LOOP);
1316         glVertex2i(x, y);
1317         glVertex2i(x+boxw, y);
1318         glVertex2i(x+boxw, y-boxh);
1319         glVertex2i(x, y-boxh);
1320         glEnd();
1321         glBegin(GL_LINE_LOOP);
1322         glVertex2i(x+boxw-10, y-7);
1323         glVertex2i(x+boxw-4, y-7);
1324         glVertex2i(x+boxw-7, y-2);
1325         glEnd();
1326         glBegin(GL_LINE_LOOP);
1327         glVertex2i(x+boxw-10, y-boxh+7);
1328         glVertex2i(x+boxw-4, y-boxh+7);
1329         glVertex2i(x+boxw-7, y-boxh+2);
1330         glEnd();
1331         UI_ThemeColor(TH_TEXT);
1332
1333         i= 0; br= DOC_WIDTH; lines= 0; // XXX -doc_scroll;
1334         for(p=docs; *p; p++) {
1335                 if(*p == '\r' && *(++p) != '\n') *(--p)= '\n'; /* Fix line endings */
1336                 if(*p == ' ' || *p == '\t')
1337                         br= i;
1338                 else if(*p == '\n') {
1339                         buf[i]= '\0';
1340                         if(lines>=0) {
1341                                 y -= st->lheight;
1342                                 text_draw(st, buf, 0, 0, 1, x+4, y-3, NULL);
1343                         }
1344                         i= 0; br= DOC_WIDTH; lines++;
1345                 }
1346                 buf[i++]= *p;
1347                 if(i == DOC_WIDTH) { /* Reached the width, go to last break and wrap there */
1348                         buf[br]= '\0';
1349                         if(lines>=0) {
1350                                 y -= st->lheight;
1351                                 text_draw(st, buf, 0, 0, 1, x+4, y-3, NULL);
1352                         }
1353                         p -= i-br-1; /* Rewind pointer to last break */
1354                         i= 0; br= DOC_WIDTH; lines++;
1355                 }
1356                 if(lines >= DOC_HEIGHT) break;
1357         }
1358
1359         if(0 /* XXX doc_scroll*/ > 0 && lines < DOC_HEIGHT) {
1360                 // XXX doc_scroll--;
1361                 draw_documentation(st, ar);
1362         }
1363 }
1364
1365 /*********************** draw suggestion list *******************************/
1366
1367 static void draw_suggestion_list(SpaceText *st, ARegion *ar)
1368 {
1369         SuggItem *item, *first, *last, *sel;
1370         TextLine *tmp;
1371         char str[SUGG_LIST_WIDTH+1];
1372         int w, boxw=0, boxh, i, l, x, y, b, *top;
1373         
1374         if(!st || !st->text) return;
1375         if(!texttool_text_is_active(st->text)) return;
1376
1377         first = texttool_suggest_first();
1378         last = texttool_suggest_last();
1379
1380         if(!first || !last) return;
1381
1382         text_pop_suggest_list();
1383         sel = texttool_suggest_selected();
1384         top = texttool_suggest_top();
1385
1386         /* Count the visible lines to the cursor */
1387         for(tmp=st->text->curl, l=-st->top; tmp; tmp=tmp->prev, l++);
1388         if(l<0) return;
1389         
1390         if(st->showlinenrs) {
1391                 x = st->cwidth*(st->text->curc-st->left) + TXT_OFFSET + TEXTXLOC - 4;
1392         }
1393         else {
1394                 x = st->cwidth*(st->text->curc-st->left) + TXT_OFFSET - 4;
1395         }
1396         y = ar->winy - st->lheight*l - 2;
1397
1398         boxw = SUGG_LIST_WIDTH*st->cwidth + 20;
1399         boxh = SUGG_LIST_SIZE*st->lheight + 8;
1400         
1401         UI_ThemeColor(TH_SHADE1);
1402         glRecti(x-1, y+1, x+boxw+1, y-boxh-1);
1403         UI_ThemeColor(TH_BACK);
1404         glRecti(x, y, x+boxw, y-boxh);
1405
1406         /* Set the top 'item' of the visible list */
1407         for(i=0, item=first; i<*top && item->next; i++, item=item->next);
1408
1409         for(i=0; i<SUGG_LIST_SIZE && item; i++, item=item->next) {
1410
1411                 y -= st->lheight;
1412
1413                 strncpy(str, item->name, SUGG_LIST_WIDTH);
1414                 str[SUGG_LIST_WIDTH] = '\0';
1415
1416                 w = text_font_width(st, str);
1417                 
1418                 if(item == sel) {
1419                         UI_ThemeColor(TH_SHADE2);
1420                         glRecti(x+16, y-3, x+16+w, y+st->lheight-3);
1421                 }
1422                 b=1; /* b=1 colour block, text is default. b=0 no block, colour text */
1423                 switch (item->type) {
1424                         case 'k': UI_ThemeColor(TH_SYNTAX_B); b=0; break;
1425                         case 'm': UI_ThemeColor(TH_TEXT); break;
1426                         case 'f': UI_ThemeColor(TH_SYNTAX_L); break;
1427                         case 'v': UI_ThemeColor(TH_SYNTAX_N); break;
1428                         case '?': UI_ThemeColor(TH_TEXT); b=0; break;
1429                 }
1430                 if(b) {
1431                         glRecti(x+8, y+2, x+11, y+5);
1432                         UI_ThemeColor(TH_TEXT);
1433                 }
1434                 text_draw(st, str, 0, 0, 1, x+16, y-1, NULL);
1435
1436                 if(item == last) break;
1437         }
1438 }
1439
1440 /*********************** draw cursor ************************/
1441
1442 static void draw_cursor(SpaceText *st, ARegion *ar)
1443 {
1444         Text *text= st->text;
1445         int vcurl, vcurc, vsell, vselc, hidden=0;
1446         int offl, offc, x, y, w, i;
1447
1448         /* Draw the selection */
1449         if(text->curl!=text->sell || text->curc!=text->selc) {
1450                 /* Convert all to view space character coordinates */
1451                 wrap_offset(st, ar, text->curl, text->curc, &offl, &offc);
1452                 vcurl = txt_get_span(text->lines.first, text->curl) - st->top + offl;
1453                 vcurc = text_get_char_pos(st, text->curl->line, text->curc) - st->left + offc;
1454                 wrap_offset(st, ar, text->sell, text->selc, &offl, &offc);
1455                 vsell = txt_get_span(text->lines.first, text->sell) - st->top + offl;
1456                 vselc = text_get_char_pos(st, text->sell->line, text->selc) - st->left + offc;
1457
1458                 if(vcurc<0) vcurc=0;
1459                 if(vselc<0) vselc=0, hidden=1;
1460                 
1461                 UI_ThemeColor(TH_SHADE2);
1462                 x= st->showlinenrs ? TXT_OFFSET + TEXTXLOC : TXT_OFFSET;
1463                 y= ar->winy-2;
1464
1465                 if(vcurl==vsell) {
1466                         y -= vcurl*st->lheight;
1467                         if(vcurc < vselc)
1468                                 glRecti(x+vcurc*st->cwidth-1, y, x+vselc*st->cwidth, y-st->lheight);
1469                         else
1470                                 glRecti(x+vselc*st->cwidth-1, y, x+vcurc*st->cwidth, y-st->lheight);
1471                 }
1472                 else {
1473                         int froml, fromc, tol, toc;
1474
1475                         if(vcurl < vsell) {
1476                                 froml= vcurl; tol= vsell;
1477                                 fromc= vcurc; toc= vselc;
1478                         }
1479                         else {
1480                                 froml= vsell; tol= vcurl;
1481                                 fromc= vselc; toc= vcurc;
1482                         }
1483
1484                         y -= froml*st->lheight;
1485                         glRecti(x+fromc*st->cwidth-1, y, ar->winx, y-st->lheight); y-=st->lheight;
1486                         for(i=froml+1; i<tol; i++)
1487                                 glRecti(x-4, y, ar->winx, y-st->lheight),  y-=st->lheight;
1488
1489                         glRecti(x-4, y, x+toc*st->cwidth, y-st->lheight);  y-=st->lheight;
1490                 }
1491         }
1492         else {
1493                 wrap_offset(st, ar, text->sell, text->selc, &offl, &offc);
1494                 vsell = txt_get_span(text->lines.first, text->sell) - st->top + offl;
1495                 vselc = text_get_char_pos(st, text->sell->line, text->selc) - st->left + offc;
1496
1497                 if(vselc<0) {
1498                         vselc= 0;
1499                         hidden= 1;
1500                 }
1501         }
1502
1503         if(st->line_hlight) {
1504                 int x1, x2, y1, y2;
1505
1506                 if(st->wordwrap) {
1507                         int visible_lines = text_get_visible_lines(st, ar, text->sell->line);
1508                         int offl, offc;
1509
1510                         wrap_offset_in_line(st, ar, text->sell, text->selc, &offl, &offc);
1511
1512                         y1= ar->winy-2 - (vsell-offl)*st->lheight;
1513                         y2= y1-st->lheight*visible_lines+1;
1514                 } else {
1515                         y1= ar->winy-2 - vsell*st->lheight;
1516                         y2= y1-st->lheight+1;
1517                 }
1518
1519                 if(!(y1<0 || y2 > ar->winy)) { /* check we need to draw */
1520                         x1= st->showlinenrs ? TXT_OFFSET + TEXTXLOC : TXT_OFFSET;
1521                         x2= x1 + ar->winx;
1522
1523                         glColor4ub(255, 255, 255, 32);
1524                         
1525                         glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1526                         glEnable(GL_BLEND);
1527                         glRecti(x1-4, y1, x2, y2);
1528                         glDisable(GL_BLEND);
1529                 }
1530         }
1531         
1532         if(!hidden) {
1533                 /* Draw the cursor itself (we draw the sel. cursor as this is the leading edge) */
1534                 x= st->showlinenrs ? TXT_OFFSET + TEXTXLOC : TXT_OFFSET;
1535                 x += vselc*st->cwidth;
1536                 y= ar->winy-2 - vsell*st->lheight;
1537                 
1538                 if(st->overwrite) {
1539                         char ch= text->sell->line[text->selc];
1540                         
1541                         w= st->cwidth;
1542                         if(ch=='\t')  w*= st->tabnumber-(vselc+st->left)%st->tabnumber;
1543                         
1544                         UI_ThemeColor(TH_HILITE);
1545                         glRecti(x, y-st->lheight-1, x+w, y-st->lheight+1);
1546                 }
1547                 else {
1548                         UI_ThemeColor(TH_HILITE);
1549                         glRecti(x-1, y, x+1, y-st->lheight);
1550                 }
1551         }
1552 }
1553
1554 /******************* draw matching brackets *********************/
1555
1556 static void draw_brackets(SpaceText *st, ARegion *ar)
1557 {
1558         TextLine *startl, *endl, *linep;
1559         Text *text = st->text;
1560         int b, c, startc, endc, find, stack;
1561         int viewc, viewl, offl, offc, x, y;
1562         char ch;
1563
1564         // showsyntax must be on or else the format string will be null
1565         if(!text->curl || !st->showsyntax) return;
1566
1567         startl= text->curl;
1568         startc= text->curc;
1569         b= text_check_bracket(startl->line[startc]);
1570         if(b==0 && startc>0) b = text_check_bracket(startl->line[--startc]);
1571         if(b==0) return;
1572         
1573         linep= startl;
1574         c= startc;
1575         endl= NULL;
1576         endc= -1;
1577         find= -b;
1578         stack= 0;
1579         
1580         /* Dont highlight backets if syntax HL is off or bracket in string or comment. */
1581         if(!linep->format || linep->format[c] == 'l' || linep->format[c] == '#')
1582                 return;
1583
1584         if(b>0) {
1585                 /* opening bracket, search forward for close */
1586                 c++;
1587                 while(linep) {
1588                         while(c<linep->len) {
1589                                 if(linep->format && linep->format[c] != 'l' && linep->format[c] != '#') {
1590                                         b= text_check_bracket(linep->line[c]);
1591                                         if(b==find) {
1592                                                 if(stack==0) {
1593                                                         endl= linep;
1594                                                         endc= c;
1595                                                         break;
1596                                                 }
1597                                                 stack--;
1598                                         }
1599                                         else if(b==-find) {
1600                                                 stack++;
1601                                         }
1602                                 }
1603                                 c++;
1604                         }
1605                         if(endl) break;
1606                         linep= linep->next;
1607                         c= 0;
1608                 }
1609         }
1610         else {
1611                 /* closing bracket, search backward for open */
1612                 c--;
1613                 while(linep) {
1614                         while(c>=0) {
1615                                 if(linep->format && linep->format[c] != 'l' && linep->format[c] != '#') {
1616                                         b= text_check_bracket(linep->line[c]);
1617                                         if(b==find) {
1618                                                 if(stack==0) {
1619                                                         endl= linep;
1620                                                         endc= c;
1621                                                         break;
1622                                                 }
1623                                                 stack--;
1624                                         }
1625                                         else if(b==-find) {
1626                                                 stack++;
1627                                         }
1628                                 }
1629                                 c--;
1630                         }
1631                         if(endl) break;
1632                         linep= linep->prev;
1633                         if(linep) c= linep->len-1;
1634                 }
1635         }
1636
1637         if(!endl || endc==-1)
1638                 return;
1639
1640         UI_ThemeColor(TH_HILITE);       
1641         x= st->showlinenrs ? TXT_OFFSET + TEXTXLOC : TXT_OFFSET;
1642         y= ar->winy - st->lheight;
1643
1644         /* draw opening bracket */
1645         ch= startl->line[startc];
1646         wrap_offset(st, ar, startl, startc, &offl, &offc);
1647         viewc= text_get_char_pos(st, startl->line, startc) - st->left + offc;
1648
1649         if(viewc >= 0){
1650                 viewl= txt_get_span(text->lines.first, startl) - st->top + offl;
1651
1652                 text_font_draw_character(st, x+viewc*st->cwidth, y-viewl*st->lheight, ch);
1653                 text_font_draw_character(st, x+viewc*st->cwidth+1, y-viewl*st->lheight, ch);
1654         }
1655
1656         /* draw closing bracket */
1657         ch= endl->line[endc];
1658         wrap_offset(st, ar, endl, endc, &offl, &offc);
1659         viewc= text_get_char_pos(st, endl->line, endc) - st->left + offc;
1660
1661         if(viewc >= 0) {
1662                 viewl= txt_get_span(text->lines.first, endl) - st->top + offl;
1663
1664                 text_font_draw_character(st, x+viewc*st->cwidth, y-viewl*st->lheight, ch);
1665                 text_font_draw_character(st, x+viewc*st->cwidth+1, y-viewl*st->lheight, ch);
1666         }
1667 }
1668
1669 /*********************** main area drawing *************************/
1670
1671 void draw_text_main(SpaceText *st, ARegion *ar)
1672 {
1673         Text *text= st->text;
1674         TextLine *tmp;
1675         rcti scroll;
1676         char linenr[12];
1677         int i, x, y, winx, linecount= 0, lineno= 0;
1678         int wraplinecount= 0, wrap_skip= 0;
1679
1680         /* if no text, nothing to do */
1681         if(!text)
1682                 return;
1683         
1684         text_update_drawcache(st, ar);
1685
1686         /* make sure all the positional pointers exist */
1687         if(!text->curl || !text->sell || !text->lines.first || !text->lines.last)
1688                 txt_clean_text(text);
1689         
1690         if(st->lheight) st->viewlines= (int)ar->winy/st->lheight;
1691         else st->viewlines= 0;
1692         
1693         /* update rects for scroll */
1694         calc_text_rcts(st, ar, &scroll);        /* scroll will hold the entire bar size */
1695
1696         /* update syntax formatting if needed */
1697         tmp= text->lines.first;
1698         lineno= 0;
1699         for(i= 0; i<st->top && tmp; i++) {
1700                 if(st->showsyntax && !tmp->format)
1701                         txt_format_line(st, tmp, 0);
1702
1703                 if(st->wordwrap) {
1704                         int lines= text_get_visible_lines_no(st, lineno);
1705
1706                         if (wraplinecount+lines>st->top) {
1707                                 wrap_skip= st->top-wraplinecount;
1708                                 break;
1709                         } else {
1710                                 wraplinecount+= lines;
1711                                 tmp= tmp->next;
1712                                 linecount++;
1713                         }
1714                 } else {
1715                         tmp= tmp->next;
1716                         linecount++;
1717                 }
1718
1719                 lineno++;
1720         }
1721
1722         text_font_begin(st);
1723         st->cwidth= BLF_fixed_width(mono);
1724         st->cwidth= MAX2(st->cwidth, 1);
1725
1726         /* draw line numbers background */
1727         if(st->showlinenrs) {
1728                 x= TXT_OFFSET + TEXTXLOC;
1729
1730                 UI_ThemeColor(TH_GRID);
1731                 glRecti((TXT_OFFSET-12), 0, (TXT_OFFSET-5) + TEXTXLOC, ar->winy - 2);
1732         }
1733         else {
1734                 st->linenrs_tot= 0; /* not used */
1735                 x= TXT_OFFSET;
1736         }
1737         y= ar->winy-st->lheight;
1738         winx= ar->winx - TXT_SCROLL_WIDTH;
1739         
1740         /* draw cursor */
1741         draw_cursor(st, ar);
1742
1743         /* draw the text */
1744         UI_ThemeColor(TH_TEXT);
1745
1746         for(i=0; y>0 && i<st->viewlines && tmp; i++, tmp= tmp->next) {
1747                 if(st->showsyntax && !tmp->format)
1748                         txt_format_line(st, tmp, 0);
1749
1750                 if(st->showlinenrs && !wrap_skip) {
1751                         /* draw line number */
1752                         if(tmp == text->curl)
1753                                 UI_ThemeColor(TH_HILITE);
1754                         else
1755                                 UI_ThemeColor(TH_TEXT);
1756
1757                         sprintf(linenr, "%d", i + linecount + 1);
1758                         /* itoa(i + linecount + 1, linenr, 10); */ /* not ansi-c :/ */
1759                         text_font_draw(st, TXT_OFFSET - 7, y, linenr);
1760
1761                         UI_ThemeColor(TH_TEXT);
1762                 }
1763
1764                 if(st->wordwrap) {
1765                         /* draw word wrapped text */
1766                         int lines = text_draw_wrapped(st, tmp->line, x, y, winx-x, tmp->format, wrap_skip);
1767                         y -= lines*st->lheight;
1768                 }
1769                 else {
1770                         /* draw unwrapped text */
1771                         text_draw(st, tmp->line, st->left, ar->winx/st->cwidth, 1, x, y, tmp->format);
1772                         y -= st->lheight;
1773                 }
1774
1775                 wrap_skip= 0;
1776         }
1777         
1778         /* draw other stuff */
1779         draw_brackets(st, ar);
1780         draw_markers(st, ar);
1781         glTranslatef(0.375f, 0.375f, 0.0f); /* XXX scroll requires exact pixel space */
1782         draw_textscroll(st, &scroll);
1783         draw_documentation(st, ar);
1784         draw_suggestion_list(st, ar);
1785         
1786         text_font_end(st);
1787 }
1788
1789 /************************** update ***************************/
1790
1791 void text_update_character_width(SpaceText *st)
1792 {
1793         text_font_begin(st);
1794         st->cwidth= BLF_fixed_width(mono);
1795         st->cwidth= MAX2(st->cwidth, 1);
1796         text_font_end(st);
1797 }
1798
1799 /* Moves the view to the cursor location,
1800   also used to make sure the view isnt outside the file */
1801 void text_update_cursor_moved(bContext *C)
1802 {
1803         ScrArea *sa= CTX_wm_area(C);
1804         SpaceText *st= CTX_wm_space_text(C);
1805         Text *text;
1806         ARegion *ar;
1807         int i, x, winx= 0;
1808
1809         if(ELEM3(NULL, st, st->text, st->text->curl)) return;
1810
1811         text= st->text;
1812
1813         for(ar=sa->regionbase.first; ar; ar= ar->next)
1814                 if(ar->regiontype==RGN_TYPE_WINDOW)
1815                         winx= ar->winx;
1816         
1817         winx -= TXT_SCROLL_WIDTH;
1818
1819         text_update_character_width(st);
1820
1821         i= txt_get_span(text->lines.first, text->sell);
1822         if(st->wordwrap) {
1823                 int offl, offc;
1824                 wrap_offset(st, CTX_wm_region(C), text->sell, text->selc, &offl, &offc);
1825                 i+= offl;
1826         }
1827
1828         if(st->top+st->viewlines <= i || st->top > i)
1829                 st->top= i - st->viewlines/2;
1830         
1831         if(st->wordwrap) {
1832                 st->left= 0;
1833         }
1834         else {
1835                 x= text_draw(st, text->sell->line, st->left, text->selc, 0, 0, 0, NULL);
1836
1837                 if(x==0 || x>winx)
1838                         st->left= text->curc-0.5*winx/st->cwidth;
1839         }
1840
1841         if(st->top < 0) st->top= 0;
1842         if(st->left <0) st->left= 0;
1843 }
1844