Util function to determine number of digits from an integer
[blender.git] / source / blender / editors / space_text / text_draw.c
1 /*
2  * ***** BEGIN GPL LICENSE BLOCK *****
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * GNU General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License
14  * along with this program; if not, write to the Free Software Foundation,
15  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16  *
17  * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
18  * All rights reserved.
19  *
20  * The Original Code is: all of this file.
21  *
22  * Contributor(s): none yet.
23  *
24  * ***** END GPL LICENSE BLOCK *****
25  */
26
27 /** \file blender/editors/space_text/text_draw.c
28  *  \ingroup sptext
29  */
30
31 #include "MEM_guardedalloc.h"
32
33 #include "BLF_api.h"
34
35 #include "BLI_blenlib.h"
36 #include "BLI_math.h"
37
38 #include "DNA_text_types.h"
39 #include "DNA_space_types.h"
40 #include "DNA_screen_types.h"
41
42 #include "BKE_context.h"
43 #include "BKE_suggestions.h"
44 #include "BKE_text.h"
45 #include "BKE_screen.h"
46
47 #include "ED_text.h"
48
49 #include "BIF_gl.h"
50 #include "BIF_glutil.h"
51
52 #include "UI_interface.h"
53 #include "UI_resources.h"
54 #include "UI_view2d.h"
55
56 #include "text_intern.h"
57 #include "text_format.h"
58
59 /******************** text font drawing ******************/
60
61 typedef struct TextDrawContext {
62         int font_id;
63         int cwidth;
64         int lheight_dpi;
65 } TextDrawContext;
66
67 static void text_draw_context_init(const SpaceText *st, TextDrawContext *tdc)
68 {
69         tdc->font_id = blf_mono_font;
70         tdc->cwidth = 0;
71         tdc->lheight_dpi = st->lheight_dpi;
72 }
73
74 static void text_font_begin(const TextDrawContext *tdc)
75 {
76         BLF_size(tdc->font_id, tdc->lheight_dpi, 72);
77 }
78
79 static void text_font_end(const TextDrawContext *UNUSED(tdc))
80 {
81 }
82
83 static int text_font_draw(const TextDrawContext *tdc, int x, int y, const char *str)
84 {
85         int columns;
86
87         BLF_position(tdc->font_id, x, y, 0);
88         columns = BLF_draw_mono(tdc->font_id, str, BLF_DRAW_STR_DUMMY_MAX, tdc->cwidth);
89
90         return tdc->cwidth * columns;
91 }
92
93 static int text_font_draw_character(const TextDrawContext *tdc, int x, int y, char c)
94 {
95         BLF_position(tdc->font_id, x, y, 0);
96         BLF_draw(tdc->font_id, &c, 1);
97
98         return tdc->cwidth;
99 }
100
101 static int text_font_draw_character_utf8(const TextDrawContext *tdc, int x, int y, const char *c)
102 {
103         int columns;
104
105         const size_t len = BLI_str_utf8_size_safe(c);
106         BLF_position(tdc->font_id, x, y, 0);
107         columns = BLF_draw_mono(tdc->font_id, c, len, tdc->cwidth);
108
109         return tdc->cwidth * columns;
110 }
111
112 #if 0
113 /* Formats every line of the current text */
114 static void txt_format_text(SpaceText *st) 
115 {
116         TextLine *linep;
117
118         if (!st->text) return;
119
120         for (linep = st->text->lines.first; linep; linep = linep->next)
121                 txt_format_line(st, linep, 0);
122 }
123 #endif
124
125 /* Sets the current drawing color based on the format character specified */
126 static void format_draw_color(char formatchar)
127 {
128         switch (formatchar) {
129                 case FMT_TYPE_WHITESPACE:
130                         break;
131                 case FMT_TYPE_SYMBOL:
132                         UI_ThemeColor(TH_SYNTAX_S);
133                         break;
134                 case FMT_TYPE_COMMENT:
135                         UI_ThemeColor(TH_SYNTAX_C);
136                         break;
137                 case FMT_TYPE_NUMERAL:
138                         UI_ThemeColor(TH_SYNTAX_N);
139                         break;
140                 case FMT_TYPE_STRING:
141                         UI_ThemeColor(TH_SYNTAX_L);
142                         break;
143                 case FMT_TYPE_DIRECTIVE:
144                         UI_ThemeColor(TH_SYNTAX_D);
145                         break;
146                 case FMT_TYPE_SPECIAL:
147                         UI_ThemeColor(TH_SYNTAX_V);
148                         break;
149                 case FMT_TYPE_RESERVED:
150                         UI_ThemeColor(TH_SYNTAX_R);
151                         break;
152                 case FMT_TYPE_KEYWORD:
153                         UI_ThemeColor(TH_SYNTAX_B);
154                         break;
155                 case FMT_TYPE_DEFAULT:
156                 default:
157                         UI_ThemeColor(TH_TEXT);
158                         break;
159         }
160 }
161
162 /************************** draw text *****************************/
163
164 /* Notes on word-wrap
165  * --
166  * All word-wrap functions follow the algorithm below to maintain consistency.
167  *     line        The line to wrap (tabs converted to spaces)
168  *     view_width    The maximum number of characters displayable in the region
169  *                 This equals region_width/font_width for the region
170  *     wrap_chars    Characters that allow wrapping. This equals [' ', '\t', '-']
171  * 
172  * def wrap(line, view_width, wrap_chars):
173  *     draw_start = 0
174  *     draw_end = view_width
175  *     pos = 0
176  *     for c in line:
177  *         if pos-draw_start >= view_width:
178  *             print line[draw_start:draw_end]
179  *             draw_start = draw_end
180  *             draw_end += view_width
181  *         elif c in wrap_chars:
182  *             draw_end = pos+1
183  *         pos += 1
184  *     print line[draw_start:]
185  * 
186  */
187
188 int wrap_width(const SpaceText *st, ARegion *ar)
189 {
190         int winx = ar->winx - TXT_SCROLL_WIDTH;
191         int x, max;
192         
193         x = st->showlinenrs ? TXT_OFFSET + TEXTXLOC : TXT_OFFSET;
194         max = st->cwidth ? (winx - x) / st->cwidth : 0;
195         return max > 8 ? max : 8;
196 }
197
198 /* Sets (offl, offc) for transforming (line, curs) to its wrapped position */
199 void wrap_offset(const SpaceText *st, ARegion *ar, TextLine *linein, int cursin, int *offl, int *offc)
200 {
201         Text *text;
202         TextLine *linep;
203         int i, j, start, end, max, chop;
204         char ch;
205
206         *offl = *offc = 0;
207
208         if (!st->text) return;
209         if (!st->wordwrap) return;
210
211         text = st->text;
212
213         /* Move pointer to first visible line (top) */
214         linep = text->lines.first;
215         i = st->top;
216         while (i > 0 && linep) {
217                 int lines = text_get_visible_lines(st, ar, linep->line);
218
219                 /* Line before top */
220                 if (linep == linein) {
221                         if (lines <= i)
222                                 /* no visible part of line */
223                                 return;
224                 }
225
226                 if (i - lines < 0) {
227                         break;
228                 }
229                 else {
230                         linep = linep->next;
231                         (*offl) += lines - 1;
232                         i -= lines;
233                 }
234         }
235
236         max = wrap_width(st, ar);
237         cursin = txt_utf8_offset_to_column(linein->line, cursin);
238
239         while (linep) {
240                 start = 0;
241                 end = max;
242                 chop = 1;
243                 *offc = 0;
244                 for (i = 0, j = 0; linep->line[j]; j += BLI_str_utf8_size_safe(linep->line + j)) {
245                         int chars;
246                         int columns = BLI_str_utf8_char_width_safe(linep->line + j); /* = 1 for tab */
247
248                         /* Mimic replacement of tabs */
249                         ch = linep->line[j];
250                         if (ch == '\t') {
251                                 chars = st->tabnumber - i % st->tabnumber;
252                                 if (linep == linein && i < cursin) cursin += chars - 1;
253                                 ch = ' ';
254                         }
255                         else {
256                                 chars = 1;
257                         }
258
259                         while (chars--) {
260                                 if (i + columns - start > max) {
261                                         end = MIN2(end, i);
262
263                                         if (chop && linep == linein && i >= cursin) {
264                                                 if (i == cursin) {
265                                                         (*offl)++;
266                                                         *offc -= end - start;
267                                                 }
268
269                                                 return;
270                                         }
271
272                                         (*offl)++;
273                                         *offc -= end - start;
274
275                                         start = end;
276                                         end += max;
277                                         chop = 1;
278                                 }
279                                 else if (ch == ' ' || ch == '-') {
280                                         end = i + 1;
281                                         chop = 0;
282                                         if (linep == linein && i >= cursin)
283                                                 return;
284                                 }
285                                 i += columns;
286                         }
287                 }
288                 if (linep == linein) break;
289                 linep = linep->next;
290         }
291 }
292
293 /* cursin - mem, offc - view */
294 void wrap_offset_in_line(const SpaceText *st, ARegion *ar, TextLine *linein, int cursin, int *offl, int *offc)
295 {
296         int i, j, start, end, chars, max, chop;
297         char ch;
298
299         *offl = *offc = 0;
300
301         if (!st->text) return;
302         if (!st->wordwrap) return;
303
304         max = wrap_width(st, ar);
305
306         start = 0;
307         end = max;
308         chop = 1;
309         *offc = 0;
310         cursin = txt_utf8_offset_to_column(linein->line, cursin);
311
312         for (i = 0, j = 0; linein->line[j]; j += BLI_str_utf8_size_safe(linein->line + j)) {
313                 int columns = BLI_str_utf8_char_width_safe(linein->line + j); /* = 1 for tab */
314
315                 /* Mimic replacement of tabs */
316                 ch = linein->line[j];
317                 if (ch == '\t') {
318                         chars = st->tabnumber - i % st->tabnumber;
319                         if (i < cursin) cursin += chars - 1;
320                         ch = ' ';
321                 }
322                 else
323                         chars = 1;
324
325                 while (chars--) {
326                         if (i + columns - start > max) {
327                                 end = MIN2(end, i);
328
329                                 if (chop && i >= cursin) {
330                                         if (i == cursin) {
331                                                 (*offl)++;
332                                                 *offc -= end - start;
333                                         }
334
335                                         return;
336                                 }
337
338                                 (*offl)++;
339                                 *offc -= end - start;
340
341                                 start = end;
342                                 end += max;
343                                 chop = 1;
344                         }
345                         else if (ch == ' ' || ch == '-') {
346                                 end = i + 1;
347                                 chop = 0;
348                                 if (i >= cursin)
349                                         return;
350                         }
351                         i += columns;
352                 }
353         }
354 }
355
356 int text_get_char_pos(const SpaceText *st, const char *line, int cur)
357 {
358         int a = 0, i;
359         
360         for (i = 0; i < cur && line[i]; i += BLI_str_utf8_size_safe(line + i)) {
361                 if (line[i] == '\t')
362                         a += st->tabnumber - a % st->tabnumber;
363                 else
364                         a += BLI_str_utf8_char_width_safe(line + i);
365         }
366         return a;
367 }
368
369 static const char *txt_utf8_forward_columns(const char *str, int columns, int *padding)
370 {
371         int col;
372         const char *p = str;
373         while (*p) {
374                 col = BLI_str_utf8_char_width(p);
375                 if (columns - col < 0)
376                         break;
377                 columns -= col;
378                 p += BLI_str_utf8_size_safe(p);
379                 if (columns == 0)
380                         break;
381         }
382         if (padding)
383                 *padding = *p ? columns : 0;
384         return p;
385 }
386
387 static int text_draw_wrapped(
388         const SpaceText *st, const TextDrawContext *tdc,
389         const char *str, int x, int y, int w, const char *format, int skip)
390 {
391         const bool use_syntax = (st->showsyntax && format);
392         FlattenString fs;
393         int basex, lines;
394         int i, wrap, end, max, columns, padding; /* column */
395         /* warning, only valid when 'use_syntax' is set */
396         int a, fstart, fpos;                     /* utf8 chars */
397         int mi, ma, mstart, mend;                /* mem */
398         char fmt_prev = 0xff;
399         /* don't draw lines below this */
400         const int clip_min_y = -(int)(st->lheight_dpi - 1);
401
402         flatten_string(st, &fs, str);
403         str = fs.buf;
404         max = w / st->cwidth;
405         if (max < 8) max = 8;
406         basex = x;
407         lines = 1;
408         
409         fpos = fstart = 0; mstart = 0;
410         mend = txt_utf8_forward_columns(str, max, &padding) - str;
411         end = wrap = max - padding;
412         
413         for (i = 0, mi = 0; str[mi]; i += columns, mi += BLI_str_utf8_size_safe(str + mi)) {
414                 columns = BLI_str_utf8_char_width_safe(str + mi);
415                 if (i + columns > end) {
416                         /* skip hidden part of line */
417                         if (skip) {
418                                 skip--;
419                                 if (use_syntax) {
420                                         /* currently fpos only used when formatting */
421                                         fpos += BLI_strnlen_utf8(str + mstart, mend - mstart);
422                                 }
423                                 fstart = fpos; mstart = mend;
424                                 mend = txt_utf8_forward_columns(str + mend, max, &padding) - str;
425                                 end = (wrap += max - padding);
426                                 continue;
427                         }
428
429                         /* Draw the visible portion of text on the overshot line */
430                         for (a = fstart, ma = mstart; ma < mend; a++, ma += BLI_str_utf8_size_safe(str + ma)) {
431                                 if (use_syntax) {
432                                         if (fmt_prev != format[a]) format_draw_color(fmt_prev = format[a]);
433                                 }
434                                 x += text_font_draw_character_utf8(tdc, x, y, str + ma);
435                                 fpos++;
436                         }
437                         y -= st->lheight_dpi + TXT_LINE_SPACING;
438                         x = basex;
439                         lines++;
440                         fstart = fpos; mstart = mend;
441                         mend = txt_utf8_forward_columns(str + mend, max, &padding) - str;
442                         end = (wrap += max - padding);
443
444                         if (y <= clip_min_y)
445                                 break;
446                 }
447                 else if (str[mi] == ' ' || str[mi] == '-') {
448                         wrap = i + 1; mend = mi + 1;
449                 }
450         }
451
452         /* Draw the remaining text */
453         for (a = fstart, ma = mstart; str[ma] && y > clip_min_y; a++, ma += BLI_str_utf8_size_safe(str + ma)) {
454                 if (use_syntax) {
455                         if (fmt_prev != format[a]) format_draw_color(fmt_prev = format[a]);
456                 }
457
458                 x += text_font_draw_character_utf8(tdc, x, y, str + ma);
459         }
460
461         flatten_string_free(&fs);
462
463         return lines;
464 }
465
466 static void text_draw(
467         const SpaceText *st, const TextDrawContext *tdc,
468         char *str, int cshift, int maxwidth, int x, int y, const char *format)
469 {
470         const bool use_syntax = (st->showsyntax && format);
471         FlattenString fs;
472         int columns, size, n, w = 0, padding, amount = 0;
473         const char *in = NULL;
474
475         for (n = flatten_string(st, &fs, str), str = fs.buf; n > 0; n--) {
476                 columns = BLI_str_utf8_char_width_safe(str);
477                 size = BLI_str_utf8_size_safe(str);
478
479                 if (!in) {
480                         if (w >= cshift) {
481                                 padding = w - cshift;
482                                 in = str;
483                         }
484                         else if (format)
485                                 format++;
486                 }
487                 if (in) {
488                         if (maxwidth && w + columns > cshift + maxwidth)
489                                 break;
490                         amount++;
491                 }
492
493                 w += columns;
494                 str += size;
495         }
496         if (!in) {
497                 flatten_string_free(&fs);
498                 return; /* String is shorter than shift or ends with a padding */
499         }
500
501         x += tdc->cwidth * padding;
502
503         if (use_syntax) {
504                 int a, str_shift = 0;
505                 char fmt_prev = 0xff;
506
507                 for (a = 0; a < amount; a++) {
508                         if (format[a] != fmt_prev) format_draw_color(fmt_prev = format[a]);
509                         x += text_font_draw_character_utf8(tdc, x, y, in + str_shift);
510                         str_shift += BLI_str_utf8_size_safe(in + str_shift);
511                 }
512         }
513         else {
514                 text_font_draw(tdc, x, y, in);
515         }
516
517         flatten_string_free(&fs);
518 }
519
520 /************************ cache utilities *****************************/
521
522 typedef struct DrawCache {
523         int *line_height;
524         int total_lines, nlines;
525
526         /* this is needed to check cache relevance */
527         int winx, wordwrap, showlinenrs, tabnumber;
528         short lheight;
529         char cwidth;
530         char text_id[MAX_ID_NAME];
531
532         /* for partial lines recalculation */
533         short update_flag;
534         int valid_head, valid_tail; /* amount of unchanged lines */
535 } DrawCache;
536
537 static void text_drawcache_init(SpaceText *st)
538 {
539         DrawCache *drawcache = MEM_callocN(sizeof(DrawCache), "text draw cache");
540
541         drawcache->winx = -1;
542         drawcache->nlines = BLI_listbase_count(&st->text->lines);
543         drawcache->text_id[0] = '\0';
544
545         st->drawcache = drawcache;
546 }
547
548 static void text_update_drawcache(SpaceText *st, ARegion *ar)
549 {
550         DrawCache *drawcache;
551         int full_update = 0, nlines = 0;
552         Text *txt = st->text;
553
554         if (!st->drawcache) text_drawcache_init(st);
555
556         text_update_character_width(st);
557
558         drawcache = (DrawCache *)st->drawcache;
559         nlines = drawcache->nlines;
560
561         /* check if full cache update is needed */
562         full_update |= drawcache->winx != ar->winx;               /* area was resized */
563         full_update |= drawcache->wordwrap != st->wordwrap;       /* word-wrapping option was toggled */
564         full_update |= drawcache->showlinenrs != st->showlinenrs; /* word-wrapping option was toggled */
565         full_update |= drawcache->tabnumber != st->tabnumber;     /* word-wrapping option was toggled */
566         full_update |= drawcache->lheight != st->lheight_dpi;         /* word-wrapping option was toggled */
567         full_update |= drawcache->cwidth != st->cwidth;           /* word-wrapping option was toggled */
568         full_update |= !STREQLEN(drawcache->text_id, txt->id.name, MAX_ID_NAME); /* text datablock was changed */
569
570         if (st->wordwrap) {
571                 /* update line heights */
572                 if (full_update || !drawcache->line_height) {
573                         drawcache->valid_head  = 0;
574                         drawcache->valid_tail  = 0;
575                         drawcache->update_flag = 1;
576                 }
577
578                 if (drawcache->update_flag) {
579                         TextLine *line = st->text->lines.first;
580                         int lineno = 0, size, lines_count;
581                         int *fp = drawcache->line_height, *new_tail, *old_tail;
582
583                         nlines = BLI_listbase_count(&txt->lines);
584                         size = sizeof(int) * nlines;
585
586                         if (fp) fp = MEM_reallocN(fp, size);
587                         else fp = MEM_callocN(size, "text drawcache line_height");
588
589                         drawcache->valid_tail = drawcache->valid_head = 0;
590                         old_tail = fp + drawcache->nlines - drawcache->valid_tail;
591                         new_tail = fp + nlines - drawcache->valid_tail;
592                         memmove(new_tail, old_tail, drawcache->valid_tail);
593
594                         drawcache->total_lines = 0;
595
596                         if (st->showlinenrs)
597                                 st->linenrs_tot = integer_digits_i(nlines);
598
599                         while (line) {
600                                 if (drawcache->valid_head) { /* we're inside valid head lines */
601                                         lines_count = fp[lineno];
602                                         drawcache->valid_head--;
603                                 }
604                                 else if (lineno > new_tail - fp) {  /* we-re inside valid tail lines */
605                                         lines_count = fp[lineno];
606                                 }
607                                 else {
608                                         lines_count = text_get_visible_lines(st, ar, line->line);
609                                 }
610
611                                 fp[lineno] = lines_count;
612
613                                 line = line->next;
614                                 lineno++;
615                                 drawcache->total_lines += lines_count;
616                         }
617
618                         drawcache->line_height = fp;
619                 }
620         }
621         else {
622                 if (drawcache->line_height) {
623                         MEM_freeN(drawcache->line_height);
624                         drawcache->line_height = NULL;
625                 }
626
627                 if (full_update || drawcache->update_flag) {
628                         nlines = BLI_listbase_count(&txt->lines);
629
630                         if (st->showlinenrs)
631                                 st->linenrs_tot = integer_digits_i(nlines);
632                 }
633
634                 drawcache->total_lines = nlines;
635         }
636
637         drawcache->nlines = nlines;
638
639         /* store settings */
640         drawcache->winx        = ar->winx;
641         drawcache->wordwrap    = st->wordwrap;
642         drawcache->lheight     = st->lheight_dpi;
643         drawcache->cwidth      = st->cwidth;
644         drawcache->showlinenrs = st->showlinenrs;
645         drawcache->tabnumber   = st->tabnumber;
646
647         strncpy(drawcache->text_id, txt->id.name, MAX_ID_NAME);
648
649         /* clear update flag */
650         drawcache->update_flag = 0;
651         drawcache->valid_head  = 0;
652         drawcache->valid_tail  = 0;
653 }
654
655 void text_drawcache_tag_update(SpaceText *st, int full)
656 {
657         /* this happens if text editor ops are caled from python */
658         if (st == NULL)
659                 return;
660                 
661         if (st->drawcache) {
662                 DrawCache *drawcache = (DrawCache *)st->drawcache;
663                 Text *txt = st->text;
664
665                 if (drawcache->update_flag) {
666                         /* happens when tagging update from space listener */
667                         /* should do nothing to prevent locally tagged cache be fully recalculated */
668                         return;
669                 }
670
671                 if (!full) {
672                         int sellno = BLI_findindex(&txt->lines, txt->sell);
673                         int curlno = BLI_findindex(&txt->lines, txt->curl);
674
675                         if (curlno < sellno) {
676                                 drawcache->valid_head = curlno;
677                                 drawcache->valid_tail = drawcache->nlines - sellno - 1;
678                         }
679                         else {
680                                 drawcache->valid_head = sellno;
681                                 drawcache->valid_tail = drawcache->nlines - curlno - 1;
682                         }
683
684                         /* quick cache recalculation is also used in delete operator,
685                          * which could merge lines which are adjacent to current selection lines
686                          * expand recalculate area to this lines */
687                         if (drawcache->valid_head > 0) drawcache->valid_head--;
688                         if (drawcache->valid_tail > 0) drawcache->valid_tail--;
689                 }
690                 else {
691                         drawcache->valid_head = 0;
692                         drawcache->valid_tail = 0;
693                 }
694
695                 drawcache->update_flag = 1;
696         }
697 }
698
699 void text_free_caches(SpaceText *st)
700 {
701         DrawCache *drawcache = (DrawCache *)st->drawcache;
702
703         if (drawcache) {
704                 if (drawcache->line_height)
705                         MEM_freeN(drawcache->line_height);
706
707                 MEM_freeN(drawcache);
708         }
709 }
710
711 /************************ word-wrap utilities *****************************/
712
713 /* cache should be updated in caller */
714 static int text_get_visible_lines_no(const SpaceText *st, int lineno)
715 {
716         const DrawCache *drawcache = st->drawcache;
717
718         return drawcache->line_height[lineno];
719 }
720
721 int text_get_visible_lines(const SpaceText *st, ARegion *ar, const char *str)
722 {
723         int i, j, start, end, max, lines, chars;
724         char ch;
725
726         max = wrap_width(st, ar);
727         lines = 1;
728         start = 0;
729         end = max;
730         for (i = 0, j = 0; str[j]; j += BLI_str_utf8_size_safe(str + j)) {
731                 int columns = BLI_str_utf8_char_width_safe(str + j); /* = 1 for tab */
732
733                 /* Mimic replacement of tabs */
734                 ch = str[j];
735                 if (ch == '\t') {
736                         chars = st->tabnumber - i % st->tabnumber;
737                         ch = ' ';
738                 }
739                 else {
740                         chars = 1;
741                 }
742
743                 while (chars--) {
744                         if (i + columns - start > max) {
745                                 lines++;
746                                 start = MIN2(end, i);
747                                 end += max;
748                         }
749                         else if (ch == ' ' || ch == '-') {
750                                 end = i + 1;
751                         }
752
753                         i += columns;
754                 }
755         }
756
757         return lines;
758 }
759
760 int text_get_span_wrap(const SpaceText *st, ARegion *ar, TextLine *from, TextLine *to)
761 {
762         if (st->wordwrap) {
763                 int ret = 0;
764                 TextLine *tmp = from;
765
766                 /* Look forwards */
767                 while (tmp) {
768                         if (tmp == to) return ret;
769                         ret += text_get_visible_lines(st, ar, tmp->line);
770                         tmp = tmp->next;
771                 }
772
773                 return ret;
774         }
775         else {
776                 return txt_get_span(from, to);
777         }
778 }
779
780 int text_get_total_lines(SpaceText *st, ARegion *ar)
781 {
782         DrawCache *drawcache;
783
784         text_update_drawcache(st, ar);
785         drawcache = st->drawcache;
786
787         return drawcache->total_lines;
788 }
789
790 /************************ draw scrollbar *****************************/
791
792 static void calc_text_rcts(SpaceText *st, ARegion *ar, rcti *scroll, rcti *back)
793 {
794         int lhlstart, lhlend, ltexth, sell_off, curl_off;
795         short barheight, barstart, hlstart, hlend, blank_lines;
796         short pix_available, pix_top_margin, pix_bottom_margin, pix_bardiff;
797
798         pix_top_margin = 8;
799         pix_bottom_margin = 4;
800         pix_available = ar->winy - pix_top_margin - pix_bottom_margin;
801         ltexth = text_get_total_lines(st, ar);
802         blank_lines = st->viewlines / 2;
803
804         /* nicer code: use scroll rect for entire bar */
805         back->xmin = ar->winx - (V2D_SCROLL_WIDTH + 1);
806         back->xmax = ar->winx;
807         back->ymin = 0;
808         back->ymax = ar->winy;
809         
810         scroll->xmin = ar->winx - V2D_SCROLL_WIDTH;
811         scroll->xmax = ar->winx - 5;
812         scroll->ymin = 4;
813         scroll->ymax = 4 + pix_available;
814         
815         /* when re-sizing a view-port with the bar at the bottom to a greater height more blank lines will be added */
816         if (ltexth + blank_lines < st->top + st->viewlines) {
817                 blank_lines = st->top + st->viewlines - ltexth;
818         }
819         
820         ltexth += blank_lines;
821
822         barheight = (ltexth > 0) ? (st->viewlines * pix_available) / ltexth : 0;
823         pix_bardiff = 0;
824         if (barheight < 20) {
825                 pix_bardiff = 20 - barheight; /* take into account the now non-linear sizing of the bar */
826                 barheight = 20;
827         }
828         barstart = (ltexth > 0) ? ((pix_available - pix_bardiff) * st->top) / ltexth : 0;
829
830         st->txtbar = *scroll;
831         st->txtbar.ymax -= barstart;
832         st->txtbar.ymin = st->txtbar.ymax - barheight;
833
834         CLAMP(st->txtbar.ymin, pix_bottom_margin, ar->winy - pix_top_margin);
835         CLAMP(st->txtbar.ymax, pix_bottom_margin, ar->winy - pix_top_margin);
836
837         st->pix_per_line = (pix_available > 0) ? (float) ltexth / pix_available : 0;
838         if (st->pix_per_line < 0.1f) st->pix_per_line = 0.1f;
839
840         curl_off = text_get_span_wrap(st, ar, st->text->lines.first, st->text->curl);
841         sell_off = text_get_span_wrap(st, ar, st->text->lines.first, st->text->sell);
842         lhlstart = MIN2(curl_off, sell_off);
843         lhlend = MAX2(curl_off, sell_off);
844
845         if (ltexth > 0) {
846                 hlstart = (lhlstart * pix_available) / ltexth;
847                 hlend = (lhlend * pix_available) / ltexth;
848
849                 /* the scrollbar is non-linear sized */
850                 if (pix_bardiff > 0) {
851                         /* the start of the highlight is in the current viewport */
852                         if (st->viewlines && lhlstart >= st->top && lhlstart <= st->top + st->viewlines) {
853                                 /* speed the progresion of the start of the highlight through the scrollbar */
854                                 hlstart = ( ( (pix_available - pix_bardiff) * lhlstart) / ltexth) + (pix_bardiff * (lhlstart - st->top) / st->viewlines);
855                         }
856                         else if (lhlstart > st->top + st->viewlines && hlstart < barstart + barheight && hlstart > barstart) {
857                                 /* push hl start down */
858                                 hlstart = barstart + barheight;
859                         }
860                         else if (lhlend > st->top && lhlstart < st->top && hlstart > barstart) {
861                                 /*fill out start */
862                                 hlstart = barstart;
863                         }
864
865                         if (hlend <= hlstart) {
866                                 hlend = hlstart + 2;
867                         }
868
869                         /* the end of the highlight is in the current viewport */
870                         if (st->viewlines && lhlend >= st->top && lhlend <= st->top + st->viewlines) {
871                                 /* speed the progresion of the end of the highlight through the scrollbar */
872                                 hlend = (((pix_available - pix_bardiff) * lhlend) / ltexth) + (pix_bardiff * (lhlend - st->top) / st->viewlines);
873                         }
874                         else if (lhlend < st->top && hlend >= barstart - 2 && hlend < barstart + barheight) {
875                                 /* push hl end up */
876                                 hlend = barstart;
877                         }
878                         else if (lhlend > st->top + st->viewlines && lhlstart < st->top + st->viewlines && hlend < barstart + barheight) {
879                                 /* fill out end */
880                                 hlend = barstart + barheight;
881                         }
882
883                         if (hlend <= hlstart) {
884                                 hlstart = hlend - 2;
885                         }
886                 }
887         }
888         else {
889                 hlstart = 0;
890                 hlend = 0;
891         }
892
893         if (hlend - hlstart < 2) {
894                 hlend = hlstart + 2;
895         }
896         
897         st->txtscroll = *scroll;
898         st->txtscroll.ymax = ar->winy - pix_top_margin - hlstart;
899         st->txtscroll.ymin = ar->winy - pix_top_margin - hlend;
900
901         CLAMP(st->txtscroll.ymin, pix_bottom_margin, ar->winy - pix_top_margin);
902         CLAMP(st->txtscroll.ymax, pix_bottom_margin, ar->winy - pix_top_margin);
903 }
904
905 static void draw_textscroll(const SpaceText *st, rcti *scroll, rcti *back)
906 {
907         bTheme *btheme = UI_GetTheme();
908         uiWidgetColors wcol = btheme->tui.wcol_scroll;
909         unsigned char col[4];
910         float rad;
911         
912         UI_ThemeColor(TH_BACK);
913         glRecti(back->xmin, back->ymin, back->xmax, back->ymax);
914
915         UI_draw_widget_scroll(&wcol, scroll, &st->txtbar, (st->flags & ST_SCROLL_SELECT) ? UI_SCROLL_PRESSED : 0);
916
917         UI_draw_roundbox_corner_set(UI_CNR_ALL);
918         rad = 0.4f * min_ii(BLI_rcti_size_x(&st->txtscroll), BLI_rcti_size_y(&st->txtscroll));
919         UI_GetThemeColor3ubv(TH_HILITE, col);
920         col[3] = 48;
921         glColor4ubv(col);
922         glEnable(GL_BLEND);
923         UI_draw_roundbox(st->txtscroll.xmin + 1, st->txtscroll.ymin, st->txtscroll.xmax - 1, st->txtscroll.ymax, rad);
924         glDisable(GL_BLEND);
925 }
926
927 /*********************** draw documentation *******************************/
928
929 #if 0
930 static void draw_documentation(const SpaceText *st, ARegion *ar)
931 {
932         TextDrawContext tdc = {0};
933         TextLine *tmp;
934         char *docs, buf[DOC_WIDTH + 1], *p;
935         int i, br, lines;
936         int boxw, boxh, l, x, y /* , top */ /* UNUSED */;
937         
938         if (!st || !st->text) return;
939         if (!texttool_text_is_active(st->text)) return;
940         
941         docs = texttool_docs_get();
942
943         if (!docs) return;
944
945         text_draw_context_init(st, &tdc);
946
947         /* Count the visible lines to the cursor */
948         for (tmp = st->text->curl, l = -st->top; tmp; tmp = tmp->prev, l++) ;
949         if (l < 0) return;
950         
951         if (st->showlinenrs) {
952                 x = st->cwidth * (st->text->curc - st->left) + TXT_OFFSET + TEXTXLOC - 4;
953         }
954         else {
955                 x = st->cwidth * (st->text->curc - st->left) + TXT_OFFSET - 4;
956         }
957         if (texttool_suggest_first()) {
958                 x += SUGG_LIST_WIDTH * st->cwidth + 50;
959         }
960
961         /* top = */ /* UNUSED */ y = ar->winy - st->lheight_dpi * l - 2;
962         boxw = DOC_WIDTH * st->cwidth + 20;
963         boxh = (DOC_HEIGHT + 1) * (st->lheight_dpi + TXT_LINE_SPACING);
964
965         /* Draw panel */
966         UI_ThemeColor(TH_BACK);
967         glRecti(x, y, x + boxw, y - boxh);
968         UI_ThemeColor(TH_SHADE1);
969         glBegin(GL_LINE_LOOP);
970         glVertex2i(x, y);
971         glVertex2i(x + boxw, y);
972         glVertex2i(x + boxw, y - boxh);
973         glVertex2i(x, y - boxh);
974         glEnd();
975         glBegin(GL_LINE_LOOP);
976         glVertex2i(x + boxw - 10, y - 7);
977         glVertex2i(x + boxw - 4, y - 7);
978         glVertex2i(x + boxw - 7, y - 2);
979         glEnd();
980         glBegin(GL_LINE_LOOP);
981         glVertex2i(x + boxw - 10, y - boxh + 7);
982         glVertex2i(x + boxw - 4, y - boxh + 7);
983         glVertex2i(x + boxw - 7, y - boxh + 2);
984         glEnd();
985         UI_ThemeColor(TH_TEXT);
986
987         i = 0; br = DOC_WIDTH; lines = 0; // XXX -doc_scroll;
988         for (p = docs; *p; p++) {
989                 if (*p == '\r' && *(++p) != '\n') *(--p) = '\n';  /* Fix line endings */
990                 if (*p == ' ' || *p == '\t')
991                         br = i;
992                 else if (*p == '\n') {
993                         buf[i] = '\0';
994                         if (lines >= 0) {
995                                 y -= st->lheight_dpi;
996                                 text_draw(st, &tdc, buf, 0, 0, x + 4, y - 3, NULL);
997                         }
998                         i = 0; br = DOC_WIDTH; lines++;
999                 }
1000                 buf[i++] = *p;
1001                 if (i == DOC_WIDTH) { /* Reached the width, go to last break and wrap there */
1002                         buf[br] = '\0';
1003                         if (lines >= 0) {
1004                                 y -= st->lheight_dpi;
1005                                 text_draw(st, &tdc, buf, 0, 0, x + 4, y - 3, NULL);
1006                         }
1007                         p -= i - br - 1; /* Rewind pointer to last break */
1008                         i = 0; br = DOC_WIDTH; lines++;
1009                 }
1010                 if (lines >= DOC_HEIGHT) break;
1011         }
1012
1013         if (0 /* XXX doc_scroll*/ /* > 0 && lines < DOC_HEIGHT */) {
1014                 // XXX doc_scroll--;
1015                 draw_documentation(st, ar);
1016         }
1017 }
1018 #endif
1019
1020 /*********************** draw suggestion list *******************************/
1021
1022 static void draw_suggestion_list(const SpaceText *st, const TextDrawContext *tdc, ARegion *ar)
1023 {
1024         SuggItem *item, *first, *last, *sel;
1025         char str[SUGG_LIST_WIDTH * BLI_UTF8_MAX + 1];
1026         int offl, offc, vcurl, vcurc;
1027         int w, boxw = 0, boxh, i, x, y, *top;
1028         const int lheight = st->lheight_dpi + TXT_LINE_SPACING;
1029         const int margin_x = 2;
1030         
1031         if (!st->text) return;
1032         if (!texttool_text_is_active(st->text)) return;
1033
1034         first = texttool_suggest_first();
1035         last = texttool_suggest_last();
1036
1037         if (!first || !last) return;
1038
1039         text_pop_suggest_list();
1040         sel = texttool_suggest_selected();
1041         top = texttool_suggest_top();
1042
1043         wrap_offset(st, ar, st->text->curl, st->text->curc, &offl, &offc);
1044         vcurl = txt_get_span(st->text->lines.first, st->text->curl) - st->top + offl;
1045         vcurc = text_get_char_pos(st, st->text->curl->line, st->text->curc) - st->left + offc;
1046
1047         x = st->showlinenrs ? TXT_OFFSET + TEXTXLOC : TXT_OFFSET;
1048         x += vcurc * st->cwidth - 4;
1049         y = ar->winy - (vcurl + 1) * lheight - 2;
1050
1051         /* offset back so the start of the text lines up with the suggestions,
1052          * not essential but makes suggestions easier to follow */
1053         x -= st->cwidth * (st->text->curc - text_find_identifier_start(st->text->curl->line, st->text->curc));
1054
1055         boxw = SUGG_LIST_WIDTH * st->cwidth + 20;
1056         boxh = SUGG_LIST_SIZE * lheight + 8;
1057         
1058         if (x + boxw > ar->winx)
1059                 x = MAX2(0, ar->winx - boxw);
1060
1061         /* not needed but stands out nicer */
1062         UI_draw_box_shadow(220, x, y - boxh, x + boxw, y);
1063
1064         UI_ThemeColor(TH_SHADE1);
1065         glRecti(x - 1, y + 1, x + boxw + 1, y - boxh - 1);
1066         UI_ThemeColorShade(TH_BACK, 16);
1067         glRecti(x, y, x + boxw, y - boxh);
1068
1069         /* Set the top 'item' of the visible list */
1070         for (i = 0, item = first; i < *top && item->next; i++, item = item->next) ;
1071
1072         for (i = 0; i < SUGG_LIST_SIZE && item; i++, item = item->next) {
1073                 int len = txt_utf8_forward_columns(item->name, SUGG_LIST_WIDTH, NULL) - item->name;
1074
1075                 y -= lheight;
1076
1077                 BLI_strncpy(str, item->name, len + 1);
1078
1079                 w = st->cwidth * text_get_char_pos(st, str, len);
1080                 
1081                 if (item == sel) {
1082                         UI_ThemeColor(TH_SHADE2);
1083                         glRecti(x + margin_x, y - 3, x + margin_x + w, y + lheight - 3);
1084                 }
1085
1086                 format_draw_color(item->type);
1087                 text_draw(st, tdc, str, 0, 0, x + margin_x, y - 1, NULL);
1088
1089                 if (item == last) break;
1090         }
1091 }
1092
1093 /*********************** draw cursor ************************/
1094
1095 static void draw_cursor(SpaceText *st, ARegion *ar)
1096 {
1097         Text *text = st->text;
1098         int vcurl, vcurc, vsell, vselc, hidden = 0;
1099         int x, y, w, i;
1100         const int lheight = st->lheight_dpi + TXT_LINE_SPACING;
1101
1102         /* Draw the selection */
1103         if (text->curl != text->sell || text->curc != text->selc) {
1104                 int offl, offc;
1105                 /* Convert all to view space character coordinates */
1106                 wrap_offset(st, ar, text->curl, text->curc, &offl, &offc);
1107                 vcurl = txt_get_span(text->lines.first, text->curl) - st->top + offl;
1108                 vcurc = text_get_char_pos(st, text->curl->line, text->curc) - st->left + offc;
1109                 wrap_offset(st, ar, text->sell, text->selc, &offl, &offc);
1110                 vsell = txt_get_span(text->lines.first, text->sell) - st->top + offl;
1111                 vselc = text_get_char_pos(st, text->sell->line, text->selc) - st->left + offc;
1112
1113                 if (vcurc < 0) {
1114                         vcurc = 0;
1115                 }
1116                 if (vselc < 0) {
1117                         vselc = 0;
1118                         hidden = 1;
1119                 }
1120                 
1121                 UI_ThemeColor(TH_SHADE2);
1122                 x = st->showlinenrs ? TXT_OFFSET + TEXTXLOC : TXT_OFFSET;
1123                 y = ar->winy;
1124
1125                 if (vcurl == vsell) {
1126                         y -= vcurl * lheight;
1127                         if (vcurc < vselc)
1128                                 glRecti(x + vcurc * st->cwidth - 1, y, x + vselc * st->cwidth, y - lheight);
1129                         else
1130                                 glRecti(x + vselc * st->cwidth - 1, y, x + vcurc * st->cwidth, y - lheight);
1131                 }
1132                 else {
1133                         int froml, fromc, tol, toc;
1134
1135                         if (vcurl < vsell) {
1136                                 froml = vcurl; tol = vsell;
1137                                 fromc = vcurc; toc = vselc;
1138                         }
1139                         else {
1140                                 froml = vsell; tol = vcurl;
1141                                 fromc = vselc; toc = vcurc;
1142                         }
1143
1144                         y -= froml * lheight;
1145
1146                         glRecti(x + fromc * st->cwidth - 1, y, ar->winx, y - lheight);
1147                         y -= lheight;
1148                         for (i = froml + 1; i < tol; i++) {
1149                                 glRecti(x - 4, y, ar->winx, y - lheight);
1150                                 y -= lheight;
1151                         }
1152
1153                         glRecti(x - 4, y, x + toc * st->cwidth, y - lheight);
1154                         y -= lheight;
1155                 }
1156         }
1157         else {
1158                 int offl, offc;
1159                 wrap_offset(st, ar, text->sell, text->selc, &offl, &offc);
1160                 vsell = txt_get_span(text->lines.first, text->sell) - st->top + offl;
1161                 vselc = text_get_char_pos(st, text->sell->line, text->selc) - st->left + offc;
1162
1163                 if (vselc < 0) {
1164                         vselc = 0;
1165                         hidden = 1;
1166                 }
1167         }
1168
1169         if (st->line_hlight) {
1170                 int x1, x2, y1, y2;
1171
1172                 if (st->wordwrap) {
1173                         int visible_lines = text_get_visible_lines(st, ar, text->sell->line);
1174                         int offl, offc;
1175
1176                         wrap_offset_in_line(st, ar, text->sell, text->selc, &offl, &offc);
1177
1178                         y1 = ar->winy - (vsell - offl) * lheight;
1179                         y2 = y1 - (lheight * visible_lines);
1180                 }
1181                 else {
1182                         y1 = ar->winy - vsell * lheight;
1183                         y2 = y1 - (lheight);
1184                 }
1185
1186                 if (!(y1 < 0 || y2 > ar->winy)) { /* check we need to draw */
1187                         x1 = 0; // st->showlinenrs ? TXT_OFFSET + TEXTXLOC : TXT_OFFSET;
1188                         x2 = x1 + ar->winx;
1189
1190                         glColor4ub(255, 255, 255, 32);
1191                         
1192                         glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1193                         glEnable(GL_BLEND);
1194                         glRecti(x1 - 4, y1, x2, y2);
1195                         glDisable(GL_BLEND);
1196                 }
1197         }
1198         
1199         if (!hidden) {
1200                 /* Draw the cursor itself (we draw the sel. cursor as this is the leading edge) */
1201                 x = st->showlinenrs ? TXT_OFFSET + TEXTXLOC : TXT_OFFSET;
1202                 x += vselc * st->cwidth;
1203                 y = ar->winy - vsell * lheight;
1204                 
1205                 if (st->overwrite) {
1206                         char ch = text->sell->line[text->selc];
1207                         
1208                         y += TXT_LINE_SPACING;
1209                         w = st->cwidth;
1210                         if (ch == '\t') w *= st->tabnumber - (vselc + st->left) % st->tabnumber;
1211                         
1212                         UI_ThemeColor(TH_HILITE);
1213                         glRecti(x, y - lheight - 1, x + w, y - lheight + 1);
1214                 }
1215                 else {
1216                         UI_ThemeColor(TH_HILITE);
1217                         glRecti(x - 1, y, x + 1, y - lheight);
1218                 }
1219         }
1220 }
1221
1222 /******************* draw matching brackets *********************/
1223
1224 static void draw_brackets(const SpaceText *st, const TextDrawContext *tdc, ARegion *ar)
1225 {
1226         TextLine *startl, *endl, *linep;
1227         Text *text = st->text;
1228         int b, fc, find, stack, viewc, viewl, offl, offc, x, y;
1229         int startc, endc, c;
1230         
1231         char ch;
1232
1233         // showsyntax must be on or else the format string will be null
1234         if (!text->curl || !st->showsyntax) return;
1235
1236         startl = text->curl;
1237         startc = text->curc;
1238         b = text_check_bracket(startl->line[startc]);
1239         if (b == 0 && startc > 0) b = text_check_bracket(startl->line[--startc]);
1240         if (b == 0) return;
1241
1242         linep = startl;
1243         c = startc;
1244         fc = txt_utf8_offset_to_index(linep->line, startc);
1245         endl = NULL;
1246         endc = -1;
1247         find = -b;
1248         stack = 0;
1249         
1250         /* Don't highlight backets if syntax HL is off or bracket in string or comment. */
1251         if (!linep->format || linep->format[fc] == FMT_TYPE_STRING || linep->format[fc] == FMT_TYPE_COMMENT)
1252                 return;
1253
1254         if (b > 0) {
1255                 /* opening bracket, search forward for close */
1256                 fc++;
1257                 c += BLI_str_utf8_size_safe(linep->line + c);
1258                 while (linep) {
1259                         while (c < linep->len) {
1260                                 if (linep->format && linep->format[fc] != FMT_TYPE_STRING && linep->format[fc] != FMT_TYPE_COMMENT) {
1261                                         b = text_check_bracket(linep->line[c]);
1262                                         if (b == find) {
1263                                                 if (stack == 0) {
1264                                                         endl = linep;
1265                                                         endc = c;
1266                                                         break;
1267                                                 }
1268                                                 stack--;
1269                                         }
1270                                         else if (b == -find) {
1271                                                 stack++;
1272                                         }
1273                                 }
1274                                 fc++;
1275                                 c += BLI_str_utf8_size_safe(linep->line + c);
1276                         }
1277                         if (endl) break;
1278                         linep = linep->next;
1279                         c = 0;
1280                         fc = 0;
1281                 }
1282         }
1283         else {
1284                 /* closing bracket, search backward for open */
1285                 fc--;
1286                 if (c > 0) c -= linep->line + c - BLI_str_prev_char_utf8(linep->line + c);
1287                 while (linep) {
1288                         while (fc >= 0) {
1289                                 if (linep->format && linep->format[fc] != FMT_TYPE_STRING && linep->format[fc] != FMT_TYPE_COMMENT) {
1290                                         b = text_check_bracket(linep->line[c]);
1291                                         if (b == find) {
1292                                                 if (stack == 0) {
1293                                                         endl = linep;
1294                                                         endc = c;
1295                                                         break;
1296                                                 }
1297                                                 stack--;
1298                                         }
1299                                         else if (b == -find) {
1300                                                 stack++;
1301                                         }
1302                                 }
1303                                 fc--;
1304                                 if (c > 0) c -= linep->line + c - BLI_str_prev_char_utf8(linep->line + c);
1305                         }
1306                         if (endl) break;
1307                         linep = linep->prev;
1308                         if (linep) {
1309                                 if (linep->format) fc = strlen(linep->format) - 1;
1310                                 else fc = -1;
1311                                 if (linep->len) c = BLI_str_prev_char_utf8(linep->line + linep->len) - linep->line;
1312                                 else fc = -1;
1313                         }
1314                 }
1315         }
1316
1317         if (!endl || endc == -1)
1318                 return;
1319
1320         UI_ThemeColor(TH_HILITE);
1321         x = st->showlinenrs ? TXT_OFFSET + TEXTXLOC : TXT_OFFSET;
1322         y = ar->winy - st->lheight_dpi;
1323
1324         /* draw opening bracket */
1325         ch = startl->line[startc];
1326         wrap_offset(st, ar, startl, startc, &offl, &offc);
1327         viewc = text_get_char_pos(st, startl->line, startc) - st->left + offc;
1328
1329         if (viewc >= 0) {
1330                 viewl = txt_get_span(text->lines.first, startl) - st->top + offl;
1331
1332                 text_font_draw_character(tdc, x + viewc * st->cwidth, y - viewl * (st->lheight_dpi + TXT_LINE_SPACING), ch);
1333                 text_font_draw_character(tdc, x + viewc * st->cwidth + 1, y - viewl * (st->lheight_dpi + TXT_LINE_SPACING), ch);
1334         }
1335
1336         /* draw closing bracket */
1337         ch = endl->line[endc];
1338         wrap_offset(st, ar, endl, endc, &offl, &offc);
1339         viewc = text_get_char_pos(st, endl->line, endc) - st->left + offc;
1340
1341         if (viewc >= 0) {
1342                 viewl = txt_get_span(text->lines.first, endl) - st->top + offl;
1343
1344                 text_font_draw_character(tdc, x + viewc * st->cwidth, y - viewl * (st->lheight_dpi + TXT_LINE_SPACING), ch);
1345                 text_font_draw_character(tdc, x + viewc * st->cwidth + 1, y - viewl * (st->lheight_dpi + TXT_LINE_SPACING), ch);
1346         }
1347 }
1348
1349 /*********************** main region drawing *************************/
1350
1351 void draw_text_main(SpaceText *st, ARegion *ar)
1352 {
1353         TextDrawContext tdc = {0};
1354         Text *text = st->text;
1355         TextFormatType *tft;
1356         TextLine *tmp;
1357         rcti scroll, back;
1358         char linenr[12];
1359         int i, x, y, winx, linecount = 0, lineno = 0;
1360         int wraplinecount = 0, wrap_skip = 0;
1361         int margin_column_x;
1362
1363         /* if no text, nothing to do */
1364         if (!text)
1365                 return;
1366
1367         /* dpi controlled line height and font size */
1368         st->lheight_dpi = (U.widget_unit * st->lheight) / 20;
1369
1370         /* don't draw lines below this */
1371         const int clip_min_y = -(int)(st->lheight_dpi - 1);
1372
1373         st->viewlines = (st->lheight_dpi) ? (int)(ar->winy - clip_min_y) / (st->lheight_dpi + TXT_LINE_SPACING) : 0;
1374         
1375         text_draw_context_init(st, &tdc);
1376
1377         text_update_drawcache(st, ar);
1378
1379         /* make sure all the positional pointers exist */
1380         if (!text->curl || !text->sell || !text->lines.first || !text->lines.last)
1381                 txt_clean_text(text);
1382         
1383         /* update rects for scroll */
1384         calc_text_rcts(st, ar, &scroll, &back); /* scroll will hold the entire bar size */
1385
1386         /* update syntax formatting if needed */
1387         tft = ED_text_format_get(text);
1388         tmp = text->lines.first;
1389         lineno = 0;
1390         for (i = 0; i < st->top && tmp; i++) {
1391                 if (st->showsyntax && !tmp->format)
1392                         tft->format_line(st, tmp, false);
1393
1394                 if (st->wordwrap) {
1395                         int lines = text_get_visible_lines_no(st, lineno);
1396
1397                         if (wraplinecount + lines > st->top) {
1398                                 wrap_skip = st->top - wraplinecount;
1399                                 break;
1400                         }
1401                         else {
1402                                 wraplinecount += lines;
1403                                 tmp = tmp->next;
1404                                 linecount++;
1405                         }
1406                 }
1407                 else {
1408                         tmp = tmp->next;
1409                         linecount++;
1410                 }
1411
1412                 lineno++;
1413         }
1414
1415
1416         text_font_begin(&tdc);
1417
1418         tdc.cwidth = max_ii((int)BLF_fixed_width(tdc.font_id), 1);
1419         st->cwidth = tdc.cwidth;
1420
1421         /* draw line numbers background */
1422         if (st->showlinenrs) {
1423                 x = TXT_OFFSET + TEXTXLOC;
1424
1425                 UI_ThemeColor(TH_GRID);
1426                 glRecti((TXT_OFFSET - 12), 0, (TXT_OFFSET - 5) + TEXTXLOC, ar->winy - 2);
1427         }
1428         else {
1429                 st->linenrs_tot = 0; /* not used */
1430                 x = TXT_OFFSET;
1431         }
1432         y = ar->winy - st->lheight_dpi;
1433         winx = ar->winx - TXT_SCROLL_WIDTH;
1434         
1435         /* draw cursor */
1436         draw_cursor(st, ar);
1437
1438         /* draw the text */
1439         UI_ThemeColor(TH_TEXT);
1440
1441         for (i = 0; y > clip_min_y && i < st->viewlines && tmp; i++, tmp = tmp->next) {
1442                 if (st->showsyntax && !tmp->format)
1443                         tft->format_line(st, tmp, false);
1444
1445                 if (st->showlinenrs && !wrap_skip) {
1446                         /* draw line number */
1447                         if (tmp == text->curl)
1448                                 UI_ThemeColor(TH_HILITE);
1449                         else
1450                                 UI_ThemeColor(TH_TEXT);
1451
1452                         BLI_snprintf(linenr, sizeof(linenr), "%*d", st->linenrs_tot, i + linecount + 1);
1453                         /* itoa(i + linecount + 1, linenr, 10); */ /* not ansi-c :/ */
1454                         text_font_draw(&tdc, TXT_OFFSET - 7, y, linenr);
1455
1456                         UI_ThemeColor(TH_TEXT);
1457                 }
1458
1459                 if (st->wordwrap) {
1460                         /* draw word wrapped text */
1461                         int lines = text_draw_wrapped(st, &tdc, tmp->line, x, y, winx - x, tmp->format, wrap_skip);
1462                         y -= lines * (st->lheight_dpi + TXT_LINE_SPACING);
1463                 }
1464                 else {
1465                         /* draw unwrapped text */
1466                         text_draw(st, &tdc, tmp->line, st->left, ar->winx / st->cwidth, x, y, tmp->format);
1467                         y -= st->lheight_dpi + TXT_LINE_SPACING;
1468                 }
1469                 
1470                 wrap_skip = 0;
1471         }
1472         
1473         if (st->flags & ST_SHOW_MARGIN) {
1474                 margin_column_x = x + st->cwidth * (st->margin_column - st->left);
1475                 
1476                 if (margin_column_x >= x) {
1477                         /* same color as line number background */
1478                         UI_ThemeColor(TH_GRID);
1479
1480                         setlinestyle(1);
1481                         glBegin(GL_LINES);
1482                         glVertex2i(margin_column_x, 0);
1483                         glVertex2i(margin_column_x, ar->winy - 2);
1484                         glEnd();
1485                         setlinestyle(0);
1486                 }
1487         }
1488
1489         /* draw other stuff */
1490         draw_brackets(st, &tdc, ar);
1491         draw_textscroll(st, &scroll, &back);
1492         /* draw_documentation(st, ar); - No longer supported */
1493         draw_suggestion_list(st, &tdc, ar);
1494         
1495         text_font_end(&tdc);
1496 }
1497
1498 /************************** update ***************************/
1499
1500 void text_update_character_width(SpaceText *st)
1501 {
1502         TextDrawContext tdc = {0};
1503
1504         text_draw_context_init(st, &tdc);
1505
1506         text_font_begin(&tdc);
1507         st->cwidth = BLF_fixed_width(tdc.font_id);
1508         st->cwidth = MAX2(st->cwidth, (char)1);
1509         text_font_end(&tdc);
1510 }
1511
1512 /* Moves the view to the cursor location,
1513  * also used to make sure the view isn't outside the file */
1514 void text_scroll_to_cursor(SpaceText *st, ARegion *ar, const bool center)
1515 {
1516         Text *text;
1517         int i, x, winx = ar->winx;
1518
1519         if (ELEM(NULL, st, st->text, st->text->curl)) return;
1520
1521         text = st->text;
1522
1523         text_update_character_width(st);
1524
1525         i = txt_get_span(text->lines.first, text->sell);
1526         if (st->wordwrap) {
1527                 int offl, offc;
1528                 wrap_offset(st, ar, text->sell, text->selc, &offl, &offc);
1529                 i += offl;
1530         }
1531
1532         if (center) {
1533                 if (st->top + st->viewlines <= i || st->top > i) {
1534                         st->top = i - st->viewlines / 2;
1535                 }
1536         }
1537         else {
1538                 if (st->top + st->viewlines <= i) {
1539                         st->top = i - (st->viewlines - 1);
1540                 }
1541                 else if (st->top > i) {
1542                         st->top = i;
1543                 }
1544         }
1545         
1546         if (st->wordwrap) {
1547                 st->left = 0;
1548         }
1549         else {
1550                 x = st->cwidth * (text_get_char_pos(st, text->sell->line, text->selc) - st->left);
1551                 winx -= TXT_OFFSET + (st->showlinenrs ? TEXTXLOC : 0) + TXT_SCROLL_WIDTH;
1552
1553                 if (center) {
1554                         if (x <= 0 || x > winx) {
1555                                 st->left += (x - winx / 2) / st->cwidth;
1556                         }
1557                 }
1558                 else {
1559                         if (x <= 0) {
1560                                 st->left += ((x + 1) / st->cwidth) - 1;
1561                         }
1562                         else if (x > winx) {
1563                                 st->left += ((x - (winx + 1)) / st->cwidth) + 1;
1564                         }
1565                 }
1566         }
1567
1568         if (st->top < 0) st->top = 0;
1569         if (st->left < 0) st->left = 0;
1570
1571         st->scroll_accum[0] = 0.0f;
1572         st->scroll_accum[1] = 0.0f;
1573 }
1574
1575 /* takes an area instead of a region, use for listeners */
1576 void text_scroll_to_cursor__area(SpaceText *st, ScrArea *sa, const bool center)
1577 {
1578         ARegion *ar;
1579
1580         if (ELEM(NULL, st, st->text, st->text->curl)) return;
1581
1582         ar = BKE_area_find_region_type(sa, RGN_TYPE_WINDOW);
1583
1584         if (ar) {
1585                 text_scroll_to_cursor(st, ar, center);
1586         }
1587 }
1588
1589 void text_update_cursor_moved(bContext *C)
1590 {
1591         ScrArea *sa = CTX_wm_area(C);
1592         SpaceText *st = CTX_wm_space_text(C);
1593
1594         text_scroll_to_cursor__area(st, sa, true);
1595 }
1596
1597 /**
1598  * Takes a cursor (row, character) and returns x,y pixel coords.
1599  */
1600 bool ED_text_region_location_from_cursor(SpaceText *st, ARegion *ar, const int cursor_co[2], int r_pixel_co[2])
1601 {
1602         TextLine *line = NULL;
1603
1604         if (!st->text) {
1605                 goto error;
1606         }
1607
1608         line = BLI_findlink(&st->text->lines, cursor_co[0]);
1609         if (!line || (cursor_co[1] < 0) || (cursor_co[1] > line->len)) {
1610                 goto error;
1611         }
1612         else {
1613                 int offl, offc;
1614                 int linenr_offset = st->showlinenrs ? TXT_OFFSET + TEXTXLOC : TXT_OFFSET;
1615                 /* handle tabs as well! */
1616                 int char_pos = text_get_char_pos(st, line->line, cursor_co[1]);
1617
1618                 wrap_offset(st, ar, line, cursor_co[1], &offl, &offc);
1619                 r_pixel_co[0] = (char_pos + offc - st->left) * st->cwidth + linenr_offset;
1620                 r_pixel_co[1] = (cursor_co[0] + offl - st->top) * (st->lheight_dpi + TXT_LINE_SPACING);
1621                 r_pixel_co[1] = (ar->winy - (r_pixel_co[1] + TXT_OFFSET)) - st->lheight_dpi;
1622         }
1623         return true;
1624
1625
1626 error:
1627         r_pixel_co[0] = r_pixel_co[1] = -1;
1628         return false;
1629 }