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