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