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