Adds support for utf paths on Windows.
[blender.git] / source / blender / editors / space_text / text_ops.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  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software Foundation,
16  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  *
18  * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
19  * All rights reserved.
20  *
21  * The Original Code is: all of this file.
22  *
23  * Contributor(s): none yet.
24  *
25  * ***** END GPL LICENSE BLOCK *****
26  */
27
28 /** \file blender/editors/space_text/text_ops.c
29  *  \ingroup sptext
30  */
31
32
33 #include <stdlib.h>
34 #include <string.h>
35 #include <ctype.h> /* ispunct */
36 #include <sys/stat.h>
37 #include <errno.h>
38
39 #include "MEM_guardedalloc.h"
40
41 #include "DNA_text_types.h"
42 #include "DNA_userdef_types.h"
43
44 #include "BLI_blenlib.h"
45 #include "BLI_utildefines.h"
46
47 #include "PIL_time.h"
48
49 #include "BKE_context.h"
50 #include "BKE_global.h"
51 #include "BKE_library.h"
52 #include "BKE_main.h"
53 #include "BKE_report.h"
54 #include "BKE_text.h"
55
56 #include "WM_api.h"
57 #include "WM_types.h"
58
59 #include "ED_text.h"
60 #include "ED_curve.h"
61 #include "ED_screen.h"
62 #include "UI_interface.h"
63 #include "UI_resources.h"
64
65 #include "RNA_access.h"
66 #include "RNA_define.h"
67
68 #ifdef WITH_PYTHON
69 #include "BPY_extern.h"
70 #endif
71
72 #include "text_intern.h"
73
74 /************************ poll ***************************/
75
76
77 BM_INLINE int text_pixel_x_to_index(SpaceText *st, const int x)
78 {
79         /* add half the char width so mouse cursor selection is inbetween letters */
80         return (x + (st->cwidth / 2)) / st->cwidth;
81 }
82
83 static int text_new_poll(bContext *UNUSED(C))
84 {
85         return 1;
86 }
87
88 static int text_edit_poll(bContext *C)
89 {
90         Text *text= CTX_data_edit_text(C);
91
92         if(!text)
93                 return 0;
94
95         if(text->id.lib) {
96                 // BKE_report(op->reports, RPT_ERROR, "Can't edit external libdata");
97                 return 0;
98         }
99
100         return 1;
101 }
102
103 static int text_space_edit_poll(bContext *C)
104 {
105         SpaceText *st= CTX_wm_space_text(C);
106         Text *text= CTX_data_edit_text(C);
107
108         if(!st || !text)
109                 return 0;
110
111         if(text->id.lib) {
112                 // BKE_report(op->reports, RPT_ERROR, "Can't edit external libdata");
113                 return 0;
114         }
115
116         return 1;
117 }
118
119 static int text_region_edit_poll(bContext *C)
120 {
121         SpaceText *st= CTX_wm_space_text(C);
122         Text *text= CTX_data_edit_text(C);
123         ARegion *ar= CTX_wm_region(C);
124
125         if(!st || !text)
126                 return 0;
127         
128         if(!ar || ar->regiontype != RGN_TYPE_WINDOW)
129                 return 0;
130
131         if(text->id.lib) {
132                 // BKE_report(op->reports, RPT_ERROR, "Can't edit external libdata");
133                 return 0;
134         }
135
136         return 1;
137 }
138
139 /********************** updates *********************/
140
141 void text_update_line_edited(TextLine *line)
142 {
143         if(!line)
144                 return;
145
146         /* we just free format here, and let it rebuild during draw */
147         if(line->format) {
148                 MEM_freeN(line->format);
149                 line->format= NULL;
150         }
151 }
152
153 void text_update_edited(Text *text)
154 {
155         TextLine *line;
156
157         for(line=text->lines.first; line; line=line->next)
158                 text_update_line_edited(line);
159 }
160
161 /******************* new operator *********************/
162
163 static int text_new_exec(bContext *C, wmOperator *UNUSED(op))
164 {
165         SpaceText *st= CTX_wm_space_text(C);
166         Text *text;
167         PointerRNA ptr, idptr;
168         PropertyRNA *prop;
169
170         text= add_empty_text("Text");
171
172         /* hook into UI */
173         uiIDContextProperty(C, &ptr, &prop);
174
175         if(prop) {
176                 /* when creating new ID blocks, use is already 1, but RNA
177                  * pointer se also increases user, so this compensates it */
178                 /* doesnt always seem to happen... (ton) */
179                 if(text->id.us>1)
180                         text->id.us--;
181
182                 RNA_id_pointer_create(&text->id, &idptr);
183                 RNA_property_pointer_set(&ptr, prop, idptr);
184                 RNA_property_update(C, &ptr, prop);
185         }
186         else if(st) {
187                 st->text= text;
188                 st->top= 0;
189                 text_drawcache_tag_update(st, 1);
190         }
191
192         WM_event_add_notifier(C, NC_TEXT|NA_ADDED, text);
193
194         return OPERATOR_FINISHED;
195 }
196
197 void TEXT_OT_new(wmOperatorType *ot)
198 {
199         /* identifiers */
200         ot->name= "Create Text Block";
201         ot->idname= "TEXT_OT_new";
202         ot->description= "Create a new text data block";
203         
204         /* api callbacks */
205         ot->exec= text_new_exec;
206         ot->poll= text_new_poll;
207         
208         /* flags */
209         ot->flag= OPTYPE_UNDO;
210 }
211
212 /******************* open operator *********************/
213
214 static void text_open_init(bContext *C, wmOperator *op)
215 {
216         PropertyPointerRNA *pprop;
217
218         op->customdata= pprop= MEM_callocN(sizeof(PropertyPointerRNA), "OpenPropertyPointerRNA");
219         uiIDContextProperty(C, &pprop->ptr, &pprop->prop);
220 }
221
222 static int text_open_cancel(bContext *UNUSED(C), wmOperator *op)
223 {
224         MEM_freeN(op->customdata);
225         return OPERATOR_CANCELLED;
226 }
227
228 static int text_open_exec(bContext *C, wmOperator *op)
229 {
230         SpaceText *st= CTX_wm_space_text(C);
231         Text *text;
232         PropertyPointerRNA *pprop;
233         PointerRNA idptr;
234         char str[FILE_MAX];
235         short internal = RNA_boolean_get(op->ptr, "internal");
236
237         RNA_string_get(op->ptr, "filepath", str);
238
239         text= add_text(str, G.main->name);
240
241         if(!text) {
242                 if(op->customdata) MEM_freeN(op->customdata);
243                 return OPERATOR_CANCELLED;
244         }
245
246         if(!op->customdata)
247                 text_open_init(C, op);
248
249         /* hook into UI */
250         pprop= op->customdata;
251
252         if(pprop->prop) {
253                 /* when creating new ID blocks, use is already 1, but RNA
254                  * pointer se also increases user, so this compensates it */
255                 text->id.us--;
256
257                 RNA_id_pointer_create(&text->id, &idptr);
258                 RNA_property_pointer_set(&pprop->ptr, pprop->prop, idptr);
259                 RNA_property_update(C, &pprop->ptr, pprop->prop);
260         }
261         else if(st) {
262                 st->text= text;
263                 st->top= 0;
264         }
265         
266         if (internal) {
267                 if(text->name)
268                         MEM_freeN(text->name);
269                 
270                 text->name = NULL;
271         }
272
273         text_drawcache_tag_update(st, 1);
274         WM_event_add_notifier(C, NC_TEXT|NA_ADDED, text);
275
276         MEM_freeN(op->customdata);
277
278         return OPERATOR_FINISHED;
279 }
280
281 static int text_open_invoke(bContext *C, wmOperator *op, wmEvent *UNUSED(event))
282 {
283         Text *text= CTX_data_edit_text(C);
284         char *path= (text && text->name)? text->name: G.main->name;
285
286         if(RNA_struct_property_is_set(op->ptr, "filepath"))
287                 return text_open_exec(C, op);
288         
289         text_open_init(C, op);
290         RNA_string_set(op->ptr, "filepath", path);
291         WM_event_add_fileselect(C, op); 
292
293         return OPERATOR_RUNNING_MODAL;
294 }
295
296 void TEXT_OT_open(wmOperatorType *ot)
297 {
298         /* identifiers */
299         ot->name= "Open Text Block";
300         ot->idname= "TEXT_OT_open";
301         ot->description= "Open a new text data block";
302
303         /* api callbacks */
304         ot->exec= text_open_exec;
305         ot->invoke= text_open_invoke;
306         ot->cancel= text_open_cancel;
307         ot->poll= text_new_poll;
308
309         /* flags */
310         ot->flag= OPTYPE_UNDO;
311         
312         /* properties */
313         WM_operator_properties_filesel(ot, FOLDERFILE|TEXTFILE|PYSCRIPTFILE, FILE_SPECIAL, FILE_OPENFILE, WM_FILESEL_FILEPATH, FILE_DEFAULTDISPLAY);  //XXX TODO, relative_path
314         RNA_def_boolean(ot->srna, "internal", 0, "Make internal", "Make text file internal after loading");
315 }
316
317 /******************* reload operator *********************/
318
319 static int text_reload_exec(bContext *C, wmOperator *op)
320 {
321         Text *text= CTX_data_edit_text(C);
322
323         if(!reopen_text(text)) {
324                 BKE_report(op->reports, RPT_ERROR, "Could not reopen file");
325                 return OPERATOR_CANCELLED;
326         }
327
328 #ifdef WITH_PYTHON
329         if(text->compiled)
330                 BPY_text_free_code(text);
331 #endif
332
333         text_update_edited(text);
334         text_update_cursor_moved(C);
335         text_drawcache_tag_update(CTX_wm_space_text(C), 1);
336         WM_event_add_notifier(C, NC_TEXT|NA_EDITED, text);
337
338         return OPERATOR_FINISHED;
339 }
340
341 void TEXT_OT_reload(wmOperatorType *ot)
342 {
343         /* identifiers */
344         ot->name= "Reload";
345         ot->idname= "TEXT_OT_reload";
346         ot->description= "Reload active text data block from its file";
347         
348         /* api callbacks */
349         ot->exec= text_reload_exec;
350         ot->invoke= WM_operator_confirm;
351         ot->poll= text_edit_poll;
352 }
353
354 /******************* delete operator *********************/
355
356 static int text_unlink_poll(bContext *C)
357 {
358         /* it should be possible to unlink texts if they're lib-linked in... */
359         return CTX_data_edit_text(C) != NULL;
360 }
361
362 static int text_unlink_exec(bContext *C, wmOperator *UNUSED(op))
363 {
364         Main *bmain= CTX_data_main(C);
365         SpaceText *st= CTX_wm_space_text(C);
366         Text *text= CTX_data_edit_text(C);
367
368         /* make the previous text active, if its not there make the next text active */
369         if(st) {
370                 if(text->id.prev) {
371                         st->text = text->id.prev;
372                         text_update_cursor_moved(C);
373                         WM_event_add_notifier(C, NC_TEXT|ND_CURSOR, st->text);
374                 }
375                 else if(text->id.next) {
376                         st->text = text->id.next;
377                         text_update_cursor_moved(C);
378                         WM_event_add_notifier(C, NC_TEXT|ND_CURSOR, st->text);
379                 }
380         }
381
382         unlink_text(bmain, text);
383         free_libblock(&bmain->text, text);
384
385         text_drawcache_tag_update(st, 1);
386         WM_event_add_notifier(C, NC_TEXT|NA_REMOVED, NULL);
387
388         return OPERATOR_FINISHED;
389 }
390
391 void TEXT_OT_unlink(wmOperatorType *ot)
392 {
393         /* identifiers */
394         ot->name= "Unlink";
395         ot->idname= "TEXT_OT_unlink";
396         ot->description= "Unlink active text data block";
397         
398         /* api callbacks */
399         ot->exec= text_unlink_exec;
400         ot->invoke= WM_operator_confirm;
401         ot->poll= text_unlink_poll;
402         
403         /* flags */
404         ot->flag= OPTYPE_UNDO;
405 }
406
407 /******************* make internal operator *********************/
408
409 static int text_make_internal_exec(bContext *C, wmOperator *UNUSED(op))
410 {
411         Text *text= CTX_data_edit_text(C);
412
413         text->flags |= TXT_ISMEM | TXT_ISDIRTY;
414
415         if(text->name) {
416                 MEM_freeN(text->name);
417                 text->name= NULL;
418         }
419
420         text_update_cursor_moved(C);
421         WM_event_add_notifier(C, NC_TEXT|NA_EDITED, text);
422
423         return OPERATOR_FINISHED;
424 }
425
426 void TEXT_OT_make_internal(wmOperatorType *ot)
427 {
428         /* identifiers */
429         ot->name= "Make Internal";
430         ot->idname= "TEXT_OT_make_internal";
431         ot->description= "Make active text file internal";
432
433         /* api callbacks */
434         ot->exec= text_make_internal_exec;
435         ot->poll= text_edit_poll;
436         
437         /* flags */
438         ot->flag= OPTYPE_UNDO;
439 }
440
441 /******************* save operator *********************/
442
443 static int text_save_poll(bContext *C)
444 {
445         Text *text= CTX_data_edit_text(C);
446
447         if(!text_edit_poll(C))
448                 return 0;
449         
450         return (text->name != NULL && !(text->flags & TXT_ISMEM));
451 }
452
453 static void txt_write_file(Text *text, ReportList *reports) 
454 {
455         FILE *fp;
456         TextLine *tmp;
457         struct stat st;
458         char filepath[FILE_MAX];
459         
460         BLI_strncpy(filepath, text->name, FILE_MAX);
461         BLI_path_abs(filepath, G.main->name);
462         
463         fp= BLI_fopen(filepath, "w");
464         if(fp==NULL) {
465                 BKE_reportf(reports, RPT_ERROR, "Unable to save \"%s\": %s", filepath, errno ? strerror(errno) : "Unknown error writing file");
466                 return;
467         }
468
469         tmp= text->lines.first;
470         while(tmp) {
471                 if(tmp->next) fprintf(fp, "%s\n", tmp->line);
472                 else fprintf(fp, "%s", tmp->line);
473                 
474                 tmp= tmp->next;
475         }
476         
477         fclose (fp);
478
479         if(stat(filepath, &st) == 0) {
480                 text->mtime= st.st_mtime;
481         }
482         else {
483                 text->mtime= 0;
484                 BKE_reportf(reports, RPT_WARNING, "Unable to stat \"%s\": %s", filepath, errno ? strerror(errno) : "Unknown error starrng file");
485         }
486         
487         if(text->flags & TXT_ISDIRTY)
488                 text->flags ^= TXT_ISDIRTY;
489 }
490
491 static int text_save_exec(bContext *C, wmOperator *op)
492 {
493         Text *text= CTX_data_edit_text(C);
494
495         txt_write_file(text, op->reports);
496
497         text_update_cursor_moved(C);
498         WM_event_add_notifier(C, NC_TEXT|NA_EDITED, text);
499
500         return OPERATOR_FINISHED;
501 }
502
503 void TEXT_OT_save(wmOperatorType *ot)
504 {
505         /* identifiers */
506         ot->name= "Save";
507         ot->idname= "TEXT_OT_save";
508         ot->description= "Save active text data block";
509
510         /* api callbacks */
511         ot->exec= text_save_exec;
512         ot->poll= text_save_poll;
513 }
514
515 /******************* save as operator *********************/
516
517 static int text_save_as_exec(bContext *C, wmOperator *op)
518 {
519         Text *text= CTX_data_edit_text(C);
520         char str[FILE_MAX];
521
522         if(!text)
523                 return OPERATOR_CANCELLED;
524
525         RNA_string_get(op->ptr, "filepath", str);
526
527         if(text->name) MEM_freeN(text->name);
528         text->name= BLI_strdup(str);
529         text->flags &= ~TXT_ISMEM;
530
531         txt_write_file(text, op->reports);
532
533         text_update_cursor_moved(C);
534         WM_event_add_notifier(C, NC_TEXT|NA_EDITED, text);
535
536         return OPERATOR_FINISHED;
537 }
538
539 static int text_save_as_invoke(bContext *C, wmOperator *op, wmEvent *UNUSED(event))
540 {
541         Text *text= CTX_data_edit_text(C);
542         char *str;
543
544         if(RNA_struct_property_is_set(op->ptr, "filepath"))
545                 return text_save_as_exec(C, op);
546
547         if(text->name)
548                 str= text->name;
549         else if(text->flags & TXT_ISMEM)
550                 str= text->id.name+2;
551         else
552                 str= G.main->name;
553         
554         RNA_string_set(op->ptr, "filepath", str);
555         WM_event_add_fileselect(C, op); 
556
557         return OPERATOR_RUNNING_MODAL;
558 }
559
560 void TEXT_OT_save_as(wmOperatorType *ot)
561 {
562         /* identifiers */
563         ot->name= "Save As";
564         ot->idname= "TEXT_OT_save_as";
565         ot->description= "Save active text file with options";
566         
567         /* api callbacks */
568         ot->exec= text_save_as_exec;
569         ot->invoke= text_save_as_invoke;
570         ot->poll= text_edit_poll;
571
572         /* properties */
573         WM_operator_properties_filesel(ot, FOLDERFILE|TEXTFILE|PYSCRIPTFILE, FILE_SPECIAL, FILE_SAVE, WM_FILESEL_FILEPATH, FILE_DEFAULTDISPLAY);  //XXX TODO, relative_path
574 }
575
576 /******************* run script operator *********************/
577
578 static int text_run_script_poll(bContext *C)
579 {
580         return (CTX_data_edit_text(C) != NULL);
581 }
582
583 static int text_run_script(bContext *C, ReportList *reports)
584 {
585 #ifdef WITH_PYTHON
586         Text *text= CTX_data_edit_text(C);
587         const short is_live= (reports == NULL);
588
589         /* only for comparison */
590         void *curl_prev= text->curl;
591         int curc_prev= text->curc;
592
593         if (BPY_text_exec(C, text, reports, !is_live)) {
594                 if(is_live) {
595                         /* for nice live updates */
596                         WM_event_add_notifier(C, NC_WINDOW|NA_EDITED, NULL);
597                 }
598                 return OPERATOR_FINISHED;
599         }
600
601         /* Don't report error messages while live editing */
602         if(!is_live) {
603                 if(text->curl != curl_prev || curc_prev != text->curc) {
604                         text_update_cursor_moved(C);
605                         WM_event_add_notifier(C, NC_TEXT|NA_EDITED, text);
606                 }
607
608                 BKE_report(reports, RPT_ERROR, "Python script fail, look in the console for now...");
609         }
610 #else
611         (void)C;
612         (void)reports;
613 #endif /* !WITH_PYTHON */
614         return OPERATOR_CANCELLED;
615 }
616
617 static int text_run_script_exec(bContext *C, wmOperator *op)
618 {
619 #ifndef WITH_PYTHON
620         (void)C; /* unused */
621
622         BKE_report(op->reports, RPT_ERROR, "Python disabled in this build");
623
624         return OPERATOR_CANCELLED;
625 #else
626         return text_run_script(C, op->reports);
627 #endif
628 }
629
630 void TEXT_OT_run_script(wmOperatorType *ot)
631 {
632         /* identifiers */
633         ot->name= "Run Script";
634         ot->idname= "TEXT_OT_run_script";
635         ot->description= "Run active script";
636         
637         /* api callbacks */
638         ot->poll= text_run_script_poll;
639         ot->exec= text_run_script_exec;
640
641         /* flags */
642         ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO;
643 }
644
645 /******************* refresh pyconstraints operator *********************/
646
647 static int text_refresh_pyconstraints_exec(bContext *UNUSED(C), wmOperator *UNUSED(op))
648 {
649 #ifdef WITH_PYTHON
650 #if 0
651         Text *text= CTX_data_edit_text(C);
652         Object *ob;
653         bConstraint *con;
654         short update;
655         
656         /* check all pyconstraints */
657         for(ob= CTX_data_main(C)->object.first; ob; ob= ob->id.next) {
658                 update = 0;
659                 if(ob->type==OB_ARMATURE && ob->pose) {
660                         bPoseChannel *pchan;
661                         for(pchan= ob->pose->chanbase.first; pchan; pchan= pchan->next) {
662                                 for(con = pchan->constraints.first; con; con= con->next) {
663                                         if(con->type==CONSTRAINT_TYPE_PYTHON) {
664                                                 bPythonConstraint *data = con->data;
665                                                 if(data->text==text) BPY_pyconstraint_update(ob, con);
666                                                 update = 1;
667                                                 
668                                         }
669                                 }
670                         }
671                 }
672                 for(con = ob->constraints.first; con; con= con->next) {
673                         if(con->type==CONSTRAINT_TYPE_PYTHON) {
674                                 bPythonConstraint *data = con->data;
675                                 if(data->text==text) BPY_pyconstraint_update(ob, con);
676                                 update = 1;
677                         }
678                 }
679                 
680                 if(update) {
681                         DAG_id_tag_update(&ob->id, OB_RECALC_DATA);
682                 }
683         }
684 #endif
685 #endif
686
687         return OPERATOR_FINISHED;
688 }
689
690 void TEXT_OT_refresh_pyconstraints(wmOperatorType *ot)
691 {
692         /* identifiers */
693         ot->name= "Refresh PyConstraints";
694         ot->idname= "TEXT_OT_refresh_pyconstraints";
695         ot->description= "Refresh all pyconstraints";
696         
697         /* api callbacks */
698         ot->exec= text_refresh_pyconstraints_exec;
699         ot->poll= text_edit_poll;
700 }
701
702 /******************* paste operator *********************/
703
704 static char *txt_copy_selected(Text *text)
705 {
706         TextLine *tmp, *linef, *linel;
707         char *buf= NULL;
708         int charf, charl, length= 0;
709         
710         if(!text) return NULL;
711         if(!text->curl) return NULL;
712         if(!text->sell) return NULL;
713
714         if(!txt_has_sel(text)) return NULL;
715
716         if(text->curl==text->sell) {
717                 linef= linel= text->curl;
718                 
719                 if(text->curc < text->selc) {
720                         charf= text->curc;
721                         charl= text->selc;
722                 }
723                 else{
724                         charf= text->selc;
725                         charl= text->curc;
726                 }
727         }
728         else if(txt_get_span(text->curl, text->sell)<0) {
729                 linef= text->sell;
730                 linel= text->curl;
731
732                 charf= text->selc;              
733                 charl= text->curc;
734         }
735         else {
736                 linef= text->curl;
737                 linel= text->sell;
738                 
739                 charf= text->curc;
740                 charl= text->selc;
741         }
742
743         if(linef == linel) {
744                 length= charl-charf;
745
746                 buf= MEM_callocN(length+1, "cut buffera");
747                 
748                 BLI_strncpy(buf, linef->line + charf, length+1);
749         }
750         else {
751                 length+= linef->len - charf;
752                 length+= charl;
753                 length++; /* For the '\n' */
754                 
755                 tmp= linef->next;
756                 while(tmp && tmp!= linel) {
757                         length+= tmp->len+1;
758                         tmp= tmp->next;
759                 }
760                 
761                 buf= MEM_callocN(length+1, "cut bufferb");
762                 
763                 strncpy(buf, linef->line+ charf, linef->len-charf);
764                 length= linef->len-charf;
765                 
766                 buf[length++]='\n';
767                 
768                 tmp= linef->next;
769                 while(tmp && tmp!=linel) {
770                         strncpy(buf+length, tmp->line, tmp->len);
771                         length+= tmp->len;
772                         
773                         buf[length++]='\n';                     
774                         
775                         tmp= tmp->next;
776                 }
777                 strncpy(buf+length, linel->line, charl);
778                 length+= charl;
779                 
780                 buf[length]=0;
781         }
782
783         return buf;
784 }
785
786 static int text_paste_exec(bContext *C, wmOperator *op)
787 {
788         Text *text= CTX_data_edit_text(C);
789         char *buf;
790         int selection= RNA_boolean_get(op->ptr, "selection");
791
792         buf= WM_clipboard_text_get(selection);
793
794         if(!buf)
795                 return OPERATOR_CANCELLED;
796
797         text_drawcache_tag_update(CTX_wm_space_text(C), 0);
798
799         txt_insert_buf(text, buf);
800         text_update_edited(text);
801
802         MEM_freeN(buf);
803
804         text_update_cursor_moved(C);
805         WM_event_add_notifier(C, NC_TEXT|NA_EDITED, text);
806
807         /* run the script while editing, evil but useful */
808         if(CTX_wm_space_text(C)->live_edit)
809                 text_run_script(C, NULL);
810
811         return OPERATOR_FINISHED;
812 }
813
814 void TEXT_OT_paste(wmOperatorType *ot)
815 {
816         /* identifiers */
817         ot->name= "Paste";
818         ot->idname= "TEXT_OT_paste";
819         ot->description= "Paste text from clipboard";
820         
821         /* api callbacks */
822         ot->exec= text_paste_exec;
823         ot->poll= text_edit_poll;
824         
825         /* properties */
826         RNA_def_boolean(ot->srna, "selection", 0, "Selection", "Paste text selected elsewhere rather than copied (X11 only)");
827 }
828
829 /******************* copy operator *********************/
830
831 static void txt_copy_clipboard(Text *text)
832 {
833         char *buf;
834
835         buf= txt_copy_selected(text);
836
837         if(buf) {
838                 WM_clipboard_text_set(buf, 0);
839                 MEM_freeN(buf);
840         }
841 }
842
843 static int text_copy_exec(bContext *C, wmOperator *UNUSED(op))
844 {
845         Text *text= CTX_data_edit_text(C);
846
847         txt_copy_clipboard(text);
848
849         return OPERATOR_FINISHED;
850 }
851
852 void TEXT_OT_copy(wmOperatorType *ot)
853 {
854         /* identifiers */
855         ot->name= "Copy";
856         ot->idname= "TEXT_OT_copy";
857         ot->description= "Copy selected text to clipboard";
858
859         /* api callbacks */
860         ot->exec= text_copy_exec;
861         ot->poll= text_edit_poll;
862 }
863
864 /******************* cut operator *********************/
865
866 static int text_cut_exec(bContext *C, wmOperator *UNUSED(op))
867 {
868         Text *text= CTX_data_edit_text(C);
869
870         text_drawcache_tag_update(CTX_wm_space_text(C), 0);
871
872         txt_copy_clipboard(text);
873         txt_delete_selected(text);
874
875         text_update_cursor_moved(C);
876         WM_event_add_notifier(C, NC_TEXT|NA_EDITED, text);
877
878         /* run the script while editing, evil but useful */
879         if(CTX_wm_space_text(C)->live_edit)
880                 text_run_script(C, NULL);
881
882         return OPERATOR_FINISHED;
883 }
884
885 void TEXT_OT_cut(wmOperatorType *ot)
886 {
887         /* identifiers */
888         ot->name= "Cut";
889         ot->idname= "TEXT_OT_cut";
890         ot->description= "Cut selected text to clipboard";
891         
892         /* api callbacks */
893         ot->exec= text_cut_exec;
894         ot->poll= text_edit_poll;
895 }
896
897 /******************* indent operator *********************/
898
899 static int text_indent_exec(bContext *C, wmOperator *UNUSED(op))
900 {
901         Text *text= CTX_data_edit_text(C);
902
903         text_drawcache_tag_update(CTX_wm_space_text(C), 0);
904
905         if(txt_has_sel(text)) {
906                 txt_order_cursors(text);
907                 txt_indent(text);
908         }
909         else
910                 txt_add_char(text, '\t');
911
912         text_update_edited(text);
913
914         text_update_cursor_moved(C);
915         WM_event_add_notifier(C, NC_TEXT|NA_EDITED, text);
916
917         return OPERATOR_FINISHED;
918 }
919
920 void TEXT_OT_indent(wmOperatorType *ot)
921 {
922         /* identifiers */
923         ot->name= "Indent";
924         ot->idname= "TEXT_OT_indent";
925         ot->description= "Indent selected text";
926         
927         /* api callbacks */
928         ot->exec= text_indent_exec;
929         ot->poll= text_edit_poll;
930 }
931
932 /******************* unindent operator *********************/
933
934 static int text_unindent_exec(bContext *C, wmOperator *UNUSED(op))
935 {
936         Text *text= CTX_data_edit_text(C);
937
938         if(txt_has_sel(text)) {
939                 text_drawcache_tag_update(CTX_wm_space_text(C), 0);
940
941                 txt_order_cursors(text);
942                 txt_unindent(text);
943
944                 text_update_edited(text);
945
946                 text_update_cursor_moved(C);
947                 WM_event_add_notifier(C, NC_TEXT|NA_EDITED, text);
948
949                 return OPERATOR_FINISHED;
950         }
951
952         return OPERATOR_CANCELLED;
953 }
954
955 void TEXT_OT_unindent(wmOperatorType *ot)
956 {
957         /* identifiers */
958         ot->name= "Unindent";
959         ot->idname= "TEXT_OT_unindent";
960         ot->description= "Unindent selected text";
961         
962         /* api callbacks */
963         ot->exec= text_unindent_exec;
964         ot->poll= text_edit_poll;
965 }
966
967 /******************* line break operator *********************/
968
969 static int text_line_break_exec(bContext *C, wmOperator *UNUSED(op))
970 {
971         SpaceText *st= CTX_wm_space_text(C);
972         Text *text= CTX_data_edit_text(C);
973         int a, curts;
974         int space = (text->flags & TXT_TABSTOSPACES) ? st->tabnumber : 1;
975
976         text_drawcache_tag_update(st, 0);
977
978         // double check tabs/spaces before splitting the line
979         curts= setcurr_tab_spaces(text, space);
980         txt_split_curline(text);
981
982         for(a=0; a < curts; a++) {
983                 if (text->flags & TXT_TABSTOSPACES) {
984                         txt_add_char(text, ' ');
985                 } else {
986                         txt_add_char(text, '\t');
987                 }
988         }
989
990         if(text->curl) {
991                 if(text->curl->prev)
992                         text_update_line_edited(text->curl->prev);
993                 text_update_line_edited(text->curl);
994         }
995
996         text_update_cursor_moved(C);
997         WM_event_add_notifier(C, NC_TEXT|NA_EDITED, text);
998
999         return OPERATOR_CANCELLED;
1000 }
1001
1002 void TEXT_OT_line_break(wmOperatorType *ot)
1003 {
1004         /* identifiers */
1005         ot->name= "Line Break";
1006         ot->idname= "TEXT_OT_line_break";
1007         ot->description= "Insert line break at cursor position";
1008         
1009         /* api callbacks */
1010         ot->exec= text_line_break_exec;
1011         ot->poll= text_edit_poll;
1012 }
1013
1014 /******************* comment operator *********************/
1015
1016 static int text_comment_exec(bContext *C, wmOperator *UNUSED(op))
1017 {
1018         Text *text= CTX_data_edit_text(C);
1019
1020         if(txt_has_sel(text)) {
1021                 text_drawcache_tag_update(CTX_wm_space_text(C), 0);
1022
1023                 txt_order_cursors(text);
1024                 txt_comment(text);
1025                 text_update_edited(text);
1026
1027                 text_update_cursor_moved(C);
1028                 WM_event_add_notifier(C, NC_TEXT|NA_EDITED, text);
1029                 return OPERATOR_FINISHED;
1030         }
1031
1032         return OPERATOR_CANCELLED;
1033 }
1034
1035 void TEXT_OT_comment(wmOperatorType *ot)
1036 {
1037         /* identifiers */
1038         ot->name= "Comment";
1039         ot->idname= "TEXT_OT_comment";
1040         ot->description= "Convert selected text to comment";
1041         
1042         /* api callbacks */
1043         ot->exec= text_comment_exec;
1044         ot->poll= text_edit_poll;
1045 }
1046
1047 /******************* uncomment operator *********************/
1048
1049 static int text_uncomment_exec(bContext *C, wmOperator *UNUSED(op))
1050 {
1051         Text *text= CTX_data_edit_text(C);
1052
1053         if(txt_has_sel(text)) {
1054                 text_drawcache_tag_update(CTX_wm_space_text(C), 0);
1055
1056                 txt_order_cursors(text);
1057                 txt_uncomment(text);
1058                 text_update_edited(text);
1059
1060                 text_update_cursor_moved(C);
1061                 WM_event_add_notifier(C, NC_TEXT|NA_EDITED, text);
1062
1063                 return OPERATOR_FINISHED;
1064         }
1065
1066         return OPERATOR_CANCELLED;
1067 }
1068
1069 void TEXT_OT_uncomment(wmOperatorType *ot)
1070 {
1071         /* identifiers */
1072         ot->name= "Uncomment";
1073         ot->idname= "TEXT_OT_uncomment";
1074         ot->description= "Convert selected comment to text";
1075         
1076         /* api callbacks */
1077         ot->exec= text_uncomment_exec;
1078         ot->poll= text_edit_poll;
1079 }
1080
1081 /******************* convert whitespace operator *********************/
1082
1083 enum { TO_SPACES, TO_TABS };
1084 static EnumPropertyItem whitespace_type_items[]= {
1085         {TO_SPACES, "SPACES", 0, "To Spaces", NULL},
1086         {TO_TABS, "TABS", 0, "To Tabs", NULL},
1087         {0, NULL, 0, NULL, NULL}};
1088
1089 static int text_convert_whitespace_exec(bContext *C, wmOperator *op)
1090 {
1091         SpaceText *st= CTX_wm_space_text(C);
1092         Text *text= CTX_data_edit_text(C);
1093         TextLine *tmp;
1094         FlattenString fs;
1095         size_t a, j;
1096         char *text_check_line, *new_line;
1097         int extra, number; //unknown for now
1098         int type= RNA_enum_get(op->ptr, "type");
1099         
1100         tmp = text->lines.first;
1101         
1102         //first convert to all space, this make it a lot easier to convert to tabs because there is no mixtures of ' ' && '\t'
1103         while(tmp) {
1104                 text_check_line = tmp->line;
1105                 number = flatten_string(st, &fs, text_check_line)+1;
1106                 flatten_string_free(&fs);
1107                 new_line = MEM_callocN(number, "Converted_Line");
1108                 j = 0;
1109                 for(a=0; a < strlen(text_check_line); a++) { //foreach char in line
1110                         if(text_check_line[a] == '\t') { //checking for tabs
1111                                 //get the number of spaces this tabs is showing
1112                                 //i don't like doing it this way but will look into it later
1113                                 new_line[j] = '\0';
1114                                 number = flatten_string(st, &fs, new_line);
1115                                 flatten_string_free(&fs);
1116                                 new_line[j] = '\t';
1117                                 new_line[j+1] = '\0';
1118                                 number = flatten_string(st, &fs, new_line)-number;
1119                                 flatten_string_free(&fs);
1120
1121                                 for(extra = 0; extra < number; extra++) {
1122                                         new_line[j] = ' ';
1123                                         j++;
1124                                 }
1125                         }
1126                         else {
1127                                 new_line[j] = text_check_line[a];
1128                                 ++j;
1129                         }
1130                 }
1131                 new_line[j] = '\0';
1132                 // put new_line in the tmp->line spot still need to try and set the curc correctly
1133                 if(tmp->line) MEM_freeN(tmp->line);
1134                 if(tmp->format) MEM_freeN(tmp->format);
1135                 
1136                 tmp->line = new_line;
1137                 tmp->len = strlen(new_line);
1138                 tmp->format = NULL;
1139                 tmp = tmp->next;
1140         }
1141         
1142         if (type == TO_TABS) { // Converting to tabs
1143                 //start over from the beginning
1144                 tmp = text->lines.first;
1145                 
1146                 while(tmp) {
1147                         text_check_line = tmp->line;
1148                         extra = 0;
1149                         for(a = 0; a < strlen(text_check_line); a++) {
1150                                 number = 0;
1151                                 for(j = 0; j < (size_t)st->tabnumber; j++) {
1152                                         if((a+j) <= strlen(text_check_line)) { //check to make sure we are not pass the end of the line
1153                                                 if(text_check_line[a+j] != ' ') {
1154                                                         number = 1;
1155                                                 }
1156                                         }
1157                                 }
1158                                 if(!number) { //found all number of space to equal a tab
1159                                         a = a+(st->tabnumber-1);
1160                                         extra = extra+1;
1161                                 }
1162                         }
1163                         
1164                         if( extra > 0 ) { //got tabs make malloc and do what you have to do
1165                                 new_line = MEM_callocN(strlen(text_check_line)-(((st->tabnumber*extra)-extra)-1), "Converted_Line");
1166                                 extra = 0; //reuse vars
1167                                 for(a = 0; a < strlen(text_check_line); a++) {
1168                                         number = 0;
1169                                         for(j = 0; j < (size_t)st->tabnumber; j++) {
1170                                                 if((a+j) <= strlen(text_check_line)) { //check to make sure we are not pass the end of the line
1171                                                         if(text_check_line[a+j] != ' ') {
1172                                                                 number = 1;
1173                                                         }
1174                                                 }
1175                                         }
1176
1177                                         if(!number) { //found all number of space to equal a tab
1178                                                 new_line[extra] = '\t';
1179                                                 a = a+(st->tabnumber-1);
1180                                                 ++extra;
1181                                                 
1182                                         }
1183                                         else { //not adding a tab
1184                                                 new_line[extra] = text_check_line[a];
1185                                                 ++extra;
1186                                         }
1187                                 }
1188                                 new_line[extra] = '\0';
1189                                 // put new_line in the tmp->line spot still need to try and set the curc correctly
1190                                 if(tmp->line) MEM_freeN(tmp->line);
1191                                 if(tmp->format) MEM_freeN(tmp->format);
1192                                 
1193                                 tmp->line = new_line;
1194                                 tmp->len = strlen(new_line);
1195                                 tmp->format = NULL;
1196                         }
1197                         tmp = tmp->next;
1198                 }
1199         }
1200
1201         text_update_edited(text);
1202         text_update_cursor_moved(C);
1203         text_drawcache_tag_update(st, 1);
1204         WM_event_add_notifier(C, NC_TEXT|NA_EDITED, text);
1205
1206         return OPERATOR_FINISHED;
1207 }
1208
1209 void TEXT_OT_convert_whitespace(wmOperatorType *ot)
1210 {
1211         /* identifiers */
1212         ot->name= "Convert Whitespace";
1213         ot->idname= "TEXT_OT_convert_whitespace";
1214         ot->description= "Convert whitespaces by type";
1215         
1216         /* api callbacks */
1217         ot->exec= text_convert_whitespace_exec;
1218         ot->poll= text_edit_poll;
1219
1220         /* properties */
1221         RNA_def_enum(ot->srna, "type", whitespace_type_items, TO_SPACES, "Type", "Type of whitespace to convert to");
1222 }
1223
1224 /******************* select all operator *********************/
1225
1226 static int text_select_all_exec(bContext *C, wmOperator *UNUSED(op))
1227 {
1228         Text *text= CTX_data_edit_text(C);
1229
1230         txt_sel_all(text);
1231
1232         text_update_cursor_moved(C);
1233         WM_event_add_notifier(C, NC_TEXT|NA_EDITED, text);
1234
1235         return OPERATOR_FINISHED;
1236 }
1237
1238 void TEXT_OT_select_all(wmOperatorType *ot)
1239 {
1240         /* identifiers */
1241         ot->name= "Select All";
1242         ot->idname= "TEXT_OT_select_all";
1243         ot->description= "Select all text";
1244         
1245         /* api callbacks */
1246         ot->exec= text_select_all_exec;
1247         ot->poll= text_edit_poll;
1248 }
1249
1250 /******************* select line operator *********************/
1251
1252 static int text_select_line_exec(bContext *C, wmOperator *UNUSED(op))
1253 {
1254         Text *text= CTX_data_edit_text(C);
1255
1256         txt_sel_line(text);
1257
1258         text_update_cursor_moved(C);
1259         WM_event_add_notifier(C, NC_TEXT|NA_EDITED, text);
1260
1261         return OPERATOR_FINISHED;
1262 }
1263
1264 void TEXT_OT_select_line(wmOperatorType *ot)
1265 {
1266         /* identifiers */
1267         ot->name= "Select Line";
1268         ot->idname= "TEXT_OT_select_line";
1269         ot->description= "Select text by line";
1270         
1271         /* api callbacks */
1272         ot->exec= text_select_line_exec;
1273         ot->poll= text_edit_poll;
1274 }
1275
1276 /******************* select word operator *********************/
1277
1278 static int text_select_word_exec(bContext *C, wmOperator *UNUSED(op))
1279 {
1280         Text *text= CTX_data_edit_text(C);
1281
1282         txt_jump_left(text, 0);
1283         txt_jump_right(text, 1);
1284
1285         text_update_cursor_moved(C);
1286         WM_event_add_notifier(C, NC_TEXT|NA_EDITED, text);
1287
1288         return OPERATOR_FINISHED;
1289 }
1290
1291 void TEXT_OT_select_word(wmOperatorType *ot)
1292 {
1293         /* identifiers */
1294         ot->name= "Select Word";
1295         ot->idname= "TEXT_OT_select_word";
1296         ot->description= "Select word under cursor";
1297
1298         /* api callbacks */
1299         ot->exec= text_select_word_exec;
1300         ot->poll= text_edit_poll;
1301 }
1302
1303 /******************* previous marker operator *********************/
1304
1305 static int text_previous_marker_exec(bContext *C, wmOperator *UNUSED(op))
1306 {
1307         Text *text= CTX_data_edit_text(C);
1308         TextMarker *mrk;
1309         int lineno;
1310
1311         lineno= txt_get_span(text->lines.first, text->curl);
1312         mrk= text->markers.last;
1313         while(mrk && (mrk->lineno>lineno || (mrk->lineno==lineno && mrk->end > text->curc)))
1314                 mrk= mrk->prev;
1315         if(!mrk) mrk= text->markers.last;
1316         if(mrk) {
1317                 txt_move_to(text, mrk->lineno, mrk->start, 0);
1318                 txt_move_to(text, mrk->lineno, mrk->end, 1);
1319         }
1320
1321         text_update_cursor_moved(C);
1322         WM_event_add_notifier(C, NC_TEXT|NA_EDITED, text);
1323
1324         return OPERATOR_FINISHED;
1325 }
1326
1327 void TEXT_OT_previous_marker(wmOperatorType *ot)
1328 {
1329         /* identifiers */
1330         ot->name= "Previous Marker";
1331         ot->idname= "TEXT_OT_previous_marker";
1332         ot->description= "Move to previous marker";
1333         
1334         /* api callbacks */
1335         ot->exec= text_previous_marker_exec;
1336         ot->poll= text_edit_poll;
1337 }
1338
1339 /******************* next marker operator *********************/
1340
1341 static int text_next_marker_exec(bContext *C, wmOperator *UNUSED(op))
1342 {
1343         Text *text= CTX_data_edit_text(C);
1344         TextMarker *mrk;
1345         int lineno;
1346
1347         lineno= txt_get_span(text->lines.first, text->curl);
1348         mrk= text->markers.first;
1349         while(mrk && (mrk->lineno<lineno || (mrk->lineno==lineno && mrk->start <= text->curc)))
1350                 mrk= mrk->next;
1351         if(!mrk) mrk= text->markers.first;
1352         if(mrk) {
1353                 txt_move_to(text, mrk->lineno, mrk->start, 0);
1354                 txt_move_to(text, mrk->lineno, mrk->end, 1);
1355         }
1356
1357         text_update_cursor_moved(C);
1358         WM_event_add_notifier(C, NC_TEXT|NA_EDITED, text);
1359
1360         return OPERATOR_FINISHED;
1361 }
1362
1363 void TEXT_OT_next_marker(wmOperatorType *ot)
1364 {
1365         /* identifiers */
1366         ot->name= "Next Marker";
1367         ot->idname= "TEXT_OT_next_marker";
1368         ot->description= "Move to next marker";
1369         
1370         /* api callbacks */
1371         ot->exec= text_next_marker_exec;
1372         ot->poll= text_edit_poll;
1373 }
1374
1375 /******************* clear all markers operator *********************/
1376
1377 static int text_clear_all_markers_exec(bContext *C, wmOperator *UNUSED(op))
1378 {
1379         Text *text= CTX_data_edit_text(C);
1380
1381         txt_clear_markers(text, 0, 0);
1382
1383         text_update_cursor_moved(C);
1384         WM_event_add_notifier(C, NC_TEXT|NA_EDITED, text);
1385
1386         return OPERATOR_FINISHED;
1387 }
1388
1389 void TEXT_OT_markers_clear(wmOperatorType *ot)
1390 {
1391         /* identifiers */
1392         ot->name= "Clear All Markers";
1393         ot->idname= "TEXT_OT_markers_clear";
1394         ot->description= "Clear all markers";
1395         
1396         /* api callbacks */
1397         ot->exec= text_clear_all_markers_exec;
1398         ot->poll= text_edit_poll;
1399 }
1400
1401 /************************ move operator ************************/
1402
1403 static EnumPropertyItem move_type_items[]= {
1404         {LINE_BEGIN, "LINE_BEGIN", 0, "Line Begin", ""},
1405         {LINE_END, "LINE_END", 0, "Line End", ""},
1406         {FILE_TOP, "FILE_TOP", 0, "File Top", ""},
1407         {FILE_BOTTOM, "FILE_BOTTOM", 0, "File Bottom", ""},
1408         {PREV_CHAR, "PREVIOUS_CHARACTER", 0, "Previous Character", ""},
1409         {NEXT_CHAR, "NEXT_CHARACTER", 0, "Next Character", ""},
1410         {PREV_WORD, "PREVIOUS_WORD", 0, "Previous Word", ""},
1411         {NEXT_WORD, "NEXT_WORD", 0, "Next Word", ""},
1412         {PREV_LINE, "PREVIOUS_LINE", 0, "Previous Line", ""},
1413         {NEXT_LINE, "NEXT_LINE", 0, "Next Line", ""},
1414         {PREV_PAGE, "PREVIOUS_PAGE", 0, "Previous Page", ""},
1415         {NEXT_PAGE, "NEXT_PAGE", 0, "Next Page", ""},
1416         {0, NULL, 0, NULL, NULL}};
1417
1418 /* get cursor position in line by relative wrapped line and column positions */
1419 static int text_get_cursor_rel(SpaceText* st, ARegion *ar, TextLine *linein, int rell, int relc)
1420 {
1421         int i, j, start, end, max, chop, curs, loop, endj, found, selc;
1422         char ch;
1423
1424         max= wrap_width(st, ar);
1425
1426         selc= start= endj= curs= found= 0;
1427         end= max;
1428         chop= loop= 1;
1429
1430         for(i=0, j=0; loop; j+=BLI_str_utf8_size(linein->line+j)) {
1431                 int chars;
1432                 /* Mimic replacement of tabs */
1433                 ch= linein->line[j];
1434                 if(ch=='\t') {
1435                         chars= st->tabnumber-i%st->tabnumber;
1436                         ch= ' ';
1437                 }
1438                 else chars= 1;
1439
1440                 while(chars--) {
1441                         if(rell==0 && i-start==relc) {
1442                                 /* current position could be wrapped to next line */
1443                                 /* this should be checked when end of current line would be reached */
1444                                 selc= j;
1445                                 found= 1;
1446                         }
1447                         else if(i-end==relc) {
1448                                 curs= j;
1449                         }
1450                         if(i-start>=max) {
1451                                 if(found) {
1452                                         /* exact cursor position was found, check if it's */
1453                                         /* still on needed line (hasn't been wrapped) */
1454                                         if(selc>endj && !chop) selc= endj;
1455                                         loop= 0;
1456                                         break;
1457                                 }
1458
1459                                 if(chop) endj= j;
1460
1461                                 start= end;
1462                                 end += max;
1463                                 chop= 1;
1464                                 rell--;
1465
1466                                 if(rell==0 && i-start>=relc) {
1467                                         selc= curs;
1468                                         loop= 0;
1469                                         break;
1470                                 }
1471                         }
1472                         else if (ch=='\0') {
1473                                 if(!found) selc= linein->len;
1474                                 loop= 0;
1475                                 break;
1476                         }
1477                         else if(ch==' ' || ch=='-') {
1478                                 if(found) {
1479                                         loop= 0;
1480                                         break;
1481                                 }
1482
1483                                 if(rell==0 && i-start>=relc) {
1484                                         selc= curs;
1485                                         loop= 0;
1486                                         break;
1487                                 }
1488                                 end= i+1;
1489                                 endj= j;
1490                                 chop= 0;
1491                         }
1492                         i++;
1493                 }
1494         }
1495
1496         return selc;
1497 }
1498
1499 static int cursor_skip_find_line(SpaceText* st, ARegion *ar,
1500         int lines, TextLine **linep, int *charp, int *rell, int *relc)
1501 {
1502         int offl, offc, visible_lines;
1503
1504         wrap_offset_in_line(st, ar, *linep, *charp, &offl, &offc);
1505         *relc= text_get_char_pos(st, (*linep)->line, *charp) + offc;
1506         *rell= lines;
1507
1508         /* handle current line */
1509         if(lines>0) {
1510                 visible_lines= text_get_visible_lines(st, ar, (*linep)->line);
1511
1512                 if(*rell-visible_lines+offl>=0) {
1513                         if(!(*linep)->next) {
1514                                 if(offl < visible_lines-1) {
1515                                         *rell= visible_lines-1;
1516                                         return 1;
1517                                 }
1518
1519                                 *charp= (*linep)->len;
1520                                 return 0;
1521                         }
1522
1523                         *rell-= visible_lines-offl;
1524                         *linep=(*linep)->next;
1525                 } else {
1526                         *rell+= offl;
1527                         return 1;
1528                 }
1529         } else {
1530                 if(*rell+offl<=0) {
1531                         if(!(*linep)->prev) {
1532                                 if(offl) {
1533                                         *rell= 0;
1534                                         return 1;
1535                                 }
1536
1537                                 *charp= 0;
1538                                 return 0;
1539                         }
1540
1541                         *rell+= offl;
1542                         *linep=(*linep)->prev;
1543                 } else {
1544                         *rell+= offl;
1545                         return 1;
1546                 }
1547         }
1548
1549         /* skip lines and find destination line and offsets */
1550         while(*linep) {
1551                 visible_lines= text_get_visible_lines(st, ar, (*linep)->line);
1552
1553                 if(lines<0) { /* moving top */
1554                         if(*rell+visible_lines >= 0) {
1555                                 *rell+= visible_lines;
1556                                 break;
1557                         }
1558
1559                         if(!(*linep)->prev) {
1560                                 *rell= 0;
1561                                 break;
1562                         }
1563
1564                         *rell+= visible_lines;
1565                         *linep=(*linep)->prev;
1566                 } else { /* moving bottom */
1567                         if(*rell-visible_lines < 0) break;
1568
1569                         if(!(*linep)->next) {
1570                                 *rell= visible_lines-1;
1571                                 break;
1572                         }
1573
1574                         *rell-= visible_lines;
1575                         *linep=(*linep)->next;
1576                 }
1577         }
1578
1579         return 1;
1580 }
1581
1582 static void txt_wrap_move_bol(SpaceText *st, ARegion *ar, short sel)
1583 {
1584         Text *text= st->text;
1585         TextLine **linep;
1586         int *charp;
1587         int oldl, oldc, i, j, max, start, end, endj, chop, loop;
1588         char ch;
1589
1590         text_update_character_width(st);
1591
1592         if (sel) linep= &text->sell, charp= &text->selc;
1593         else linep= &text->curl, charp= &text->curc;
1594
1595         oldc= *charp;
1596         oldl= txt_get_span(text->lines.first, *linep);
1597
1598         max= wrap_width(st, ar);
1599
1600         start= endj= 0;
1601         end= max;
1602         chop= loop= 1;
1603         *charp= 0;
1604
1605         for(i=0, j=0; loop; j+=BLI_str_utf8_size((*linep)->line+j)) {
1606                 int chars;
1607                 /* Mimic replacement of tabs */
1608                 ch= (*linep)->line[j];
1609                 if(ch=='\t') {
1610                         chars= st->tabnumber-i%st->tabnumber;
1611                         ch= ' ';
1612                 }
1613                 else chars= 1;
1614
1615                 while(chars--) {
1616                         if(i-start>=max) {
1617                                 *charp= endj;
1618
1619                                 if(j>=oldc) {
1620                                         if(ch=='\0') *charp= txt_utf8_index_to_offset((*linep)->line, start);
1621                                         loop= 0;
1622                                         break;
1623                                 }
1624
1625                                 if(chop) endj= j;
1626
1627                                 start= end;
1628                                 end += max;
1629                                 chop= 1;
1630                         }
1631                         else if(ch==' ' || ch=='-' || ch=='\0') {
1632                                 if(j>=oldc) {
1633                                         *charp= txt_utf8_index_to_offset((*linep)->line, start);
1634                                         loop= 0;
1635                                         break;
1636                                 }
1637
1638                                 end= i+1;
1639                                 endj= j+1;
1640                                 chop= 0;
1641                         }
1642                         i++;
1643                 }
1644         }
1645
1646         if (!sel) txt_pop_sel(text);
1647         txt_undo_add_toop(text, sel?UNDO_STO:UNDO_CTO, oldl, oldc, oldl, *charp);
1648 }
1649
1650 static void txt_wrap_move_eol(SpaceText *st, ARegion *ar, short sel)
1651 {
1652         Text *text= st->text;
1653         TextLine **linep;
1654         int *charp;
1655         int oldl, oldc, i, j, max, start, end, endj, chop, loop;
1656         char ch;
1657
1658         text_update_character_width(st);
1659
1660         if (sel) linep= &text->sell, charp= &text->selc;
1661         else linep= &text->curl, charp= &text->curc;
1662
1663         oldc= *charp;
1664         oldl= txt_get_span(text->lines.first, *linep);
1665
1666         max= wrap_width(st, ar);
1667
1668         start= endj= 0;
1669         end= max;
1670         chop= loop= 1;
1671         *charp= 0;
1672
1673         for(i=0, j=0; loop; j+=BLI_str_utf8_size((*linep)->line+j)) {
1674                 int chars;
1675                 /* Mimic replacement of tabs */
1676                 ch= (*linep)->line[j];
1677                 if(ch=='\t') {
1678                         chars= st->tabnumber-i%st->tabnumber;
1679                         ch= ' ';
1680                 }
1681                 else chars= 1;
1682
1683                 while(chars--) {
1684                         if(i-start>=max) {
1685                                 if(chop) endj= BLI_str_prev_char_utf8((*linep)->line+j)-(*linep)->line;
1686
1687                                 if(endj>=oldc) {
1688                                         if(ch=='\0') *charp= (*linep)->len;
1689                                         else *charp= endj;
1690                                         loop= 0;
1691                                         break;
1692                                 }
1693
1694                                 start= end;
1695                                 end += max;
1696                                 chop= 1;
1697                         } else if(ch=='\0') {
1698                                 *charp= (*linep)->len;
1699                                 loop= 0;
1700                                 break;
1701                         } else if(ch==' ' || ch=='-') {
1702                                 end= i+1;
1703                                 endj= j;
1704                                 chop= 0;
1705                         }
1706                         i++;
1707                 }
1708         }
1709
1710         if (!sel) txt_pop_sel(text);
1711         txt_undo_add_toop(text, sel?UNDO_STO:UNDO_CTO, oldl, oldc, oldl, *charp);
1712 }
1713
1714 static void txt_wrap_move_up(SpaceText *st, ARegion *ar, short sel)
1715 {
1716         Text *text= st->text;
1717         TextLine **linep;
1718         int *charp;
1719         int oldl, oldc, offl, offc, col, newl;
1720
1721         text_update_character_width(st);
1722
1723         if (sel) linep= &text->sell, charp= &text->selc;
1724         else linep= &text->curl, charp= &text->curc;
1725
1726         /* store previous position */
1727         oldc= *charp;
1728         newl= oldl= txt_get_span(text->lines.first, *linep);
1729
1730         wrap_offset_in_line(st, ar, *linep, *charp, &offl, &offc);
1731         col= text_get_char_pos(st, (*linep)->line, *charp) + offc;
1732         if(offl) {
1733                 *charp= text_get_cursor_rel(st, ar, *linep, offl-1, col);
1734                 newl= BLI_findindex(&text->lines, linep);
1735         } else {
1736                 if((*linep)->prev) {
1737                         int visible_lines;
1738
1739                         *linep= (*linep)->prev;
1740                         visible_lines= text_get_visible_lines(st, ar, (*linep)->line);
1741                         *charp= text_get_cursor_rel(st, ar, *linep, visible_lines-1, col);
1742                         newl--;
1743                 } else *charp= 0;
1744         }
1745
1746         if (!sel) txt_pop_sel(text);
1747         txt_undo_add_toop(text, sel?UNDO_STO:UNDO_CTO, oldl, oldc, newl, *charp);
1748 }
1749
1750 static void txt_wrap_move_down(SpaceText *st, ARegion *ar, short sel)
1751 {
1752         Text *text= st->text;
1753         TextLine **linep;
1754         int *charp;
1755         int oldl, oldc, offl, offc, col, newl, visible_lines;
1756
1757         text_update_character_width(st);
1758
1759         if (sel) linep= &text->sell, charp= &text->selc;
1760         else linep= &text->curl, charp= &text->curc;
1761
1762         /* store previous position */
1763         oldc= *charp;
1764         newl= oldl= txt_get_span(text->lines.first, *linep);
1765
1766         wrap_offset_in_line(st, ar, *linep, *charp, &offl, &offc);
1767         col= text_get_char_pos(st, (*linep)->line, *charp) + offc;
1768         visible_lines= text_get_visible_lines(st, ar, (*linep)->line);
1769         if(offl<visible_lines-1) {
1770                 *charp= text_get_cursor_rel(st, ar, *linep, offl+1, col);
1771                 newl= BLI_findindex(&text->lines, linep);
1772         } else {
1773                 if((*linep)->next) {
1774                         *linep= (*linep)->next;
1775                         *charp= text_get_cursor_rel(st, ar, *linep, 0, col);
1776                         newl++;
1777                 } else *charp= (*linep)->len;
1778         }
1779
1780         if (!sel) txt_pop_sel(text);
1781         txt_undo_add_toop(text, sel?UNDO_STO:UNDO_CTO, oldl, oldc, newl, *charp);
1782 }
1783
1784 /* Moves the cursor vertically by the specified number of lines.
1785  * If the destination line is shorter than the current cursor position, the
1786  * cursor will be positioned at the end of this line.
1787  *
1788  * This is to replace screen_skip for PageUp/Down operations.
1789  */
1790 static void cursor_skip(SpaceText* st, ARegion *ar, Text *text, int lines, int sel)
1791 {
1792         TextLine **linep;
1793         int oldl, oldc, *charp;
1794         
1795         if (sel) linep= &text->sell, charp= &text->selc;
1796         else linep= &text->curl, charp= &text->curc;
1797         oldl= txt_get_span(text->lines.first, *linep);
1798         oldc= *charp;
1799
1800         if(st && ar && st->wordwrap) {
1801                 int rell, relc;
1802
1803                 /* find line and offsets inside it needed to set cursor position */
1804                 if(cursor_skip_find_line(st, ar, lines, linep, charp, &rell, &relc))
1805                   *charp= text_get_cursor_rel (st, ar, *linep, rell, relc);
1806         } else {
1807                 while (lines>0 && (*linep)->next) {
1808                         *linep= (*linep)->next;
1809                         lines--;
1810                 }
1811                 while (lines<0 && (*linep)->prev) {
1812                         *linep= (*linep)->prev;
1813                         lines++;
1814                 }
1815         }
1816
1817         if (*charp > (*linep)->len) *charp= (*linep)->len;
1818
1819         if (!sel) txt_pop_sel(text);
1820         txt_undo_add_toop(text, sel?UNDO_STO:UNDO_CTO, oldl, oldc, txt_get_span(text->lines.first, *linep), *charp);
1821 }
1822
1823 static int text_move_cursor(bContext *C, int type, int select)
1824 {
1825         SpaceText *st= CTX_wm_space_text(C);
1826         Text *text= CTX_data_edit_text(C);
1827         ARegion *ar= CTX_wm_region(C);
1828
1829         /* ensure we have the right region, it's optional */
1830         if(ar && ar->regiontype != RGN_TYPE_WINDOW)
1831                 ar= NULL;
1832
1833         switch(type) {
1834                 case LINE_BEGIN:
1835                         if(st && st->wordwrap && ar) txt_wrap_move_bol(st, ar, select);
1836                         else txt_move_bol(text, select);
1837                         break;
1838                         
1839                 case LINE_END:
1840                         if(st && st->wordwrap && ar) txt_wrap_move_eol(st, ar, select);
1841                         else txt_move_eol(text, select);
1842                         break;
1843
1844                 case FILE_TOP:
1845                         txt_move_bof(text, select);
1846                         break;
1847                         
1848                 case FILE_BOTTOM:
1849                         txt_move_eof(text, select);
1850                         break;
1851
1852                 case PREV_WORD:
1853                         txt_jump_left(text, select);
1854                         break;
1855
1856                 case NEXT_WORD:
1857                         txt_jump_right(text, select);
1858                         break;
1859
1860                 case PREV_CHAR:
1861                         txt_move_left(text, select);
1862                         break;
1863
1864                 case NEXT_CHAR: 
1865                         txt_move_right(text, select);
1866                         break;
1867
1868                 case PREV_LINE:
1869                         if(st && st->wordwrap && ar) txt_wrap_move_up(st, ar, select);
1870                         else txt_move_up(text, select);
1871                         break;
1872                         
1873                 case NEXT_LINE:
1874                         if(st && st->wordwrap && ar) txt_wrap_move_down(st, ar, select);
1875                         else txt_move_down(text, select);
1876                         break;
1877
1878                 case PREV_PAGE:
1879                         if(st) cursor_skip(st, ar, st->text, -st->viewlines, select);
1880                         else cursor_skip(NULL, NULL, text, -10, select);
1881                         break;
1882
1883                 case NEXT_PAGE:
1884                         if(st) cursor_skip(st, ar, st->text, st->viewlines, select);
1885                         else cursor_skip(NULL, NULL, text, 10, select);
1886                         break;
1887         }
1888
1889         text_update_cursor_moved(C);
1890         WM_event_add_notifier(C, NC_TEXT|ND_CURSOR, text);
1891
1892         return OPERATOR_FINISHED;
1893 }
1894
1895 static int text_move_exec(bContext *C, wmOperator *op)
1896 {
1897         int type= RNA_enum_get(op->ptr, "type");
1898
1899         return text_move_cursor(C, type, 0);
1900 }
1901
1902 void TEXT_OT_move(wmOperatorType *ot)
1903 {
1904         /* identifiers */
1905         ot->name= "Move Cursor";
1906         ot->idname= "TEXT_OT_move";
1907         ot->description= "Move cursor to position type";
1908         
1909         /* api callbacks */
1910         ot->exec= text_move_exec;
1911         ot->poll= text_edit_poll;
1912
1913         /* properties */
1914         RNA_def_enum(ot->srna, "type", move_type_items, LINE_BEGIN, "Type", "Where to move cursor to");
1915 }
1916
1917 /******************* move select operator ********************/
1918
1919 static int text_move_select_exec(bContext *C, wmOperator *op)
1920 {
1921         int type= RNA_enum_get(op->ptr, "type");
1922
1923         return text_move_cursor(C, type, 1);
1924 }
1925
1926 void TEXT_OT_move_select(wmOperatorType *ot)
1927 {
1928         /* identifiers */
1929         ot->name= "Move Select";
1930         ot->idname= "TEXT_OT_move_select";
1931         ot->description= "Make selection from current cursor position to new cursor position type";
1932         
1933         /* api callbacks */
1934         ot->exec= text_move_select_exec;
1935         ot->poll= text_space_edit_poll;
1936
1937         /* properties */
1938         RNA_def_enum(ot->srna, "type", move_type_items, LINE_BEGIN, "Type", "Where to move cursor to, to make a selection");
1939 }
1940
1941 /******************* jump operator *********************/
1942
1943 static int text_jump_exec(bContext *C, wmOperator *op)
1944 {
1945         Text *text= CTX_data_edit_text(C);
1946         int line= RNA_int_get(op->ptr, "line");
1947         short nlines= txt_get_span(text->lines.first, text->lines.last)+1;
1948
1949         if(line < 1)
1950                 txt_move_toline(text, 1, 0);
1951         else if(line > nlines)
1952                 txt_move_toline(text, nlines-1, 0);
1953         else
1954                 txt_move_toline(text, line-1, 0);
1955
1956         text_update_cursor_moved(C);
1957         WM_event_add_notifier(C, NC_TEXT|ND_CURSOR, text);
1958
1959         return OPERATOR_FINISHED;
1960 }
1961
1962 static int text_jump_invoke(bContext *C, wmOperator *op, wmEvent *UNUSED(event))
1963 {
1964         return WM_operator_props_dialog_popup(C,op,200,100);
1965
1966 }
1967
1968 void TEXT_OT_jump(wmOperatorType *ot)
1969 {
1970         /* identifiers */
1971         ot->name= "Jump";
1972         ot->idname= "TEXT_OT_jump";
1973         ot->description= "Jump cursor to line";
1974         
1975         /* api callbacks */
1976         ot->invoke= text_jump_invoke;
1977         ot->exec= text_jump_exec;
1978         ot->poll= text_edit_poll;
1979
1980         /* properties */
1981         RNA_def_int(ot->srna, "line", 1, 1, INT_MAX, "Line", "Line number to jump to", 1, 10000);
1982 }
1983
1984 /******************* delete operator **********************/
1985
1986 static EnumPropertyItem delete_type_items[]= {
1987         {DEL_NEXT_CHAR, "NEXT_CHARACTER", 0, "Next Character", ""},
1988         {DEL_PREV_CHAR, "PREVIOUS_CHARACTER", 0, "Previous Character", ""},
1989         {DEL_NEXT_WORD, "NEXT_WORD", 0, "Next Word", ""},
1990         {DEL_PREV_WORD, "PREVIOUS_WORD", 0, "Previous Word", ""},
1991         {0, NULL, 0, NULL, NULL}};
1992
1993 static int text_delete_exec(bContext *C, wmOperator *op)
1994 {
1995         Text *text= CTX_data_edit_text(C);
1996         int type= RNA_enum_get(op->ptr, "type");
1997
1998         text_drawcache_tag_update(CTX_wm_space_text(C), 0);
1999
2000         if(type == DEL_PREV_WORD)
2001                 txt_backspace_word(text);
2002         else if(type == DEL_PREV_CHAR)
2003                 txt_backspace_char(text);
2004         else if(type == DEL_NEXT_WORD)
2005                 txt_delete_word(text);
2006         else if(type == DEL_NEXT_CHAR)
2007                 txt_delete_char(text);
2008
2009         text_update_line_edited(text->curl);
2010
2011         text_update_cursor_moved(C);
2012         WM_event_add_notifier(C, NC_TEXT|NA_EDITED, text);
2013
2014         /* run the script while editing, evil but useful */
2015         if(CTX_wm_space_text(C)->live_edit)
2016                 text_run_script(C, NULL);
2017         
2018         return OPERATOR_FINISHED;
2019 }
2020
2021 void TEXT_OT_delete(wmOperatorType *ot)
2022 {
2023         /* identifiers */
2024         ot->name= "Delete";
2025         ot->idname= "TEXT_OT_delete";
2026         ot->description= "Delete text by cursor position";
2027         
2028         /* api callbacks */
2029         ot->exec= text_delete_exec;
2030         ot->poll= text_edit_poll;
2031
2032         /* properties */
2033         RNA_def_enum(ot->srna, "type", delete_type_items, DEL_NEXT_CHAR, "Type", "Which part of the text to delete");
2034 }
2035
2036 /******************* toggle overwrite operator **********************/
2037
2038 static int text_toggle_overwrite_exec(bContext *C, wmOperator *UNUSED(op))
2039 {
2040         SpaceText *st= CTX_wm_space_text(C);
2041
2042         st->overwrite= !st->overwrite;
2043
2044         WM_event_add_notifier(C, NC_TEXT|ND_CURSOR, st->text);
2045
2046         return OPERATOR_FINISHED;
2047 }
2048
2049 void TEXT_OT_overwrite_toggle(wmOperatorType *ot)
2050 {
2051         /* identifiers */
2052         ot->name= "Toggle Overwrite";
2053         ot->idname= "TEXT_OT_overwrite_toggle";
2054         ot->description= "Toggle overwrite while typing";
2055         
2056         /* api callbacks */
2057         ot->exec= text_toggle_overwrite_exec;
2058         ot->poll= text_space_edit_poll;
2059 }
2060
2061 /******************* scroll operator **********************/
2062
2063 /* Moves the view vertically by the specified number of lines */
2064 static void txt_screen_skip(SpaceText *st, ARegion *ar, int lines)
2065 {
2066         int last;
2067
2068         st->top += lines;
2069
2070         last= text_get_total_lines(st, ar);
2071         last= last - (st->viewlines/2);
2072         
2073         if(st->top>last) st->top= last;
2074         if(st->top<0) st->top= 0;
2075 }
2076
2077 /* quick enum for tsc->zone (scroller handles) */
2078 enum {
2079         SCROLLHANDLE_BAR,
2080         SCROLLHANDLE_MIN_OUTSIDE,
2081         SCROLLHANDLE_MAX_OUTSIDE
2082 };
2083
2084 typedef struct TextScroll {
2085         short old[2];
2086         short delta[2];
2087
2088         int first;
2089         int scrollbar;
2090
2091         int zone;
2092 } TextScroll;
2093
2094 static int text_scroll_poll(bContext *C)
2095 {
2096         /* it should be possible to still scroll linked texts to read them, even if they can't be edited... */
2097         return CTX_data_edit_text(C) != NULL;
2098 }
2099
2100 static int text_scroll_exec(bContext *C, wmOperator *op)
2101 {
2102         SpaceText *st= CTX_wm_space_text(C);
2103         ARegion *ar= CTX_wm_region(C);
2104
2105         int lines= RNA_int_get(op->ptr, "lines");
2106
2107         if(lines == 0)
2108                 return OPERATOR_CANCELLED;
2109
2110         txt_screen_skip(st, ar, lines*U.wheellinescroll);
2111
2112         ED_area_tag_redraw(CTX_wm_area(C));
2113
2114         return OPERATOR_FINISHED;
2115 }
2116
2117 static void text_scroll_apply(bContext *C, wmOperator *op, wmEvent *event)
2118 {
2119         SpaceText *st= CTX_wm_space_text(C);
2120         ARegion *ar= CTX_wm_region(C);
2121         TextScroll *tsc= op->customdata;
2122         int mval[2]= {event->x, event->y};
2123         short txtdelta[2] = {0, 0};
2124
2125         text_update_character_width(st);
2126
2127         if(tsc->first) {
2128                 tsc->old[0]= mval[0];
2129                 tsc->old[1]= mval[1];
2130                 tsc->first= 0;
2131         }
2132
2133         tsc->delta[0]+= mval[0] - tsc->old[0];
2134         tsc->delta[1]+= mval[1] - tsc->old[1];
2135
2136         if(!tsc->scrollbar) {
2137                 txtdelta[0]= -tsc->delta[0]/st->cwidth;
2138                 txtdelta[1]= tsc->delta[1]/st->lheight;
2139
2140                 tsc->delta[0]%= st->cwidth;
2141                 tsc->delta[1]%= st->lheight;
2142         }
2143         else {
2144                 txtdelta[1]= -tsc->delta[1]*st->pix_per_line;
2145                 tsc->delta[1]+= txtdelta[1]/st->pix_per_line;
2146         }
2147
2148         if(txtdelta[0] || txtdelta[1]) {
2149                 txt_screen_skip(st, ar, txtdelta[1]);
2150
2151                 if(st->wordwrap) {
2152                         st->left= 0;
2153                 }
2154                 else {
2155                         st->left+= txtdelta[0];
2156                         if(st->left<0) st->left= 0;
2157                 }
2158
2159                 ED_area_tag_redraw(CTX_wm_area(C));
2160         }
2161
2162         tsc->old[0]= mval[0];
2163         tsc->old[1]= mval[1];
2164 }
2165
2166 static void scroll_exit(bContext *C, wmOperator *op)
2167 {
2168         SpaceText *st= CTX_wm_space_text(C);
2169
2170         st->flags &= ~ST_SCROLL_SELECT;
2171         MEM_freeN(op->customdata);
2172 }
2173
2174 static int text_scroll_modal(bContext *C, wmOperator *op, wmEvent *event)
2175 {
2176         TextScroll *tsc= op->customdata;
2177         SpaceText *st= CTX_wm_space_text(C);
2178         ARegion *ar= CTX_wm_region(C);
2179
2180         switch(event->type) {
2181                 case MOUSEMOVE:
2182                         if(tsc->zone == SCROLLHANDLE_BAR)
2183                                 text_scroll_apply(C, op, event);
2184                         break;
2185                 case LEFTMOUSE:
2186                 case RIGHTMOUSE:
2187                 case MIDDLEMOUSE:
2188                         if(ELEM(tsc->zone, SCROLLHANDLE_MIN_OUTSIDE, SCROLLHANDLE_MAX_OUTSIDE)) {
2189                                 int last;
2190
2191                                 st->top+= st->viewlines * (tsc->zone==SCROLLHANDLE_MIN_OUTSIDE ? 1 : -1);
2192
2193                                 last= text_get_total_lines(st, ar);
2194                                 last= last - (st->viewlines/2);
2195
2196                                 CLAMP(st->top, 0, last);
2197
2198                                 ED_area_tag_redraw(CTX_wm_area(C));
2199                         }
2200                         scroll_exit(C, op);
2201                         return OPERATOR_FINISHED;
2202         }
2203
2204         return OPERATOR_RUNNING_MODAL;
2205 }
2206
2207 static int text_scroll_cancel(bContext *C, wmOperator *op)
2208 {
2209         scroll_exit(C, op);
2210
2211         return OPERATOR_CANCELLED;
2212 }
2213
2214 static int text_scroll_invoke(bContext *C, wmOperator *op, wmEvent *event)
2215 {
2216         SpaceText *st= CTX_wm_space_text(C);
2217         TextScroll *tsc;
2218         
2219         if(RNA_struct_property_is_set(op->ptr, "lines"))
2220                 return text_scroll_exec(C, op);
2221         
2222         tsc= MEM_callocN(sizeof(TextScroll), "TextScroll");
2223         tsc->first= 1;
2224         tsc->zone= SCROLLHANDLE_BAR;
2225         op->customdata= tsc;
2226         
2227         st->flags|= ST_SCROLL_SELECT;
2228         
2229         if (event->type == MOUSEPAN) {
2230                 text_update_character_width(st);
2231                 
2232                 tsc->old[0] = event->x;
2233                 tsc->old[1] = event->y;
2234                 /* Sensitivity of scroll set to 4pix per line/char */
2235                 tsc->delta[0] = (event->x - event->prevx)*st->cwidth/4;
2236                 tsc->delta[1] = (event->y - event->prevy)*st->lheight/4;
2237                 tsc->first = 0;
2238                 tsc->scrollbar = 0;
2239                 text_scroll_apply(C, op, event);
2240                 scroll_exit(C, op);
2241                 return OPERATOR_FINISHED;
2242         }
2243
2244         WM_event_add_modal_handler(C, op);
2245         
2246         return OPERATOR_RUNNING_MODAL;
2247 }
2248
2249 void TEXT_OT_scroll(wmOperatorType *ot)
2250 {
2251         /* identifiers */
2252         ot->name= "Scroll";
2253         /* don't really see the difference between this and
2254          * scroll_bar. Both do basically the same thing (aside 
2255          * from keymaps).*/
2256         ot->idname= "TEXT_OT_scroll";
2257         ot->description= "Scroll text screen";
2258         
2259         /* api callbacks */
2260         ot->exec= text_scroll_exec;
2261         ot->invoke= text_scroll_invoke;
2262         ot->modal= text_scroll_modal;
2263         ot->cancel= text_scroll_cancel;
2264         ot->poll= text_scroll_poll;
2265
2266         /* flags */
2267         ot->flag= OPTYPE_BLOCKING|OPTYPE_GRAB_POINTER;
2268
2269         /* properties */
2270         RNA_def_int(ot->srna, "lines", 1, INT_MIN, INT_MAX, "Lines", "Number of lines to scroll", -100, 100);
2271 }
2272
2273 /******************** scroll bar operator *******************/
2274
2275 static int text_region_scroll_poll(bContext *C)
2276 {
2277         /* same as text_region_edit_poll except it works on libdata too */
2278         SpaceText *st= CTX_wm_space_text(C);
2279         Text *text= CTX_data_edit_text(C);
2280         ARegion *ar= CTX_wm_region(C);
2281
2282         if(!st || !text)
2283                 return 0;
2284         
2285         if(!ar || ar->regiontype != RGN_TYPE_WINDOW)
2286                 return 0;
2287         
2288         return 1;
2289 }
2290
2291 static int text_scroll_bar_invoke(bContext *C, wmOperator *op, wmEvent *event)
2292 {
2293         SpaceText *st= CTX_wm_space_text(C);
2294         ARegion *ar= CTX_wm_region(C);
2295         TextScroll *tsc;
2296         const int *mval= event->mval;
2297         int zone= -1;
2298
2299         if(RNA_struct_property_is_set(op->ptr, "lines"))
2300                 return text_scroll_exec(C, op);
2301         
2302         /* verify we are in the right zone */
2303         if(mval[0]>st->txtbar.xmin && mval[0]<st->txtbar.xmax) {
2304                 if(mval[1]>=st->txtbar.ymin && mval[1]<=st->txtbar.ymax) {
2305                         /* mouse inside scroll handle */
2306                         zone = SCROLLHANDLE_BAR;
2307                 }
2308                 else if(mval[1]>TXT_SCROLL_SPACE && mval[1]<ar->winy-TXT_SCROLL_SPACE) {
2309                         if(mval[1]<st->txtbar.ymin) zone= SCROLLHANDLE_MIN_OUTSIDE;
2310                         else zone= SCROLLHANDLE_MAX_OUTSIDE;
2311                 }
2312         }
2313
2314         if(zone == -1) {
2315                 /* we are outside slider - nothing to do */
2316                 return OPERATOR_PASS_THROUGH;
2317         }
2318
2319         tsc= MEM_callocN(sizeof(TextScroll), "TextScroll");
2320         tsc->first= 1;
2321         tsc->scrollbar= 1;
2322         tsc->zone= zone;
2323         op->customdata= tsc;
2324         st->flags|= ST_SCROLL_SELECT;
2325
2326         /* jump scroll, works in v2d but needs to be added here too :S */
2327         if (event->type == MIDDLEMOUSE) {
2328                 tsc->old[0] = ar->winrct.xmin + (st->txtbar.xmax + st->txtbar.xmin) / 2;
2329                 tsc->old[1] = ar->winrct.ymin + (st->txtbar.ymax + st->txtbar.ymin) / 2;
2330
2331                 tsc->delta[0] = 0;
2332                 tsc->delta[1] = 0;
2333                 tsc->first = 0;
2334                 tsc->zone= SCROLLHANDLE_BAR;
2335                 text_scroll_apply(C, op, event);
2336         }
2337
2338         WM_event_add_modal_handler(C, op);
2339
2340         return OPERATOR_RUNNING_MODAL;
2341 }
2342
2343 void TEXT_OT_scroll_bar(wmOperatorType *ot)
2344 {
2345         /* identifiers */
2346         ot->name= "Scrollbar";
2347         /* don't really see the difference between this and
2348          * scroll. Both do basically the same thing (aside 
2349          * from keymaps).*/
2350         ot->idname= "TEXT_OT_scroll_bar";
2351         ot->description= "Scroll text screen";
2352         
2353         /* api callbacks */
2354         ot->invoke= text_scroll_bar_invoke;
2355         ot->modal= text_scroll_modal;
2356         ot->cancel= text_scroll_cancel;
2357         ot->poll= text_region_scroll_poll;
2358
2359         /* flags */
2360         ot->flag= OPTYPE_BLOCKING;
2361
2362         /* properties */
2363         RNA_def_int(ot->srna, "lines", 1, INT_MIN, INT_MAX, "Lines", "Number of lines to scroll", -100, 100);
2364 }
2365
2366 /******************* set selection operator **********************/
2367
2368 typedef struct SetSelection {
2369         int selecting;
2370         int selc, sell;
2371         short old[2];
2372 } SetSelection;
2373
2374 static int flatten_len(SpaceText *st, const char *str)
2375 {
2376         int i, total = 0;
2377
2378         for(i = 0; str[i]; i += BLI_str_utf8_size(str+i)) {
2379                 if(str[i]=='\t') {
2380                         total += st->tabnumber - total%st->tabnumber;
2381                 }
2382                 else total++;
2383         }
2384         
2385         return total;
2386 }
2387
2388 static int flatten_index_to_offset(SpaceText *st, const char *str, int index)
2389 {
2390         int i, j;
2391         for (i= 0, j= 0; i < index; j += BLI_str_utf8_size(str+j))
2392                 if(str[j]=='\t')
2393                         i += st->tabnumber - i%st->tabnumber;
2394                 else
2395                         i++;
2396         
2397         return j;
2398 }
2399
2400 static TextLine *get_first_visible_line(SpaceText *st, ARegion *ar, int *y)
2401 {
2402         TextLine *linep = st->text->lines.first;
2403         int i;
2404         for (i = st->top; i > 0 && linep; ) {
2405                 int lines = text_get_visible_lines(st, ar, linep->line);
2406                 
2407                 if (i-lines < 0) {
2408                         *y += i;
2409                         break;
2410                 } else {
2411                         linep = linep->next;
2412                         i -= lines;
2413                 }
2414         }
2415         return linep;
2416 }
2417
2418 static void text_cursor_set_to_pos_wrapped(SpaceText *st, ARegion *ar, int x, int y, int sel)
2419 {
2420         Text *text = st->text;
2421         int max = wrap_width(st, ar); /* view */
2422         int charp = -1;               /* mem */
2423         int loop = 1, found = 0;      /* flags */
2424         char ch;
2425         
2426         /* Point to first visible line */
2427         TextLine *linep = get_first_visible_line(st, ar, &y);
2428         
2429         while(loop && linep) {
2430                 int i = 0, start = 0, end = max; /* view */
2431                 int j = 0, curs = 0, endj = 0;   /* mem */
2432                 int chop = 1;                    /* flags */
2433                 
2434                 for (; loop; j += BLI_str_utf8_size(linep->line+j)) {
2435                         int chars;
2436                         
2437                         /* Mimic replacement of tabs */
2438                         ch = linep->line[j];
2439                         if(ch == '\t') {
2440                                 chars = st->tabnumber - i%st->tabnumber;
2441                                 ch = ' ';
2442                         }
2443                         else chars = 1;
2444                         
2445                         while (chars--) {
2446                                 /* Gone too far, go back to last wrap point */
2447                                 if (y < 0) {
2448                                         charp = endj;
2449                                         loop = 0;
2450                                         break;
2451                                         /* Exactly at the cursor */
2452                                 }
2453                                 else if (y == 0 && i-start == x) {
2454                                         /* current position could be wrapped to next line */
2455                                         /* this should be checked when end of current line would be reached */
2456                                         charp = curs= j;
2457                                         found = 1;
2458                                         /* Prepare curs for next wrap */
2459                                 }
2460                                 else if(i - end == x) {
2461                                         curs = j;
2462                                 }
2463                                 if (i - start >= max) {
2464                                         if (found) {
2465                                                 /* exact cursor position was found, check if it's */
2466                                                 /* still on needed line (hasn't been wrapped) */
2467                                                 if (charp > endj && !chop && ch!='\0') charp = endj;
2468                                                 loop = 0;
2469                                                 break;
2470                                         }
2471                                         
2472                                         if(chop) endj = j;
2473                                         start = end;
2474                                         end += max;
2475                                         
2476                                         if(j < linep->len)
2477                                                 y--;
2478                                         
2479                                         chop = 1;
2480                                         if (y == 0 && i-start >= x) {
2481                                                 charp = curs;
2482                                                 loop = 0;
2483                                                 break;
2484                                         }
2485                                 }
2486                                 else if (ch == ' ' || ch == '-' || ch == '\0') {
2487                                         if (found) {
2488                                                 loop = 0;
2489                                                 break;
2490                                         }
2491                                         
2492                                         if(y == 0 && i-start >= x) {
2493                                                 charp = curs;
2494                                                 loop = 0;
2495                                                 break;
2496                                         }
2497                                         end = i + 1;
2498                                         endj = j;
2499                                         chop = 0;
2500                                 }
2501                                 i++;
2502                         }
2503                         
2504                         if(ch == '\0') break;
2505                 }
2506                 
2507                 if(!loop || found) break;
2508                 
2509                 if(!linep->next) {
2510                         charp = linep->len;
2511                         break;
2512                 }
2513                 
2514                 /* On correct line but didn't meet cursor, must be at end */
2515                 if (y == 0) {
2516                         charp = linep->len;
2517                         break;
2518                 }
2519                 linep = linep->next;
2520                 
2521                 y--;
2522         }
2523
2524         if (linep && charp != -1) {
2525                 if(sel) { text->sell = linep; text->selc = charp; }
2526                 else { text->curl = linep; text->curc = charp; }
2527         }
2528 }
2529
2530 static void text_cursor_set_to_pos(SpaceText *st, ARegion *ar, int x, int y, int sel)
2531 {
2532         Text *text= st->text;
2533         text_update_character_width(st);
2534         y= (ar->winy - 2 - y)/st->lheight;
2535
2536         if(st->showlinenrs) x-= TXT_OFFSET+TEXTXLOC;
2537         else x-= TXT_OFFSET;
2538
2539         if(x<0) x= 0;
2540         x = text_pixel_x_to_index(st, x) + st->left;
2541         
2542         if(st->wordwrap) {
2543                 text_cursor_set_to_pos_wrapped(st, ar, x, y, sel);
2544         }
2545         else {
2546                 TextLine **linep;
2547                 int *charp;
2548                 int w;
2549                 
2550                 if(sel) { linep= &text->sell; charp= &text->selc; } 
2551                 else { linep= &text->curl; charp= &text->curc; }
2552                 
2553                 y-= txt_get_span(text->lines.first, *linep) - st->top;
2554                 
2555                 if(y>0) {
2556                         while(y-- != 0) if((*linep)->next) *linep= (*linep)->next;
2557                 }
2558                 else if(y<0) {
2559                         while(y++ != 0) if((*linep)->prev) *linep= (*linep)->prev;
2560                 }
2561
2562                 
2563                 w= flatten_len(st, (*linep)->line);
2564                 if(x<w) *charp= flatten_index_to_offset(st, (*linep)->line, x);
2565                 else *charp= (*linep)->len;
2566         }
2567         if(!sel) txt_pop_sel(text);
2568 }
2569
2570 static void text_cursor_set_apply(bContext *C, wmOperator *op, wmEvent *event)
2571 {
2572         SpaceText *st= CTX_wm_space_text(C);
2573         ARegion *ar= CTX_wm_region(C);
2574         SetSelection *ssel= op->customdata;
2575
2576         if(event->mval[1]<0 || event->mval[1]>ar->winy) {
2577                 int d= (ssel->old[1]-event->mval[1])*st->pix_per_line;
2578                 if(d) txt_screen_skip(st, ar, d);
2579
2580                 text_cursor_set_to_pos(st, ar, event->mval[0], event->mval[1]<0?0:ar->winy, 1);
2581
2582                 text_update_cursor_moved(C);
2583                 WM_event_add_notifier(C, NC_TEXT|ND_CURSOR, st->text);
2584         } 
2585         else if(!st->wordwrap && (event->mval[0]<0 || event->mval[0]>ar->winx)) {
2586                 if(event->mval[0]>ar->winx) st->left++;
2587                 else if(event->mval[0]<0 && st->left>0) st->left--;
2588                 
2589                 text_cursor_set_to_pos(st, ar, event->mval[0], event->mval[1], 1);
2590                 
2591                 text_update_cursor_moved(C);
2592                 WM_event_add_notifier(C, NC_TEXT|ND_CURSOR, st->text);
2593                 // XXX PIL_sleep_ms(10);
2594         } 
2595         else {
2596                 text_cursor_set_to_pos(st, ar, event->mval[0], event->mval[1], 1);
2597
2598                 text_update_cursor_moved(C);
2599                 WM_event_add_notifier(C, NC_TEXT|ND_CURSOR, st->text);
2600
2601                 ssel->old[0]= event->mval[0];
2602                 ssel->old[1]= event->mval[1];
2603         } 
2604 }
2605
2606 static void text_cursor_set_exit(bContext *C, wmOperator *op)
2607 {
2608         SpaceText *st= CTX_wm_space_text(C);
2609         Text *text= st->text;
2610         SetSelection *ssel= op->customdata;
2611         int linep2, charp2;
2612         char *buffer;
2613
2614         if(txt_has_sel(text)) {
2615                 buffer = txt_sel_to_buf(text);
2616                 WM_clipboard_text_set(buffer, 1);
2617                 MEM_freeN(buffer);
2618         }
2619
2620         linep2= txt_get_span(st->text->lines.first, st->text->sell);
2621         charp2= st->text->selc;
2622                 
2623         if(ssel->sell!=linep2 || ssel->selc!=charp2)
2624                 txt_undo_add_toop(st->text, UNDO_STO, ssel->sell, ssel->selc, linep2, charp2);
2625
2626         text_update_cursor_moved(C);
2627         WM_event_add_notifier(C, NC_TEXT|ND_CURSOR, st->text);
2628
2629         MEM_freeN(ssel);
2630 }
2631
2632 static int text_set_selection_invoke(bContext *C, wmOperator *op, wmEvent *event)
2633 {
2634         SpaceText *st= CTX_wm_space_text(C);
2635         SetSelection *ssel;
2636
2637         if(event->mval[0]>=st->txtbar.xmin)
2638                 return OPERATOR_PASS_THROUGH;
2639
2640         op->customdata= MEM_callocN(sizeof(SetSelection), "SetCursor");
2641         ssel= op->customdata;
2642         ssel->selecting= RNA_boolean_get(op->ptr, "select");
2643
2644         ssel->old[0]= event->mval[0];
2645         ssel->old[1]= event->mval[1];
2646
2647         ssel->sell= txt_get_span(st->text->lines.first, st->text->sell);
2648         ssel->selc= st->text->selc;
2649
2650         WM_event_add_modal_handler(C, op);
2651
2652         text_cursor_set_apply(C, op, event);
2653
2654         return OPERATOR_RUNNING_MODAL;
2655 }
2656
2657 static int text_set_selection_modal(bContext *C, wmOperator *op, wmEvent *event)
2658 {
2659         switch(event->type) {
2660                 case LEFTMOUSE:
2661                 case MIDDLEMOUSE:
2662                 case RIGHTMOUSE:
2663                         text_cursor_set_exit(C, op);
2664                         return OPERATOR_FINISHED;
2665                 case MOUSEMOVE:
2666                         text_cursor_set_apply(C, op, event);
2667                         break;
2668         }
2669
2670         return OPERATOR_RUNNING_MODAL;
2671 }
2672
2673 static int text_set_selection_cancel(bContext *C, wmOperator *op)
2674 {
2675         text_cursor_set_exit(C, op);
2676         return OPERATOR_FINISHED;
2677 }
2678
2679 void TEXT_OT_selection_set(wmOperatorType *ot)
2680 {
2681         /* identifiers */
2682         ot->name= "Set Selection";
2683         ot->idname= "TEXT_OT_selection_set";
2684         ot->description= "Set cursor selection";
2685
2686         /* api callbacks */
2687         ot->invoke= text_set_selection_invoke;
2688         ot->modal= text_set_selection_modal;
2689         ot->cancel= text_set_selection_cancel;
2690         ot->poll= text_region_edit_poll;
2691
2692         /* properties */
2693         RNA_def_boolean(ot->srna, "select", 0, "Select", "Set selection end rather than cursor");
2694 }
2695
2696 /******************* set cursor operator **********************/
2697
2698 static int text_cursor_set_exec(bContext *C, wmOperator *op)
2699 {
2700         SpaceText *st= CTX_wm_space_text(C);
2701         Text *text= st->text;
2702         ARegion *ar= CTX_wm_region(C);
2703         int x= RNA_int_get(op->ptr, "x");
2704         int y= RNA_int_get(op->ptr, "y");
2705         int oldl, oldc;
2706
2707         oldl= txt_get_span(text->lines.first, text->curl);
2708         oldc= text->curc;
2709
2710         text_cursor_set_to_pos(st, ar, x, y, 0);
2711
2712         txt_undo_add_toop(text, UNDO_CTO, oldl, oldc, txt_get_span(text->lines.first, text->curl), text->curc);
2713
2714         text_update_cursor_moved(C);
2715         WM_event_add_notifier(C, NC_TEXT|ND_CURSOR, st->text);
2716
2717         return OPERATOR_PASS_THROUGH;
2718 }
2719
2720 static int text_cursor_set_invoke(bContext *C, wmOperator *op, wmEvent *event)
2721 {
2722         SpaceText *st= CTX_wm_space_text(C);
2723
2724         if(event->mval[0]>=st->txtbar.xmin)
2725                 return OPERATOR_PASS_THROUGH;
2726
2727         RNA_int_set(op->ptr, "x", event->mval[0]);
2728         RNA_int_set(op->ptr, "y", event->mval[1]);
2729
2730         return text_cursor_set_exec(C, op);
2731 }
2732
2733 void TEXT_OT_cursor_set(wmOperatorType *ot)
2734 {
2735         /* identifiers */
2736         ot->name= "Set Cursor";
2737         ot->idname= "TEXT_OT_cursor_set";
2738         ot->description= "Set cursor position";
2739
2740         /* api callbacks */
2741         ot->invoke= text_cursor_set_invoke;
2742         ot->exec= text_cursor_set_exec;
2743         ot->poll= text_region_edit_poll;
2744
2745         /* properties */
2746         RNA_def_int(ot->srna, "x", 0, INT_MIN, INT_MAX, "X", "", INT_MIN, INT_MAX);
2747         RNA_def_int(ot->srna, "y", 0, INT_MIN, INT_MAX, "Y", "", INT_MIN, INT_MAX);
2748 }
2749
2750 /******************* line number operator **********************/
2751
2752 static int text_line_number_invoke(bContext *C, wmOperator *UNUSED(op), wmEvent *event)
2753 {
2754         SpaceText *st= CTX_wm_space_text(C);
2755         Text *text= CTX_data_edit_text(C);
2756         ARegion *ar= CTX_wm_region(C);
2757         const int *mval= event->mval;
2758         double time;
2759         static int jump_to= 0;
2760         static double last_jump= 0;
2761
2762         text_update_character_width(st);
2763
2764         if(!st->showlinenrs)
2765                 return OPERATOR_PASS_THROUGH;
2766
2767         if(!(mval[0]>2 && mval[0]<(TXT_OFFSET + TEXTXLOC) && mval[1]>2 && mval[1]<ar->winy-2))
2768                 return OPERATOR_PASS_THROUGH;
2769
2770         if(!(event->ascii>='0' && event->ascii<='9'))
2771                 return OPERATOR_PASS_THROUGH;
2772
2773         time = PIL_check_seconds_timer();
2774         if(last_jump < time-1)
2775                 jump_to= 0;
2776
2777         jump_to *= 10;
2778         jump_to += (int)(event->ascii-'0');
2779
2780         txt_move_toline(text, jump_to-1, 0);
2781         last_jump= time;
2782
2783         text_update_cursor_moved(C);
2784         WM_event_add_notifier(C, NC_TEXT|ND_CURSOR, text);
2785
2786         return OPERATOR_FINISHED;
2787 }
2788
2789 void TEXT_OT_line_number(wmOperatorType *ot)
2790 {
2791         /* identifiers */
2792         ot->name= "Line Number";
2793         ot->idname= "TEXT_OT_line_number";
2794         ot->description= "The current line number";
2795         
2796         /* api callbacks */
2797         ot->invoke= text_line_number_invoke;
2798         ot->poll= text_region_edit_poll;
2799 }
2800
2801 /******************* insert operator **********************/
2802
2803 static int text_insert_exec(bContext *C, wmOperator *op)
2804 {
2805         SpaceText *st= CTX_wm_space_text(C);
2806         Text *text= CTX_data_edit_text(C);
2807         char *str;
2808         int done = 0;
2809         size_t i = 0;
2810         unsigned int code;
2811
2812         text_drawcache_tag_update(st, 0);
2813
2814         str= RNA_string_get_alloc(op->ptr, "text", NULL, 0);
2815
2816         if(st && st->overwrite) {
2817                 while (str[i]) {
2818                         code = BLI_str_utf8_as_unicode_step(str, &i);
2819                         done |= txt_replace_char(text, code);
2820                 }
2821         } else {
2822                 while (str[i]) {
2823                         code = BLI_str_utf8_as_unicode_step(str, &i);
2824                         done |= txt_add_char(text, code);
2825                 }
2826         }
2827
2828         MEM_freeN(str);
2829         
2830         if(!done)
2831                 return OPERATOR_CANCELLED;
2832
2833         text_update_line_edited(text->curl);
2834
2835         text_update_cursor_moved(C);
2836         WM_event_add_notifier(C, NC_TEXT|NA_EDITED, text);
2837
2838         return OPERATOR_FINISHED;
2839 }
2840
2841 static int text_insert_invoke(bContext *C, wmOperator *op, wmEvent *event)
2842 {
2843         int ret;
2844
2845         // if(!RNA_struct_property_is_set(op->ptr, "text")) { /* always set from keymap XXX */
2846         if(!RNA_string_length(op->ptr, "text")) {
2847                 /* if alt/ctrl/super are pressed pass through */
2848                 if(event->ctrl || event->oskey) {
2849                         return OPERATOR_PASS_THROUGH;
2850                 }
2851                 else {
2852                         char str[BLI_UTF8_MAX+1];
2853                         size_t len;
2854                         
2855                         if (event->utf8_buf[0]) {
2856                                 len = BLI_str_utf8_size(event->utf8_buf);
2857                                 memcpy(str, event->utf8_buf, len);
2858                         } else {
2859                                 /* in theory, ghost can set value to extended ascii here */
2860                                 len = BLI_str_utf8_from_unicode(event->ascii, str);
2861                         }
2862                         str[len]= '\0';
2863                         RNA_string_set(op->ptr, "text", str);
2864                 }
2865         }
2866
2867         ret = text_insert_exec(C, op);
2868         
2869         /* run the script while editing, evil but useful */
2870         if(ret==OPERATOR_FINISHED && CTX_wm_space_text(C)->live_edit)
2871                 text_run_script(C, NULL);
2872
2873         return ret;
2874 }
2875
2876 void TEXT_OT_insert(wmOperatorType *ot)
2877 {
2878         /* identifiers */
2879         ot->name= "Insert";
2880         ot->idname= "TEXT_OT_insert";
2881         ot->description= "Insert text at cursor position";
2882         
2883         /* api callbacks */
2884         ot->exec= text_insert_exec;
2885         ot->invoke= text_insert_invoke;
2886         ot->poll= text_edit_poll;
2887
2888         /* properties */
2889         RNA_def_string(ot->srna, "text", "", 0, "Text", "Text to insert at the cursor position");
2890 }
2891
2892 /******************* find operator *********************/
2893
2894 /* mode */
2895 #define TEXT_FIND               0
2896 #define TEXT_REPLACE    1
2897 #define TEXT_MARK_ALL   2
2898
2899 static int text_find_and_replace(bContext *C, wmOperator *op, short mode)
2900 {
2901         Main *bmain= CTX_data_main(C);
2902         SpaceText *st= CTX_wm_space_text(C);
2903         Text *start= NULL, *text= st->text;
2904         int flags, first= 1;
2905         int found = 0;
2906         char *tmp;
2907
2908         if(!st->findstr[0] || (mode == TEXT_REPLACE && !st->replacestr[0]))
2909                 return OPERATOR_CANCELLED;
2910
2911         flags= st->flags;
2912         if(flags & ST_FIND_ALL)
2913                 flags ^= ST_FIND_WRAP;
2914
2915         do {
2916                 int proceed= 0;
2917
2918                 if(first) {
2919                         if(text->markers.first)
2920                                 WM_event_add_notifier(C, NC_TEXT|NA_EDITED, text);
2921
2922                         txt_clear_markers(text, TMARK_GRP_FINDALL, 0);
2923                 }
2924
2925                 first= 0;
2926                 
2927                 /* Replace current */
2928                 if(mode!=TEXT_FIND && txt_has_sel(text)) {
2929                         tmp= txt_sel_to_buf(text);
2930
2931                         if(flags & ST_MATCH_CASE) proceed= strcmp(st->findstr, tmp)==0;
2932                         else proceed= BLI_strcasecmp(st->findstr, tmp)==0;
2933
2934                         if(proceed) {
2935                                 if(mode==TEXT_REPLACE) {
2936                                         txt_insert_buf(text, st->replacestr);
2937                                         if(text->curl && text->curl->format) {
2938                                                 MEM_freeN(text->curl->format);
2939                                                 text->curl->format= NULL;
2940                                         }
2941                                         text_update_cursor_moved(C);
2942                                         WM_event_add_notifier(C, NC_TEXT|NA_EDITED, text);
2943                                         text_drawcache_tag_update(CTX_wm_space_text(C), 1);
2944                                 }
2945                                 else if(mode==TEXT_MARK_ALL) {
2946                                         unsigned char color[4];
2947                                         UI_GetThemeColor4ubv(TH_SHADE2, color);
2948
2949                                         if(txt_find_marker(text, text->curl, text->selc, TMARK_GRP_FINDALL, 0)) {
2950                                                 if(tmp) MEM_freeN(tmp), tmp=NULL;
2951                                                 break;
2952                                         }
2953
2954                                         txt_add_marker(text, text->curl, text->curc, text->selc, color, TMARK_GRP_FINDALL, TMARK_EDITALL);
2955                                         text_update_cursor_moved(C);
2956                                         WM_event_add_notifier(C, NC_TEXT|NA_EDITED, text);
2957                                 }
2958                         }
2959                         MEM_freeN(tmp);
2960                         tmp= NULL;
2961                 }
2962
2963                 /* Find next */
2964                 if(txt_find_string(text, st->findstr, flags & ST_FIND_WRAP, flags & ST_MATCH_CASE)) {
2965                         text_update_cursor_moved(C);
2966                         WM_event_add_notifier(C, NC_TEXT|ND_CURSOR, text);
2967                 }
2968                 else if(flags & ST_FIND_ALL) {
2969                         if(text==start) break;
2970                         if(!start) start= text;
2971                         if(text->id.next)
2972                                 text= st->text= text->id.next;
2973                         else
2974                                 text= st->text= bmain->text.first;
2975                         txt_move_toline(text, 0, 0);
2976                         text_update_cursor_moved(C);
2977                         WM_event_add_notifier(C, NC_TEXT|ND_CURSOR, text);
2978                         first= 1;
2979                 }
2980                 else {
2981                         if(!found && !proceed) BKE_reportf(op->reports, RPT_ERROR, "Text not found: %s", st->findstr);
2982                         break;
2983                 }
2984                 found = 1;
2985         } while(mode==TEXT_MARK_ALL);
2986
2987         return OPERATOR_FINISHED;
2988 }
2989
2990 static int text_find_exec(bContext *C, wmOperator *op)
2991 {
2992         return text_find_and_replace(C, op, TEXT_FIND);
2993 }
2994
2995 void TEXT_OT_find(wmOperatorType *ot)
2996 {
2997         /* identifiers */
2998         ot->name= "Find";
2999         ot->idname= "TEXT_OT_find";
3000         ot->description= "Find specified text";
3001         
3002         /* api callbacks */
3003         ot->exec= text_find_exec;
3004         ot->poll= text_space_edit_poll;
3005 }
3006
3007 /******************* replace operator *********************/
3008
3009 static int text_replace_exec(bContext *C, wmOperator *op)
3010 {
3011         return text_find_and_replace(C, op, TEXT_REPLACE);
3012 }
3013
3014 void TEXT_OT_replace(wmOperatorType *ot)
3015 {
3016         /* identifiers */
3017         ot->name= "Replace";
3018         ot->idname= "TEXT_OT_replace";
3019         ot->description= "Replace text with the specified text";
3020
3021         /* api callbacks */
3022         ot->exec= text_replace_exec;
3023         ot->poll= text_space_edit_poll;
3024 }
3025
3026 /******************* mark all operator *********************/
3027
3028 static int text_mark_all_exec(bContext *C, wmOperator *op)
3029 {
3030         return text_find_and_replace(C, op, TEXT_MARK_ALL);
3031 }
3032
3033 void TEXT_OT_mark_all(wmOperatorType *ot)
3034 {
3035         /* identifiers */
3036         ot->name= "Mark All";
3037         ot->idname= "TEXT_OT_mark_all";
3038         ot->description= "Mark all specified text";
3039         
3040         /* api callbacks */
3041         ot->exec= text_mark_all_exec;
3042         ot->poll= text_space_edit_poll;
3043 }
3044
3045 /******************* find set selected *********************/
3046
3047 static int text_find_set_selected_exec(bContext *C, wmOperator *op)
3048 {
3049         SpaceText *st= CTX_wm_space_text(C);
3050         Text *text= CTX_data_edit_text(C);
3051         char *tmp;
3052
3053         tmp= txt_sel_to_buf(text);
3054         BLI_strncpy(st->findstr, tmp, ST_MAX_FIND_STR);
3055         MEM_freeN(tmp);
3056
3057         if(!st->findstr[0])
3058                 return OPERATOR_FINISHED;
3059
3060         return text_find_and_replace(C, op, TEXT_FIND);
3061 }
3062
3063 void TEXT_OT_find_set_selected(wmOperatorType *ot)
3064 {
3065         /* identifiers */
3066         ot->name= "Find Set Selected";
3067         ot->idname= "TEXT_OT_find_set_selected";
3068         ot->description= "Find specified text and set as selected";
3069         
3070         /* api callbacks */
3071         ot->exec= text_find_set_selected_exec;
3072         ot->poll= text_space_edit_poll;
3073 }
3074
3075 /******************* replace set selected *********************/
3076
3077 static int text_replace_set_selected_exec(bContext *C, wmOperator *UNUSED(op))
3078 {
3079         SpaceText *st= CTX_wm_space_text(C);
3080         Text *text= CTX_data_edit_text(C);
3081         char *tmp;
3082
3083         tmp= txt_sel_to_buf(text);
3084         BLI_strncpy(st->replacestr, tmp, ST_MAX_FIND_STR);
3085         MEM_freeN(tmp);
3086
3087         return OPERATOR_FINISHED;
3088 }
3089
3090 void TEXT_OT_replace_set_selected(wmOperatorType *ot)
3091 {
3092         /* identifiers */
3093         ot->name= "Replace Set Selected";
3094         ot->idname= "TEXT_OT_replace_set_selected";
3095         ot->description= "Replace text with specified text and set as selected";
3096         
3097         /* api callbacks */
3098         ot->exec= text_replace_set_selected_exec;
3099         ot->poll= text_space_edit_poll;
3100 }
3101
3102 /****************** resolve conflict operator ******************/
3103
3104 enum { RESOLVE_IGNORE, RESOLVE_RELOAD, RESOLVE_SAVE, RESOLVE_MAKE_INTERNAL };
3105 static EnumPropertyItem resolution_items[]= {
3106         {RESOLVE_IGNORE, "IGNORE", 0, "Ignore", ""},
3107         {RESOLVE_RELOAD, "RELOAD", 0, "Reload", ""},
3108         {RESOLVE_SAVE, "SAVE", 0, "Save", ""},
3109         {RESOLVE_MAKE_INTERNAL, "MAKE_INTERNAL", 0, "Make Internal", ""},
3110         {0, NULL, 0, NULL, NULL}};
3111
3112 /* returns 0 if file on disk is the same or Text is in memory only
3113  * returns 1 if file has been modified on disk since last local edit
3114  * returns 2 if file on disk has been deleted
3115  * -1 is returned if an error occurs */
3116
3117 int text_file_modified(Text *text)
3118 {
3119         struct stat st;
3120         int result;
3121         char file[FILE_MAX];
3122
3123         if(!text || !text->name)
3124                 return 0;
3125
3126         BLI_strncpy(file, text->name, FILE_MAX);
3127         BLI_path_abs(file, G.main->name);
3128
3129         if(!BLI_exists(file))
3130                 return 2;
3131
3132         result = stat(file, &st);
3133         
3134         if(result == -1)
3135                 return -1;
3136
3137         if((st.st_mode & S_IFMT) != S_IFREG)
3138                 return -1;
3139
3140         if(st.st_mtime > text->mtime)
3141                 return 1;
3142
3143         return 0;
3144 }
3145
3146 static void text_ignore_modified(Text *text)
3147 {
3148         struct stat st;
3149         int result;
3150         char file[FILE_MAX];
3151
3152         if(!text || !text->name) return;
3153
3154         BLI_strncpy(file, text->name, FILE_MAX);
3155         BLI_path_abs(file, G.main->name);
3156
3157         if(!BLI_exists(file)) return;
3158
3159         result = stat(file, &st);
3160         
3161         if(result == -1 || (st.st_mode & S_IFMT) != S_IFREG)
3162                 return;
3163
3164         text->mtime= st.st_mtime;
3165 }
3166
3167 static int text_resolve_conflict_exec(bContext *C, wmOperator *op)
3168 {
3169         Text *text= CTX_data_edit_text(C);
3170         int resolution= RNA_enum_get(op->ptr, "resolution");
3171
3172         switch(resolution) {
3173                 case RESOLVE_RELOAD:
3174                         return text_reload_exec(C, op);
3175                 case RESOLVE_SAVE:
3176                         return text_save_exec(C, op);
3177                 case RESOLVE_MAKE_INTERNAL:
3178                         return text_make_internal_exec(C, op);
3179                 case RESOLVE_IGNORE:
3180                         text_ignore_modified(text);
3181                         return OPERATOR_FINISHED;
3182         }
3183
3184         return OPERATOR_CANCELLED;
3185 }
3186
3187 static int text_resolve_conflict_invoke(bContext *C, wmOperator *op, wmEvent *UNUSED(event))
3188 {
3189         Text *text= CTX_data_edit_text(C);
3190         uiPopupMenu *pup;
3191         uiLayout *layout;
3192
3193         switch(text_file_modified(text)) {
3194                 case 1:
3195                         if(text->flags & TXT_ISDIRTY) {
3196                                 /* modified locally and externally, ahhh. offer more possibilites. */
3197                                 pup= uiPupMenuBegin(C, "File Modified Outside and Inside Blender", ICON_NONE);
3198                                 layout= uiPupMenuLayout(pup);
3199                                 uiItemEnumO_ptr(layout, op->type, "Reload from disk (ignore local changes)", 0, "resolution", RESOLVE_RELOAD);
3200                                 uiItemEnumO_ptr(layout, op->type, "Save to disk (ignore outside changes)", 0, "resolution", RESOLVE_SAVE);
3201                                 uiItemEnumO_ptr(layout, op->type, "Make text internal (separate copy)", 0, "resolution", RESOLVE_MAKE_INTERNAL);
3202                                 uiPupMenuEnd(C, pup);
3203                         }
3204                         else {
3205                                 pup= uiPupMenuBegin(C, "File Modified Outside Blender", ICON_NONE);
3206                                 layout= uiPupMenuLayout(pup);
3207                                 uiItemEnumO_ptr(layout, op->type, "Reload from disk", 0, "resolution", RESOLVE_RELOAD);
3208                                 uiItemEnumO_ptr(layout, op->type, "Make text internal (separate copy)", 0, "resolution", RESOLVE_MAKE_INTERNAL);
3209                                 uiItemEnumO_ptr(layout, op->type, "Ignore", 0, "resolution", RESOLVE_IGNORE);
3210                                 uiPupMenuEnd(C, pup);
3211                         }
3212                         break;
3213                 case 2:
3214                         pup= uiPupMenuBegin(C, "File Deleted Outside Blender", ICON_NONE);
3215                         layout= uiPupMenuLayout(pup);
3216                         uiItemEnumO_ptr(layout, op->type, "Make text internal", 0, "resolution", RESOLVE_MAKE_INTERNAL);
3217                         uiItemEnumO_ptr(layout, op->type, "Recreate file", 0, "resolution", RESOLVE_SAVE);
3218                         uiPupMenuEnd(C, pup);
3219                         break;
3220         }
3221
3222         return OPERATOR_CANCELLED;
3223 }
3224
3225 void TEXT_OT_resolve_conflict(wmOperatorType *ot)
3226 {
3227         /* identifiers */
3228         ot->name= "Resolve Conflict";
3229         ot->idname= "TEXT_OT_resolve_conflict";
3230         ot->description= "When external text is out of sync, resolve the conflict";
3231
3232         /* api callbacks */
3233         ot->exec= text_resolve_conflict_exec;
3234         ot->invoke= text_resolve_conflict_invoke;
3235         ot->poll= text_save_poll;
3236
3237         /* properties */
3238         RNA_def_enum(ot->srna, "resolution", resolution_items, RESOLVE_IGNORE, "Resolution", "How to solve conflict due to differences in internal and external text");
3239 }
3240
3241 /********************** to 3d object operator *****************/
3242
3243 static int text_to_3d_object_exec(bContext *C, wmOperator *op)
3244 {
3245         Text *text= CTX_data_edit_text(C);
3246         int split_lines= RNA_boolean_get(op->ptr, "split_lines");
3247
3248         ED_text_to_object(C, text, split_lines);
3249
3250         return OPERATOR_FINISHED;
3251 }
3252
3253 void TEXT_OT_to_3d_object(wmOperatorType *ot)
3254 {
3255         /* identifiers */
3256         ot->name= "To 3D Object";
3257         ot->idname= "TEXT_OT_to_3d_object";
3258         ot->description= "Create 3d text object from active text data block";
3259         
3260         /* api callbacks */
3261         ot->exec= text_to_3d_object_exec;
3262         ot->poll= text_edit_poll;
3263         
3264         /* flags */
3265         ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO;
3266
3267         /* properties */
3268         RNA_def_boolean(ot->srna, "split_lines", 0, "Split Lines", "Create one object per line in the text");
3269 }
3270
3271
3272 /************************ undo ******************************/
3273
3274 void ED_text_undo_step(bContext *C, int step)
3275 {
3276         Text *text= CTX_data_edit_text(C);
3277
3278         if(!text)
3279                 return;
3280
3281         if(step==1)
3282                 txt_do_undo(text);
3283         else if(step==-1)
3284                 txt_do_redo(text);
3285
3286         text_update_edited(text);
3287
3288         text_update_cursor_moved(C);
3289         text_drawcache_tag_update(CTX_wm_space_text(C), 1);
3290         WM_event_add_notifier(C, NC_TEXT|NA_EDITED, text);
3291 }
3292