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