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