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