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