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