svn merge ^/trunk/blender -r43420:43436
[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);  //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);  //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 begining
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;                    /* 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(sel) { text->sell = linep; text->selc = charp; } 
2518         else { text->curl = linep; text->curc = charp; }
2519 }
2520
2521 static void text_cursor_set_to_pos(SpaceText *st, ARegion *ar, int x, int y, int sel)
2522 {
2523         Text *text= st->text;
2524         text_update_character_width(st);
2525         y= (ar->winy - 2 - y)/st->lheight;
2526
2527         if(st->showlinenrs) x-= TXT_OFFSET+TEXTXLOC;
2528         else x-= TXT_OFFSET;
2529
2530         if(x<0) x= 0;
2531         x = (x/st->cwidth) + st->left;
2532         
2533         if(st->wordwrap) {
2534                 text_cursor_set_to_pos_wrapped(st, ar, x, y, sel);
2535         }
2536         else {
2537                 TextLine **linep;
2538                 int *charp;
2539                 int w;
2540                 
2541                 if(sel) { linep= &text->sell; charp= &text->selc; } 
2542                 else { linep= &text->curl; charp= &text->curc; }
2543                 
2544                 y-= txt_get_span(text->lines.first, *linep) - st->top;
2545                 
2546                 if(y>0) {
2547                         while(y-- != 0) if((*linep)->next) *linep= (*linep)->next;
2548                 }
2549                 else if(y<0) {
2550                         while(y++ != 0) if((*linep)->prev) *linep= (*linep)->prev;
2551                 }
2552
2553                 
2554                 w= flatten_len(st, (*linep)->line);
2555                 if(x<w) *charp= flatten_index_to_offset(st, (*linep)->line, x);
2556                 else *charp= (*linep)->len;
2557         }
2558         if(!sel) txt_pop_sel(text);
2559 }
2560
2561 static void text_cursor_set_apply(bContext *C, wmOperator *op, wmEvent *event)
2562 {
2563         SpaceText *st= CTX_wm_space_text(C);
2564         ARegion *ar= CTX_wm_region(C);
2565         SetSelection *ssel= op->customdata;
2566
2567         if(event->mval[1]<0 || event->mval[1]>ar->winy) {
2568                 int d= (ssel->old[1]-event->mval[1])*st->pix_per_line;
2569                 if(d) txt_screen_skip(st, ar, d);
2570
2571                 text_cursor_set_to_pos(st, ar, event->mval[0], event->mval[1]<0?0:ar->winy, 1);
2572
2573                 text_update_cursor_moved(C);
2574                 WM_event_add_notifier(C, NC_TEXT|ND_CURSOR, st->text);
2575         } 
2576         else if(!st->wordwrap && (event->mval[0]<0 || event->mval[0]>ar->winx)) {
2577                 if(event->mval[0]>ar->winx) st->left++;
2578                 else if(event->mval[0]<0 && st->left>0) st->left--;
2579                 
2580                 text_cursor_set_to_pos(st, ar, event->mval[0], event->mval[1], 1);
2581                 
2582                 text_update_cursor_moved(C);
2583                 WM_event_add_notifier(C, NC_TEXT|ND_CURSOR, st->text);
2584                 // XXX PIL_sleep_ms(10);
2585         } 
2586         else {
2587                 text_cursor_set_to_pos(st, ar, event->mval[0], event->mval[1], 1);
2588
2589                 text_update_cursor_moved(C);
2590                 WM_event_add_notifier(C, NC_TEXT|ND_CURSOR, st->text);
2591
2592                 ssel->old[0]= event->mval[0];
2593                 ssel->old[1]= event->mval[1];
2594         } 
2595 }
2596
2597 static void text_cursor_set_exit(bContext *C, wmOperator *op)
2598 {
2599         SpaceText *st= CTX_wm_space_text(C);
2600         Text *text= st->text;
2601         SetSelection *ssel= op->customdata;
2602         int linep2, charp2;
2603         char *buffer;
2604
2605         if(txt_has_sel(text)) {
2606                 buffer = txt_sel_to_buf(text);
2607                 WM_clipboard_text_set(buffer, 1);
2608                 MEM_freeN(buffer);
2609         }
2610
2611         linep2= txt_get_span(st->text->lines.first, st->text->sell);
2612         charp2= st->text->selc;
2613                 
2614         if(ssel->sell!=linep2 || ssel->selc!=charp2)
2615                 txt_undo_add_toop(st->text, UNDO_STO, ssel->sell, ssel->selc, linep2, charp2);
2616
2617         text_update_cursor_moved(C);
2618         WM_event_add_notifier(C, NC_TEXT|ND_CURSOR, st->text);
2619
2620         MEM_freeN(ssel);
2621 }
2622
2623 static int text_set_selection_invoke(bContext *C, wmOperator *op, wmEvent *event)
2624 {
2625         SpaceText *st= CTX_wm_space_text(C);
2626         SetSelection *ssel;
2627
2628         if(event->mval[0]>=st->txtbar.xmin)
2629                 return OPERATOR_PASS_THROUGH;
2630
2631         op->customdata= MEM_callocN(sizeof(SetSelection), "SetCursor");
2632         ssel= op->customdata;
2633         ssel->selecting= RNA_boolean_get(op->ptr, "select");
2634
2635         ssel->old[0]= event->mval[0];
2636         ssel->old[1]= event->mval[1];
2637
2638         ssel->sell= txt_get_span(st->text->lines.first, st->text->sell);
2639         ssel->selc= st->text->selc;
2640
2641         WM_event_add_modal_handler(C, op);
2642
2643         text_cursor_set_apply(C, op, event);
2644
2645         return OPERATOR_RUNNING_MODAL;
2646 }
2647
2648 static int text_set_selection_modal(bContext *C, wmOperator *op, wmEvent *event)
2649 {
2650         switch(event->type) {
2651                 case LEFTMOUSE:
2652                 case MIDDLEMOUSE:
2653                 case RIGHTMOUSE:
2654                         text_cursor_set_exit(C, op);
2655                         return OPERATOR_FINISHED;
2656                 case MOUSEMOVE:
2657                         text_cursor_set_apply(C, op, event);
2658                         break;
2659         }
2660
2661         return OPERATOR_RUNNING_MODAL;
2662 }
2663
2664 static int text_set_selection_cancel(bContext *C, wmOperator *op)
2665 {
2666         text_cursor_set_exit(C, op);
2667         return OPERATOR_FINISHED;
2668 }
2669
2670 void TEXT_OT_selection_set(wmOperatorType *ot)
2671 {
2672         /* identifiers */
2673         ot->name= "Set Selection";
2674         ot->idname= "TEXT_OT_selection_set";
2675         ot->description= "Set cursor selection";
2676
2677         /* api callbacks */
2678         ot->invoke= text_set_selection_invoke;
2679         ot->modal= text_set_selection_modal;
2680         ot->cancel= text_set_selection_cancel;
2681         ot->poll= text_region_edit_poll;
2682
2683         /* properties */
2684         RNA_def_boolean(ot->srna, "select", 0, "Select", "Set selection end rather than cursor");
2685 }
2686
2687 /******************* set cursor operator **********************/
2688
2689 static int text_cursor_set_exec(bContext *C, wmOperator *op)
2690 {
2691         SpaceText *st= CTX_wm_space_text(C);
2692         Text *text= st->text;
2693         ARegion *ar= CTX_wm_region(C);
2694         int x= RNA_int_get(op->ptr, "x");
2695         int y= RNA_int_get(op->ptr, "y");
2696         int oldl, oldc;
2697
2698         oldl= txt_get_span(text->lines.first, text->curl);
2699         oldc= text->curc;
2700
2701         text_cursor_set_to_pos(st, ar, x, y, 0);
2702
2703         txt_undo_add_toop(text, UNDO_CTO, oldl, oldc, txt_get_span(text->lines.first, text->curl), text->curc);
2704
2705         text_update_cursor_moved(C);
2706         WM_event_add_notifier(C, NC_TEXT|ND_CURSOR, st->text);
2707
2708         return OPERATOR_PASS_THROUGH;
2709 }
2710
2711 static int text_cursor_set_invoke(bContext *C, wmOperator *op, wmEvent *event)
2712 {
2713         SpaceText *st= CTX_wm_space_text(C);
2714
2715         if(event->mval[0]>=st->txtbar.xmin)
2716                 return OPERATOR_PASS_THROUGH;
2717
2718         RNA_int_set(op->ptr, "x", event->mval[0]);
2719         RNA_int_set(op->ptr, "y", event->mval[1]);
2720
2721         return text_cursor_set_exec(C, op);
2722 }
2723
2724 void TEXT_OT_cursor_set(wmOperatorType *ot)
2725 {
2726         /* identifiers */
2727         ot->name= "Set Cursor";
2728         ot->idname= "TEXT_OT_cursor_set";
2729         ot->description= "Set cursor position";
2730
2731         /* api callbacks */
2732         ot->invoke= text_cursor_set_invoke;
2733         ot->exec= text_cursor_set_exec;
2734         ot->poll= text_region_edit_poll;
2735
2736         /* properties */
2737         RNA_def_int(ot->srna, "x", 0, INT_MIN, INT_MAX, "X", "", INT_MIN, INT_MAX);
2738         RNA_def_int(ot->srna, "y", 0, INT_MIN, INT_MAX, "Y", "", INT_MIN, INT_MAX);
2739 }
2740
2741 /******************* line number operator **********************/
2742
2743 static int text_line_number_invoke(bContext *C, wmOperator *UNUSED(op), wmEvent *event)
2744 {
2745         SpaceText *st= CTX_wm_space_text(C);
2746         Text *text= CTX_data_edit_text(C);
2747         ARegion *ar= CTX_wm_region(C);
2748         const int *mval= event->mval;
2749         double time;
2750         static int jump_to= 0;
2751         static double last_jump= 0;
2752
2753         text_update_character_width(st);
2754
2755         if(!st->showlinenrs)
2756                 return OPERATOR_PASS_THROUGH;
2757
2758         if(!(mval[0]>2 && mval[0]<(TXT_OFFSET + TEXTXLOC) && mval[1]>2 && mval[1]<ar->winy-2))
2759                 return OPERATOR_PASS_THROUGH;
2760
2761         if(!(event->ascii>='0' && event->ascii<='9'))
2762                 return OPERATOR_PASS_THROUGH;
2763
2764         time = PIL_check_seconds_timer();
2765         if(last_jump < time-1)
2766                 jump_to= 0;
2767
2768         jump_to *= 10;
2769         jump_to += (int)(event->ascii-'0');
2770
2771         txt_move_toline(text, jump_to-1, 0);
2772         last_jump= time;
2773
2774         text_update_cursor_moved(C);
2775         WM_event_add_notifier(C, NC_TEXT|ND_CURSOR, text);
2776
2777         return OPERATOR_FINISHED;
2778 }
2779
2780 void TEXT_OT_line_number(wmOperatorType *ot)
2781 {
2782         /* identifiers */
2783         ot->name= "Line Number";
2784         ot->idname= "TEXT_OT_line_number";
2785         ot->description= "The current line number";
2786         
2787         /* api callbacks */
2788         ot->invoke= text_line_number_invoke;
2789         ot->poll= text_region_edit_poll;
2790 }
2791
2792 /******************* insert operator **********************/
2793
2794 static int text_insert_exec(bContext *C, wmOperator *op)
2795 {
2796         SpaceText *st= CTX_wm_space_text(C);
2797         Text *text= CTX_data_edit_text(C);
2798         char *str;
2799         int done = 0;
2800         size_t i = 0;
2801         unsigned int code;
2802
2803         text_drawcache_tag_update(st, 0);
2804
2805         str= RNA_string_get_alloc(op->ptr, "text", NULL, 0);
2806
2807         if(st && st->overwrite) {
2808                 while (str[i]) {
2809                         code = BLI_str_utf8_as_unicode_step(str, &i);
2810                         done |= txt_replace_char(text, code);
2811                 }
2812         } else {
2813                 while (str[i]) {
2814                         code = BLI_str_utf8_as_unicode_step(str, &i);
2815                         done |= txt_add_char(text, code);
2816                 }
2817         }
2818
2819         MEM_freeN(str);
2820         
2821         if(!done)
2822                 return OPERATOR_CANCELLED;
2823
2824         text_update_line_edited(text->curl);
2825
2826         text_update_cursor_moved(C);
2827         WM_event_add_notifier(C, NC_TEXT|NA_EDITED, text);
2828
2829         return OPERATOR_FINISHED;
2830 }
2831
2832 static int text_insert_invoke(bContext *C, wmOperator *op, wmEvent *event)
2833 {
2834         int ret;
2835
2836         // if(!RNA_struct_property_is_set(op->ptr, "text")) { /* always set from keymap XXX */
2837         if(!RNA_string_length(op->ptr, "text")) {
2838                 /* if alt/ctrl/super are pressed pass through */
2839                 if(event->ctrl || event->oskey) {
2840                         return OPERATOR_PASS_THROUGH;
2841                 }
2842                 else {
2843                         char str[BLI_UTF8_MAX+1];
2844                         size_t len;
2845                         
2846                         if (event->utf8_buf[0]) {
2847                                 len = BLI_str_utf8_size(event->utf8_buf);
2848                                 memcpy(str, event->utf8_buf, len);
2849                         } else {
2850                                 /* in theory, ghost can set value to extended ascii here */
2851                                 len = BLI_str_utf8_from_unicode(event->ascii, str);
2852                         }
2853                         str[len]= '\0';
2854                         RNA_string_set(op->ptr, "text", str);
2855                 }
2856         }
2857
2858         ret = text_insert_exec(C, op);
2859         
2860         /* run the script while editing, evil but useful */
2861         if(ret==OPERATOR_FINISHED && CTX_wm_space_text(C)->live_edit)
2862                 text_run_script(C, NULL);
2863
2864         return ret;
2865 }
2866
2867 void TEXT_OT_insert(wmOperatorType *ot)
2868 {
2869         /* identifiers */
2870         ot->name= "Insert";
2871         ot->idname= "TEXT_OT_insert";
2872         ot->description= "Insert text at cursor position";
2873         
2874         /* api callbacks */
2875         ot->exec= text_insert_exec;
2876         ot->invoke= text_insert_invoke;
2877         ot->poll= text_edit_poll;
2878
2879         /* properties */
2880         RNA_def_string(ot->srna, "text", "", 0, "Text", "Text to insert at the cursor position");
2881 }
2882
2883 /******************* find operator *********************/
2884
2885 /* mode */
2886 #define TEXT_FIND               0
2887 #define TEXT_REPLACE    1
2888 #define TEXT_MARK_ALL   2
2889
2890 static int text_find_and_replace(bContext *C, wmOperator *op, short mode)
2891 {
2892         Main *bmain= CTX_data_main(C);
2893         SpaceText *st= CTX_wm_space_text(C);
2894         Text *start= NULL, *text= st->text;
2895         int flags, first= 1;
2896         int found = 0;
2897         char *tmp;
2898
2899         if(!st->findstr[0] || (mode == TEXT_REPLACE && !st->replacestr[0]))
2900                 return OPERATOR_CANCELLED;
2901
2902         flags= st->flags;
2903         if(flags & ST_FIND_ALL)
2904                 flags ^= ST_FIND_WRAP;
2905
2906         do {
2907                 int proceed= 0;
2908
2909                 if(first) {
2910                         if(text->markers.first)
2911                                 WM_event_add_notifier(C, NC_TEXT|NA_EDITED, text);
2912
2913                         txt_clear_markers(text, TMARK_GRP_FINDALL, 0);
2914                 }
2915
2916                 first= 0;
2917                 
2918                 /* Replace current */
2919                 if(mode!=TEXT_FIND && txt_has_sel(text)) {
2920                         tmp= txt_sel_to_buf(text);
2921
2922                         if(flags & ST_MATCH_CASE) proceed= strcmp(st->findstr, tmp)==0;
2923                         else proceed= BLI_strcasecmp(st->findstr, tmp)==0;
2924
2925                         if(proceed) {
2926                                 if(mode==TEXT_REPLACE) {
2927                                         txt_insert_buf(text, st->replacestr);
2928                                         if(text->curl && text->curl->format) {
2929                                                 MEM_freeN(text->curl->format);
2930                                                 text->curl->format= NULL;
2931                                         }
2932                                         text_update_cursor_moved(C);
2933                                         WM_event_add_notifier(C, NC_TEXT|NA_EDITED, text);
2934                                         text_drawcache_tag_update(CTX_wm_space_text(C), 1);
2935                                 }
2936                                 else if(mode==TEXT_MARK_ALL) {
2937                                         unsigned char color[4];
2938                                         UI_GetThemeColor4ubv(TH_SHADE2, color);
2939
2940                                         if(txt_find_marker(text, text->curl, text->selc, TMARK_GRP_FINDALL, 0)) {
2941                                                 if(tmp) MEM_freeN(tmp), tmp=NULL;
2942                                                 break;
2943                                         }
2944
2945                                         txt_add_marker(text, text->curl, text->curc, text->selc, color, TMARK_GRP_FINDALL, TMARK_EDITALL);
2946                                         text_update_cursor_moved(C);
2947                                         WM_event_add_notifier(C, NC_TEXT|NA_EDITED, text);
2948                                 }
2949                         }
2950                         MEM_freeN(tmp);
2951                         tmp= NULL;
2952                 }
2953
2954                 /* Find next */
2955                 if(txt_find_string(text, st->findstr, flags & ST_FIND_WRAP, flags & ST_MATCH_CASE)) {
2956                         text_update_cursor_moved(C);
2957                         WM_event_add_notifier(C, NC_TEXT|ND_CURSOR, text);
2958                 }
2959                 else if(flags & ST_FIND_ALL) {
2960                         if(text==start) break;
2961                         if(!start) start= text;
2962                         if(text->id.next)
2963                                 text= st->text= text->id.next;
2964                         else
2965                                 text= st->text= bmain->text.first;
2966                         txt_move_toline(text, 0, 0);
2967                         text_update_cursor_moved(C);
2968                         WM_event_add_notifier(C, NC_TEXT|ND_CURSOR, text);
2969                         first= 1;
2970                 }
2971                 else {
2972                         if(!found && !proceed) BKE_reportf(op->reports, RPT_ERROR, "Text not found: %s", st->findstr);
2973                         break;
2974                 }
2975                 found = 1;
2976         } while(mode==TEXT_MARK_ALL);
2977
2978         return OPERATOR_FINISHED;
2979 }
2980
2981 static int text_find_exec(bContext *C, wmOperator *op)
2982 {
2983         return text_find_and_replace(C, op, TEXT_FIND);
2984 }
2985
2986 void TEXT_OT_find(wmOperatorType *ot)
2987 {
2988         /* identifiers */
2989         ot->name= "Find";
2990         ot->idname= "TEXT_OT_find";
2991         ot->description= "Find specified text";
2992         
2993         /* api callbacks */
2994         ot->exec= text_find_exec;
2995         ot->poll= text_space_edit_poll;
2996 }
2997
2998 /******************* replace operator *********************/
2999
3000 static int text_replace_exec(bContext *C, wmOperator *op)
3001 {
3002         return text_find_and_replace(C, op, TEXT_REPLACE);
3003 }
3004
3005 void TEXT_OT_replace(wmOperatorType *ot)
3006 {
3007         /* identifiers */
3008         ot->name= "Replace";
3009         ot->idname= "TEXT_OT_replace";
3010         ot->description= "Replace text with the specified text";
3011
3012         /* api callbacks */
3013         ot->exec= text_replace_exec;
3014         ot->poll= text_space_edit_poll;
3015 }
3016
3017 /******************* mark all operator *********************/
3018
3019 static int text_mark_all_exec(bContext *C, wmOperator *op)
3020 {
3021         return text_find_and_replace(C, op, TEXT_MARK_ALL);
3022 }
3023
3024 void TEXT_OT_mark_all(wmOperatorType *ot)
3025 {
3026         /* identifiers */
3027         ot->name= "Mark All";
3028         ot->idname= "TEXT_OT_mark_all";
3029         ot->description= "Mark all specified text";
3030         
3031         /* api callbacks */
3032         ot->exec= text_mark_all_exec;
3033         ot->poll= text_space_edit_poll;
3034 }
3035
3036 /******************* find set selected *********************/
3037
3038 static int text_find_set_selected_exec(bContext *C, wmOperator *op)
3039 {
3040         SpaceText *st= CTX_wm_space_text(C);
3041         Text *text= CTX_data_edit_text(C);
3042         char *tmp;
3043
3044         tmp= txt_sel_to_buf(text);
3045         BLI_strncpy(st->findstr, tmp, ST_MAX_FIND_STR);
3046         MEM_freeN(tmp);
3047
3048         if(!st->findstr[0])
3049                 return OPERATOR_FINISHED;
3050
3051         return text_find_and_replace(C, op, TEXT_FIND);
3052 }
3053
3054 void TEXT_OT_find_set_selected(wmOperatorType *ot)
3055 {
3056         /* identifiers */
3057         ot->name= "Find Set Selected";
3058         ot->idname= "TEXT_OT_find_set_selected";
3059         ot->description= "Find specified text and set as selected";
3060         
3061         /* api callbacks */
3062         ot->exec= text_find_set_selected_exec;
3063         ot->poll= text_space_edit_poll;
3064 }
3065
3066 /******************* replace set selected *********************/
3067
3068 static int text_replace_set_selected_exec(bContext *C, wmOperator *UNUSED(op))
3069 {
3070         SpaceText *st= CTX_wm_space_text(C);
3071         Text *text= CTX_data_edit_text(C);
3072         char *tmp;
3073
3074         tmp= txt_sel_to_buf(text);
3075         BLI_strncpy(st->replacestr, tmp, ST_MAX_FIND_STR);
3076         MEM_freeN(tmp);
3077
3078         return OPERATOR_FINISHED;
3079 }
3080
3081 void TEXT_OT_replace_set_selected(wmOperatorType *ot)
3082 {
3083         /* identifiers */
3084         ot->name= "Replace Set Selected";
3085         ot->idname= "TEXT_OT_replace_set_selected";
3086         ot->description= "Replace text with specified text and set as selected";
3087         
3088         /* api callbacks */
3089         ot->exec= text_replace_set_selected_exec;
3090         ot->poll= text_space_edit_poll;
3091 }
3092
3093 /****************** resolve conflict operator ******************/
3094
3095 enum { RESOLVE_IGNORE, RESOLVE_RELOAD, RESOLVE_SAVE, RESOLVE_MAKE_INTERNAL };
3096 static EnumPropertyItem resolution_items[]= {
3097         {RESOLVE_IGNORE, "IGNORE", 0, "Ignore", ""},
3098         {RESOLVE_RELOAD, "RELOAD", 0, "Reload", ""},
3099         {RESOLVE_SAVE, "SAVE", 0, "Save", ""},
3100         {RESOLVE_MAKE_INTERNAL, "MAKE_INTERNAL", 0, "Make Internal", ""},
3101         {0, NULL, 0, NULL, NULL}};
3102
3103 /* returns 0 if file on disk is the same or Text is in memory only
3104    returns 1 if file has been modified on disk since last local edit
3105    returns 2 if file on disk has been deleted
3106    -1 is returned if an error occurs */
3107
3108 int text_file_modified(Text *text)
3109 {
3110         struct stat st;
3111         int result;
3112         char file[FILE_MAX];
3113
3114         if(!text || !text->name)
3115                 return 0;
3116
3117         BLI_strncpy(file, text->name, FILE_MAX);
3118         BLI_path_abs(file, G.main->name);
3119
3120         if(!BLI_exists(file))
3121                 return 2;
3122
3123         result = stat(file, &st);
3124         
3125         if(result == -1)
3126                 return -1;
3127
3128         if((st.st_mode & S_IFMT) != S_IFREG)
3129                 return -1;
3130
3131         if(st.st_mtime > text->mtime)
3132                 return 1;
3133
3134         return 0;
3135 }
3136
3137 static void text_ignore_modified(Text *text)
3138 {
3139         struct stat st;
3140         int result;
3141         char file[FILE_MAX];
3142
3143         if(!text || !text->name) return;
3144
3145         BLI_strncpy(file, text->name, FILE_MAX);
3146         BLI_path_abs(file, G.main->name);
3147
3148         if(!BLI_exists(file)) return;
3149
3150         result = stat(file, &st);
3151         
3152         if(result == -1 || (st.st_mode & S_IFMT) != S_IFREG)
3153                 return;
3154
3155         text->mtime= st.st_mtime;
3156 }
3157
3158 static int text_resolve_conflict_exec(bContext *C, wmOperator *op)
3159 {
3160         Text *text= CTX_data_edit_text(C);
3161         int resolution= RNA_enum_get(op->ptr, "resolution");
3162
3163         switch(resolution) {
3164                 case RESOLVE_RELOAD:
3165                         return text_reload_exec(C, op);
3166                 case RESOLVE_SAVE:
3167                         return text_save_exec(C, op);
3168                 case RESOLVE_MAKE_INTERNAL:
3169                         return text_make_internal_exec(C, op);
3170                 case RESOLVE_IGNORE:
3171                         text_ignore_modified(text);
3172                         return OPERATOR_FINISHED;
3173         }
3174
3175         return OPERATOR_CANCELLED;
3176 }
3177
3178 static int text_resolve_conflict_invoke(bContext *C, wmOperator *op, wmEvent *UNUSED(event))
3179 {
3180         Text *text= CTX_data_edit_text(C);
3181         uiPopupMenu *pup;
3182         uiLayout *layout;
3183
3184         switch(text_file_modified(text)) {
3185                 case 1:
3186                         if(text->flags & TXT_ISDIRTY) {
3187                                 /* modified locally and externally, ahhh. offer more possibilites. */
3188                                 pup= uiPupMenuBegin(C, "File Modified Outside and Inside Blender", ICON_NONE);
3189                                 layout= uiPupMenuLayout(pup);
3190                                 uiItemEnumO(layout, op->type->idname, "Reload from disk (ignore local changes)", 0, "resolution", RESOLVE_RELOAD);
3191                                 uiItemEnumO(layout, op->type->idname, "Save to disk (ignore outside changes)", 0, "resolution", RESOLVE_SAVE);
3192                                 uiItemEnumO(layout, op->type->idname, "Make text internal (separate copy)", 0, "resolution", RESOLVE_MAKE_INTERNAL);
3193                                 uiPupMenuEnd(C, pup);
3194                         }
3195                         else {
3196                                 pup= uiPupMenuBegin(C, "File Modified Outside Blender", ICON_NONE);
3197                                 layout= uiPupMenuLayout(pup);
3198                                 uiItemEnumO(layout, op->type->idname, "Reload from disk", 0, "resolution", RESOLVE_RELOAD);
3199                                 uiItemEnumO(layout, op->type->idname, "Make text internal (separate copy)", 0, "resolution", RESOLVE_MAKE_INTERNAL);
3200                                 uiItemEnumO(layout, op->type->idname, "Ignore", 0, "resolution", RESOLVE_IGNORE);
3201                                 uiPupMenuEnd(C, pup);
3202                         }
3203                         break;
3204                 case 2:
3205                         pup= uiPupMenuBegin(C, "File Deleted Outside Blender", ICON_NONE);
3206                         layout= uiPupMenuLayout(pup);
3207                         uiItemEnumO(layout, op->type->idname, "Make text internal", 0, "resolution", RESOLVE_MAKE_INTERNAL);
3208                         uiItemEnumO(layout, op->type->idname, "Recreate file", 0, "resolution", RESOLVE_SAVE);
3209                         uiPupMenuEnd(C, pup);
3210                         break;
3211         }
3212
3213         return OPERATOR_CANCELLED;
3214 }
3215
3216 void TEXT_OT_resolve_conflict(wmOperatorType *ot)
3217 {
3218         /* identifiers */
3219         ot->name= "Resolve Conflict";
3220         ot->idname= "TEXT_OT_resolve_conflict";
3221         ot->description= "When external text is out of sync, resolve the conflict";
3222
3223         /* api callbacks */
3224         ot->exec= text_resolve_conflict_exec;
3225         ot->invoke= text_resolve_conflict_invoke;
3226         ot->poll= text_save_poll;
3227
3228         /* properties */
3229         RNA_def_enum(ot->srna, "resolution", resolution_items, RESOLVE_IGNORE, "Resolution", "How to solve conflict due to differences in internal and external text");
3230 }
3231
3232 /********************** to 3d object operator *****************/
3233
3234 static int text_to_3d_object_exec(bContext *C, wmOperator *op)
3235 {
3236         Text *text= CTX_data_edit_text(C);
3237         int split_lines= RNA_boolean_get(op->ptr, "split_lines");
3238
3239         ED_text_to_object(C, text, split_lines);
3240
3241         return OPERATOR_FINISHED;
3242 }
3243
3244 void TEXT_OT_to_3d_object(wmOperatorType *ot)
3245 {
3246         /* identifiers */
3247         ot->name= "To 3D Object";
3248         ot->idname= "TEXT_OT_to_3d_object";
3249         ot->description= "Create 3d text object from active text data block";
3250         
3251         /* api callbacks */
3252         ot->exec= text_to_3d_object_exec;
3253         ot->poll= text_edit_poll;
3254         
3255         /* flags */
3256         ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO;
3257
3258         /* properties */
3259         RNA_def_boolean(ot->srna, "split_lines", 0, "Split Lines", "Create one object per line in the text");
3260 }
3261
3262
3263 /************************ undo ******************************/
3264
3265 void ED_text_undo_step(bContext *C, int step)
3266 {
3267         Text *text= CTX_data_edit_text(C);
3268
3269         if(!text)
3270                 return;
3271
3272         if(step==1)
3273                 txt_do_undo(text);
3274         else if(step==-1)
3275                 txt_do_redo(text);
3276
3277         text_update_edited(text);
3278
3279         text_update_cursor_moved(C);
3280         text_drawcache_tag_update(CTX_wm_space_text(C), 1);
3281         WM_event_add_notifier(C, NC_TEXT|NA_EDITED, text);
3282 }
3283