code cleanup: use const events for modal and invoke operators.
[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 <string.h>
34 #include <errno.h>
35
36 #include "MEM_guardedalloc.h"
37
38 #include "DNA_text_types.h"
39
40 #include "BLI_blenlib.h"
41
42 #include "BLF_translation.h"
43
44 #include "PIL_time.h"
45
46 #include "BKE_context.h"
47 #include "BKE_global.h"
48 #include "BKE_library.h"
49 #include "BKE_main.h"
50 #include "BKE_report.h"
51 #include "BKE_text.h"
52
53 #include "WM_api.h"
54 #include "WM_types.h"
55
56 #include "ED_text.h"
57 #include "ED_curve.h"
58 #include "ED_screen.h"
59 #include "UI_interface.h"
60 #include "UI_resources.h"
61
62 #include "RNA_access.h"
63 #include "RNA_define.h"
64
65 #ifdef WITH_PYTHON
66 #include "BPY_extern.h"
67 #endif
68
69 #include "text_intern.h"
70 #include "text_format.h"
71
72 /************************ poll ***************************/
73
74
75 BLI_INLINE int text_pixel_x_to_column(SpaceText *st, const int x)
76 {
77         /* add half the char width so mouse cursor selection is inbetween letters */
78         return (x + (st->cwidth / 2)) / st->cwidth;
79 }
80
81 static int text_new_poll(bContext *UNUSED(C))
82 {
83         return 1;
84 }
85
86 static int text_edit_poll(bContext *C)
87 {
88         Text *text = CTX_data_edit_text(C);
89
90         if (!text)
91                 return 0;
92
93         if (text->id.lib) {
94                 // BKE_report(op->reports, RPT_ERROR, "Cannot edit external libdata");
95                 return 0;
96         }
97
98         return 1;
99 }
100
101 int text_space_edit_poll(bContext *C)
102 {
103         SpaceText *st = CTX_wm_space_text(C);
104         Text *text = CTX_data_edit_text(C);
105
106         if (!st || !text)
107                 return 0;
108
109         if (text->id.lib) {
110                 // BKE_report(op->reports, RPT_ERROR, "Cannot edit external libdata");
111                 return 0;
112         }
113
114         return 1;
115 }
116
117 static int text_region_edit_poll(bContext *C)
118 {
119         SpaceText *st = CTX_wm_space_text(C);
120         Text *text = CTX_data_edit_text(C);
121         ARegion *ar = CTX_wm_region(C);
122
123         if (!st || !text)
124                 return 0;
125         
126         if (!ar || ar->regiontype != RGN_TYPE_WINDOW)
127                 return 0;
128
129         if (text->id.lib) {
130                 // BKE_report(op->reports, RPT_ERROR, "Cannot edit external libdata");
131                 return 0;
132         }
133
134         return 1;
135 }
136
137 /********************** updates *********************/
138
139 void text_update_line_edited(TextLine *line)
140 {
141         if (!line)
142                 return;
143
144         /* we just free format here, and let it rebuild during draw */
145         if (line->format) {
146                 MEM_freeN(line->format);
147                 line->format = NULL;
148         }
149 }
150
151 void text_update_edited(Text *text)
152 {
153         TextLine *line;
154
155         for (line = text->lines.first; line; line = line->next)
156                 text_update_line_edited(line);
157 }
158
159 /******************* new operator *********************/
160
161 static int text_new_exec(bContext *C, wmOperator *UNUSED(op))
162 {
163         SpaceText *st = CTX_wm_space_text(C);
164         Main *bmain = CTX_data_main(C);
165         Text *text;
166         PointerRNA ptr, idptr;
167         PropertyRNA *prop;
168
169         text = BKE_text_add(bmain, "Text");
170
171         /* hook into UI */
172         uiIDContextProperty(C, &ptr, &prop);
173
174         if (prop) {
175                 /* when creating new ID blocks, use is already 1, but RNA
176                  * pointer se also increases user, so this compensates it */
177                 /* doesnt always seem to happen... (ton) */
178                 if (text->id.us > 1)
179                         text->id.us--;
180
181                 RNA_id_pointer_create(&text->id, &idptr);
182                 RNA_property_pointer_set(&ptr, prop, idptr);
183                 RNA_property_update(C, &ptr, prop);
184         }
185         else if (st) {
186                 st->text = text;
187                 st->top = 0;
188                 text_drawcache_tag_update(st, 1);
189         }
190
191         WM_event_add_notifier(C, NC_TEXT | NA_ADDED, text);
192
193         return OPERATOR_FINISHED;
194 }
195
196 void TEXT_OT_new(wmOperatorType *ot)
197 {
198         /* identifiers */
199         ot->name = "Create Text Block";
200         ot->idname = "TEXT_OT_new";
201         ot->description = "Create a new text data block";
202         
203         /* api callbacks */
204         ot->exec = text_new_exec;
205         ot->poll = text_new_poll;
206         
207         /* flags */
208         ot->flag = OPTYPE_UNDO;
209 }
210
211 /******************* open operator *********************/
212
213 static void text_open_init(bContext *C, wmOperator *op)
214 {
215         PropertyPointerRNA *pprop;
216
217         op->customdata = pprop = MEM_callocN(sizeof(PropertyPointerRNA), "OpenPropertyPointerRNA");
218         uiIDContextProperty(C, &pprop->ptr, &pprop->prop);
219 }
220
221 static int text_open_cancel(bContext *UNUSED(C), wmOperator *op)
222 {
223         MEM_freeN(op->customdata);
224         return OPERATOR_CANCELLED;
225 }
226
227 static int text_open_exec(bContext *C, wmOperator *op)
228 {
229         SpaceText *st = CTX_wm_space_text(C);
230         Main *bmain = CTX_data_main(C);
231         Text *text;
232         PropertyPointerRNA *pprop;
233         PointerRNA idptr;
234         char str[FILE_MAX];
235         short internal = RNA_boolean_get(op->ptr, "internal");
236
237         RNA_string_get(op->ptr, "filepath", str);
238
239         text = BKE_text_load(bmain, str, G.main->name);
240
241         if (!text) {
242                 if (op->customdata) MEM_freeN(op->customdata);
243                 return OPERATOR_CANCELLED;
244         }
245
246         if (!op->customdata)
247                 text_open_init(C, op);
248
249         /* hook into UI */
250         pprop = op->customdata;
251
252         if (pprop->prop) {
253                 /* when creating new ID blocks, use is already 1, but RNA
254                  * pointer se also increases user, so this compensates it */
255                 text->id.us--;
256
257                 RNA_id_pointer_create(&text->id, &idptr);
258                 RNA_property_pointer_set(&pprop->ptr, pprop->prop, idptr);
259                 RNA_property_update(C, &pprop->ptr, pprop->prop);
260         }
261         else if (st) {
262                 st->text = text;
263                 st->top = 0;
264         }
265         
266         if (internal) {
267                 if (text->name)
268                         MEM_freeN(text->name);
269                 
270                 text->name = NULL;
271         }
272
273         text_drawcache_tag_update(st, 1);
274         WM_event_add_notifier(C, NC_TEXT | NA_ADDED, text);
275
276         MEM_freeN(op->customdata);
277
278         return OPERATOR_FINISHED;
279 }
280
281 static int text_open_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
282 {
283         Text *text = CTX_data_edit_text(C);
284         char *path = (text && text->name) ? text->name : G.main->name;
285
286         if (RNA_struct_property_is_set(op->ptr, "filepath"))
287                 return text_open_exec(C, op);
288         
289         text_open_init(C, op);
290         RNA_string_set(op->ptr, "filepath", path);
291         WM_event_add_fileselect(C, op); 
292
293         return OPERATOR_RUNNING_MODAL;
294 }
295
296 void TEXT_OT_open(wmOperatorType *ot)
297 {
298         /* identifiers */
299         ot->name = "Open Text Block";
300         ot->idname = "TEXT_OT_open";
301         ot->description = "Open a new text data block";
302
303         /* api callbacks */
304         ot->exec = text_open_exec;
305         ot->invoke = text_open_invoke;
306         ot->cancel = text_open_cancel;
307         ot->poll = text_new_poll;
308
309         /* flags */
310         ot->flag = OPTYPE_UNDO;
311         
312         /* properties */
313         WM_operator_properties_filesel(ot, FOLDERFILE | TEXTFILE | PYSCRIPTFILE, FILE_SPECIAL, FILE_OPENFILE,
314                                        WM_FILESEL_FILEPATH, FILE_DEFAULTDISPLAY);  //XXX TODO, relative_path
315         RNA_def_boolean(ot->srna, "internal", 0, "Make internal", "Make text file internal after loading");
316 }
317
318 /******************* reload operator *********************/
319
320 static int text_reload_exec(bContext *C, wmOperator *op)
321 {
322         Text *text = CTX_data_edit_text(C);
323
324         if (!BKE_text_reload(text)) {
325                 BKE_report(op->reports, RPT_ERROR, "Could not reopen file");
326                 return OPERATOR_CANCELLED;
327         }
328
329 #ifdef WITH_PYTHON
330         if (text->compiled)
331                 BPY_text_free_code(text);
332 #endif
333
334         text_update_edited(text);
335         text_update_cursor_moved(C);
336         text_drawcache_tag_update(CTX_wm_space_text(C), 1);
337         WM_event_add_notifier(C, NC_TEXT | NA_EDITED, text);
338
339         return OPERATOR_FINISHED;
340 }
341
342 void TEXT_OT_reload(wmOperatorType *ot)
343 {
344         /* identifiers */
345         ot->name = "Reload";
346         ot->idname = "TEXT_OT_reload";
347         ot->description = "Reload active text data block from its file";
348         
349         /* api callbacks */
350         ot->exec = text_reload_exec;
351         ot->invoke = WM_operator_confirm;
352         ot->poll = text_edit_poll;
353 }
354
355 /******************* delete operator *********************/
356
357 static int text_unlink_poll(bContext *C)
358 {
359         /* it should be possible to unlink texts if they're lib-linked in... */
360         return CTX_data_edit_text(C) != NULL;
361 }
362
363 static int text_unlink_exec(bContext *C, wmOperator *UNUSED(op))
364 {
365         Main *bmain = CTX_data_main(C);
366         SpaceText *st = CTX_wm_space_text(C);
367         Text *text = CTX_data_edit_text(C);
368
369         /* make the previous text active, if its not there make the next text active */
370         if (st) {
371                 if (text->id.prev) {
372                         st->text = text->id.prev;
373                         text_update_cursor_moved(C);
374                         WM_event_add_notifier(C, NC_TEXT | ND_CURSOR, st->text);
375                 }
376                 else if (text->id.next) {
377                         st->text = text->id.next;
378                         text_update_cursor_moved(C);
379                         WM_event_add_notifier(C, NC_TEXT | ND_CURSOR, st->text);
380                 }
381         }
382
383         BKE_text_unlink(bmain, text);
384         BKE_libblock_free(&bmain->text, text);
385
386         text_drawcache_tag_update(st, 1);
387         WM_event_add_notifier(C, NC_TEXT | NA_REMOVED, NULL);
388
389         return OPERATOR_FINISHED;
390 }
391
392 void TEXT_OT_unlink(wmOperatorType *ot)
393 {
394         /* identifiers */
395         ot->name = "Unlink";
396         ot->idname = "TEXT_OT_unlink";
397         ot->description = "Unlink active text data block";
398         
399         /* api callbacks */
400         ot->exec = text_unlink_exec;
401         ot->invoke = WM_operator_confirm;
402         ot->poll = text_unlink_poll;
403         
404         /* flags */
405         ot->flag = OPTYPE_UNDO;
406 }
407
408 /******************* make internal operator *********************/
409
410 static int text_make_internal_exec(bContext *C, wmOperator *UNUSED(op))
411 {
412         Text *text = CTX_data_edit_text(C);
413
414         text->flags |= TXT_ISMEM | TXT_ISDIRTY;
415
416         if (text->name) {
417                 MEM_freeN(text->name);
418                 text->name = NULL;
419         }
420
421         text_update_cursor_moved(C);
422         WM_event_add_notifier(C, NC_TEXT | NA_EDITED, text);
423
424         return OPERATOR_FINISHED;
425 }
426
427 void TEXT_OT_make_internal(wmOperatorType *ot)
428 {
429         /* identifiers */
430         ot->name = "Make Internal";
431         ot->idname = "TEXT_OT_make_internal";
432         ot->description = "Make active text file internal";
433
434         /* api callbacks */
435         ot->exec = text_make_internal_exec;
436         ot->poll = text_edit_poll;
437         
438         /* flags */
439         ot->flag = OPTYPE_UNDO;
440 }
441
442 /******************* save operator *********************/
443
444 static int text_save_poll(bContext *C)
445 {
446         Text *text = CTX_data_edit_text(C);
447
448         if (!text_edit_poll(C))
449                 return 0;
450         
451         return (text->name != NULL && !(text->flags & TXT_ISMEM));
452 }
453
454 static void txt_write_file(Text *text, ReportList *reports) 
455 {
456         FILE *fp;
457         TextLine *tmp;
458         struct stat st;
459         char filepath[FILE_MAX];
460         
461         BLI_strncpy(filepath, text->name, FILE_MAX);
462         BLI_path_abs(filepath, G.main->name);
463         
464         fp = BLI_fopen(filepath, "w");
465         if (fp == NULL) {
466                 BKE_reportf(reports, RPT_ERROR, "Unable to save '%s': %s",
467                             filepath, errno ? strerror(errno) : TIP_("unknown error writing file"));
468                 return;
469         }
470
471         tmp = text->lines.first;
472         while (tmp) {
473                 if (tmp->next) fprintf(fp, "%s\n", tmp->line);
474                 else fprintf(fp, "%s", tmp->line);
475                 
476                 tmp = tmp->next;
477         }
478         
479         fclose(fp);
480
481         if (stat(filepath, &st) == 0) {
482                 text->mtime = st.st_mtime;
483         }
484         else {
485                 text->mtime = 0;
486                 BKE_reportf(reports, RPT_WARNING, "Unable to stat '%s': %s",
487                             filepath, errno ? strerror(errno) : TIP_("unknown error stating file"));
488         }
489         
490         if (text->flags & TXT_ISDIRTY)
491                 text->flags ^= TXT_ISDIRTY;
492 }
493
494 static int text_save_exec(bContext *C, wmOperator *op)
495 {
496         Text *text = CTX_data_edit_text(C);
497
498         txt_write_file(text, op->reports);
499
500         text_update_cursor_moved(C);
501         WM_event_add_notifier(C, NC_TEXT | NA_EDITED, text);
502
503         return OPERATOR_FINISHED;
504 }
505
506 void TEXT_OT_save(wmOperatorType *ot)
507 {
508         /* identifiers */
509         ot->name = "Save";
510         ot->idname = "TEXT_OT_save";
511         ot->description = "Save active text data block";
512
513         /* api callbacks */
514         ot->exec = text_save_exec;
515         ot->poll = text_save_poll;
516 }
517
518 /******************* save as operator *********************/
519
520 static int text_save_as_exec(bContext *C, wmOperator *op)
521 {
522         Text *text = CTX_data_edit_text(C);
523         char str[FILE_MAX];
524
525         if (!text)
526                 return OPERATOR_CANCELLED;
527
528         RNA_string_get(op->ptr, "filepath", str);
529
530         if (text->name) MEM_freeN(text->name);
531         text->name = BLI_strdup(str);
532         text->flags &= ~TXT_ISMEM;
533
534         txt_write_file(text, op->reports);
535
536         text_update_cursor_moved(C);
537         WM_event_add_notifier(C, NC_TEXT | NA_EDITED, text);
538
539         return OPERATOR_FINISHED;
540 }
541
542 static int text_save_as_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
543 {
544         Text *text = CTX_data_edit_text(C);
545         char *str;
546
547         if (RNA_struct_property_is_set(op->ptr, "filepath"))
548                 return text_save_as_exec(C, op);
549
550         if (text->name)
551                 str = text->name;
552         else if (text->flags & TXT_ISMEM)
553                 str = text->id.name + 2;
554         else
555                 str = G.main->name;
556         
557         RNA_string_set(op->ptr, "filepath", str);
558         WM_event_add_fileselect(C, op); 
559
560         return OPERATOR_RUNNING_MODAL;
561 }
562
563 void TEXT_OT_save_as(wmOperatorType *ot)
564 {
565         /* identifiers */
566         ot->name = "Save As";
567         ot->idname = "TEXT_OT_save_as";
568         ot->description = "Save active text file with options";
569         
570         /* api callbacks */
571         ot->exec = text_save_as_exec;
572         ot->invoke = text_save_as_invoke;
573         ot->poll = text_edit_poll;
574
575         /* properties */
576         WM_operator_properties_filesel(ot, FOLDERFILE | TEXTFILE | PYSCRIPTFILE, FILE_SPECIAL, FILE_SAVE,
577                                        WM_FILESEL_FILEPATH, FILE_DEFAULTDISPLAY);  //XXX TODO, relative_path
578 }
579
580 /******************* run script operator *********************/
581
582 static int text_run_script_poll(bContext *C)
583 {
584         return (CTX_data_edit_text(C) != NULL);
585 }
586
587 static int text_run_script(bContext *C, ReportList *reports)
588 {
589 #ifdef WITH_PYTHON
590         Text *text = CTX_data_edit_text(C);
591         const short is_live = (reports == NULL);
592
593         /* only for comparison */
594         void *curl_prev = text->curl;
595         int curc_prev = text->curc;
596
597         if (BPY_text_exec(C, text, reports, !is_live)) {
598                 if (is_live) {
599                         /* for nice live updates */
600                         WM_event_add_notifier(C, NC_WINDOW | NA_EDITED, NULL);
601                 }
602                 return OPERATOR_FINISHED;
603         }
604
605         /* Don't report error messages while live editing */
606         if (!is_live) {
607                 /* text may have freed its self */
608                 if (CTX_data_edit_text(C) == text) {
609                         if (text->curl != curl_prev || curc_prev != text->curc) {
610                                 text_update_cursor_moved(C);
611                                 WM_event_add_notifier(C, NC_TEXT | NA_EDITED, text);
612                         }
613                 }
614
615                 BKE_report(reports, RPT_ERROR, "Python script fail, look in the console for now...");
616
617                 return OPERATOR_FINISHED;
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 *new_line;
1133         int extra, number; //unknown for now
1134         int type = RNA_enum_get(op->ptr, "type");
1135
1136         /* first convert to all space, this make it a lot easier to convert to tabs
1137          * because there is no mixtures of ' ' && '\t' */
1138         for (tmp = text->lines.first; tmp; tmp = tmp->next) {
1139                 const char *text_check_line     = tmp->line;
1140                 const int   text_check_line_len = tmp->len;
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 < text_check_line_len; 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         }
1176         
1177         if (type == TO_TABS) { // Converting to tabs
1178                 //start over from the beginning
1179                 
1180                 for (tmp = text->lines.first; tmp; tmp = tmp->next) {
1181                         const char *text_check_line     = tmp->line;
1182                         const int   text_check_line_len = tmp->len;
1183                         extra = 0;
1184                         for (a = 0; a < text_check_line_len; a++) {
1185                                 number = 0;
1186                                 for (j = 0; j < (size_t)st->tabnumber; j++) {
1187                                         if ((a + j) <= text_check_line_len) { //check to make sure we are not pass the end of the line
1188                                                 if (text_check_line[a + j] != ' ') {
1189                                                         number = 1;
1190                                                 }
1191                                         }
1192                                 }
1193                                 if (!number) { //found all number of space to equal a tab
1194                                         a = a + (st->tabnumber - 1);
1195                                         extra = extra + 1;
1196                                 }
1197                         }
1198                         
1199                         if (extra > 0) {   //got tabs make malloc and do what you have to do
1200                                 new_line = MEM_callocN(text_check_line_len - (((st->tabnumber * extra) - extra) - 1), "Converted_Line");
1201                                 extra = 0; //reuse vars
1202                                 for (a = 0; a < text_check_line_len; a++) {
1203                                         number = 0;
1204                                         for (j = 0; j < (size_t)st->tabnumber; j++) {
1205                                                 if ((a + j) <= text_check_line_len) { //check to make sure we are not pass the end of the line
1206                                                         if (text_check_line[a + j] != ' ') {
1207                                                                 number = 1;
1208                                                         }
1209                                                 }
1210                                         }
1211
1212                                         if (!number) { //found all number of space to equal a tab
1213                                                 new_line[extra] = '\t';
1214                                                 a = a + (st->tabnumber - 1);
1215                                                 extra++;
1216                                                 
1217                                         }
1218                                         else { //not adding a tab
1219                                                 new_line[extra] = text_check_line[a];
1220                                                 extra++;
1221                                         }
1222                                 }
1223                                 new_line[extra] = '\0';
1224                                 // put new_line in the tmp->line spot still need to try and set the curc correctly
1225                                 if (tmp->line) MEM_freeN(tmp->line);
1226                                 if (tmp->format) MEM_freeN(tmp->format);
1227                                 
1228                                 tmp->line = new_line;
1229                                 tmp->len = strlen(new_line);
1230                                 tmp->format = NULL;
1231                         }
1232                 }
1233         }
1234
1235         text_update_edited(text);
1236         text_update_cursor_moved(C);
1237         text_drawcache_tag_update(st, 1);
1238         WM_event_add_notifier(C, NC_TEXT | NA_EDITED, text);
1239
1240         return OPERATOR_FINISHED;
1241 }
1242
1243 void TEXT_OT_convert_whitespace(wmOperatorType *ot)
1244 {
1245         /* identifiers */
1246         ot->name = "Convert Whitespace";
1247         ot->idname = "TEXT_OT_convert_whitespace";
1248         ot->description = "Convert whitespaces by type";
1249         
1250         /* api callbacks */
1251         ot->exec = text_convert_whitespace_exec;
1252         ot->poll = text_edit_poll;
1253
1254         /* properties */
1255         RNA_def_enum(ot->srna, "type", whitespace_type_items, TO_SPACES, "Type", "Type of whitespace to convert to");
1256 }
1257
1258 /******************* select all operator *********************/
1259
1260 static int text_select_all_exec(bContext *C, wmOperator *UNUSED(op))
1261 {
1262         Text *text = CTX_data_edit_text(C);
1263
1264         txt_sel_all(text);
1265
1266         text_update_cursor_moved(C);
1267         WM_event_add_notifier(C, NC_TEXT | NA_EDITED, text);
1268
1269         return OPERATOR_FINISHED;
1270 }
1271
1272 void TEXT_OT_select_all(wmOperatorType *ot)
1273 {
1274         /* identifiers */
1275         ot->name = "Select All";
1276         ot->idname = "TEXT_OT_select_all";
1277         ot->description = "Select all text";
1278         
1279         /* api callbacks */
1280         ot->exec = text_select_all_exec;
1281         ot->poll = text_edit_poll;
1282 }
1283
1284 /******************* select line operator *********************/
1285
1286 static int text_select_line_exec(bContext *C, wmOperator *UNUSED(op))
1287 {
1288         Text *text = CTX_data_edit_text(C);
1289
1290         txt_sel_line(text);
1291
1292         text_update_cursor_moved(C);
1293         WM_event_add_notifier(C, NC_TEXT | NA_EDITED, text);
1294
1295         return OPERATOR_FINISHED;
1296 }
1297
1298 void TEXT_OT_select_line(wmOperatorType *ot)
1299 {
1300         /* identifiers */
1301         ot->name = "Select Line";
1302         ot->idname = "TEXT_OT_select_line";
1303         ot->description = "Select text by line";
1304         
1305         /* api callbacks */
1306         ot->exec = text_select_line_exec;
1307         ot->poll = text_edit_poll;
1308 }
1309
1310 /******************* select word operator *********************/
1311
1312 static int text_select_word_exec(bContext *C, wmOperator *UNUSED(op))
1313 {
1314         Text *text = CTX_data_edit_text(C);
1315         /* don't advance cursor before stepping */
1316         const bool use_init_step = false;
1317
1318         txt_jump_left(text, false, use_init_step);
1319         txt_jump_right(text, true, use_init_step);
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 /************************ move operator ************************/
1380
1381 static EnumPropertyItem move_type_items[] = {
1382         {LINE_BEGIN, "LINE_BEGIN", 0, "Line Begin", ""},
1383         {LINE_END, "LINE_END", 0, "Line End", ""},
1384         {FILE_TOP, "FILE_TOP", 0, "File Top", ""},
1385         {FILE_BOTTOM, "FILE_BOTTOM", 0, "File Bottom", ""},
1386         {PREV_CHAR, "PREVIOUS_CHARACTER", 0, "Previous Character", ""},
1387         {NEXT_CHAR, "NEXT_CHARACTER", 0, "Next Character", ""},
1388         {PREV_WORD, "PREVIOUS_WORD", 0, "Previous Word", ""},
1389         {NEXT_WORD, "NEXT_WORD", 0, "Next Word", ""},
1390         {PREV_LINE, "PREVIOUS_LINE", 0, "Previous Line", ""},
1391         {NEXT_LINE, "NEXT_LINE", 0, "Next Line", ""},
1392         {PREV_PAGE, "PREVIOUS_PAGE", 0, "Previous Page", ""},
1393         {NEXT_PAGE, "NEXT_PAGE", 0, "Next Page", ""},
1394         {0, NULL, 0, NULL, NULL}};
1395
1396 /* get cursor position in line by relative wrapped line and column positions */
1397 static int text_get_cursor_rel(SpaceText *st, ARegion *ar, TextLine *linein, int rell, int relc)
1398 {
1399         int i, j, start, end, max, chop, curs, loop, endj, found, selc;
1400         char ch;
1401
1402         max = wrap_width(st, ar);
1403
1404         selc = start = endj = curs = found = 0;
1405         end = max;
1406         chop = loop = 1;
1407
1408         for (i = 0, j = 0; loop; j += BLI_str_utf8_size_safe(linein->line + j)) {
1409                 int chars;
1410                 int columns = BLI_str_utf8_char_width_safe(linein->line + j); /* = 1 for tab */
1411
1412                 /* Mimic replacement of tabs */
1413                 ch = linein->line[j];
1414                 if (ch == '\t') {
1415                         chars = st->tabnumber - i % st->tabnumber;
1416                         ch = ' ';
1417                 }
1418                 else {
1419                         chars = 1;
1420                 }
1421
1422                 while (chars--) {
1423                         if (rell == 0 && i - start <= relc && i + columns - start > relc) {
1424                                 /* current position could be wrapped to next line */
1425                                 /* this should be checked when end of current line would be reached */
1426                                 selc = j;
1427                                 found = 1;
1428                         }
1429                         else if (i - end <= relc && i + columns - end > relc) {
1430                                 curs = j;
1431                         }
1432                         if (i + columns - start > max) {
1433                                 end = MIN2(end, i);
1434
1435                                 if (found) {
1436                                         /* exact cursor position was found, check if it's */
1437                                         /* still on needed line (hasn't been wrapped) */
1438                                         if (selc > endj && !chop) selc = endj;
1439                                         loop = 0;
1440                                         break;
1441                                 }
1442
1443                                 if (chop) endj = j;
1444
1445                                 start = end;
1446                                 end += max;
1447                                 chop = 1;
1448                                 rell--;
1449
1450                                 if (rell == 0 && i + columns - start > relc) {
1451                                         selc = curs;
1452                                         loop = 0;
1453                                         break;
1454                                 }
1455                         }
1456                         else if (ch == '\0') {
1457                                 if (!found) selc = linein->len;
1458                                 loop = 0;
1459                                 break;
1460                         }
1461                         else if (ch == ' ' || ch == '-') {
1462                                 if (found) {
1463                                         loop = 0;
1464                                         break;
1465                                 }
1466
1467                                 if (rell == 0 && i + columns - start > relc) {
1468                                         selc = curs;
1469                                         loop = 0;
1470                                         break;
1471                                 }
1472                                 end = i + 1;
1473                                 endj = j;
1474                                 chop = 0;
1475                         }
1476                         i += columns;
1477                 }
1478         }
1479
1480         return selc;
1481 }
1482
1483 static int cursor_skip_find_line(SpaceText *st, ARegion *ar,
1484                                  int lines, TextLine **linep, int *charp, int *rell, int *relc)
1485 {
1486         int offl, offc, visible_lines;
1487
1488         wrap_offset_in_line(st, ar, *linep, *charp, &offl, &offc);
1489         *relc = text_get_char_pos(st, (*linep)->line, *charp) + offc;
1490         *rell = lines;
1491
1492         /* handle current line */
1493         if (lines > 0) {
1494                 visible_lines = text_get_visible_lines(st, ar, (*linep)->line);
1495
1496                 if (*rell - visible_lines + offl >= 0) {
1497                         if (!(*linep)->next) {
1498                                 if (offl < visible_lines - 1) {
1499                                         *rell = visible_lines - 1;
1500                                         return 1;
1501                                 }
1502
1503                                 *charp = (*linep)->len;
1504                                 return 0;
1505                         }
1506
1507                         *rell -= visible_lines - offl;
1508                         *linep = (*linep)->next;
1509                 }
1510                 else {
1511                         *rell += offl;
1512                         return 1;
1513                 }
1514         }
1515         else {
1516                 if (*rell + offl <= 0) {
1517                         if (!(*linep)->prev) {
1518                                 if (offl) {
1519                                         *rell = 0;
1520                                         return 1;
1521                                 }
1522
1523                                 *charp = 0;
1524                                 return 0;
1525                         }
1526
1527                         *rell += offl;
1528                         *linep = (*linep)->prev;
1529                 }
1530                 else {
1531                         *rell += offl;
1532                         return 1;
1533                 }
1534         }
1535
1536         /* skip lines and find destination line and offsets */
1537         while (*linep) {
1538                 visible_lines = text_get_visible_lines(st, ar, (*linep)->line);
1539
1540                 if (lines < 0) { /* moving top */
1541                         if (*rell + visible_lines >= 0) {
1542                                 *rell += visible_lines;
1543                                 break;
1544                         }
1545
1546                         if (!(*linep)->prev) {
1547                                 *rell = 0;
1548                                 break;
1549                         }
1550
1551                         *rell += visible_lines;
1552                         *linep = (*linep)->prev;
1553                 }
1554                 else { /* moving bottom */
1555                         if (*rell - visible_lines < 0) break;
1556
1557                         if (!(*linep)->next) {
1558                                 *rell = visible_lines - 1;
1559                                 break;
1560                         }
1561
1562                         *rell -= visible_lines;
1563                         *linep = (*linep)->next;
1564                 }
1565         }
1566
1567         return 1;
1568 }
1569
1570 static void txt_wrap_move_bol(SpaceText *st, ARegion *ar, short sel)
1571 {
1572         Text *text = st->text;
1573         TextLine **linep;
1574         int *charp;
1575         int oldc, i, j, max, start, end, endj, chop, loop;
1576         char ch;
1577
1578         text_update_character_width(st);
1579
1580         if (sel) { linep = &text->sell; charp = &text->selc; }
1581         else     { linep = &text->curl; charp = &text->curc; }
1582
1583         oldc = *charp;
1584
1585         max = wrap_width(st, ar);
1586
1587         start = endj = 0;
1588         end = max;
1589         chop = loop = 1;
1590         *charp = 0;
1591
1592         for (i = 0, j = 0; loop; j += BLI_str_utf8_size_safe((*linep)->line + j)) {
1593                 int chars;
1594                 int columns = BLI_str_utf8_char_width_safe((*linep)->line + j); /* = 1 for tab */
1595
1596                 /* Mimic replacement of tabs */
1597                 ch = (*linep)->line[j];
1598                 if (ch == '\t') {
1599                         chars = st->tabnumber - i % st->tabnumber;
1600                         ch = ' ';
1601                 }
1602                 else {
1603                         chars = 1;
1604                 }
1605
1606                 while (chars--) {
1607                         if (i + columns - start > max) {
1608                                 end = MIN2(end, i);
1609
1610                                 *charp = endj;
1611
1612                                 if (j >= oldc) {
1613                                         if (ch == '\0') *charp = txt_utf8_column_to_offset((*linep)->line, start);
1614                                         loop = 0;
1615                                         break;
1616                                 }
1617
1618                                 if (chop) endj = j;
1619
1620                                 start = end;
1621                                 end += max;
1622                                 chop = 1;
1623                         }
1624                         else if (ch == ' ' || ch == '-' || ch == '\0') {
1625                                 if (j >= oldc) {
1626                                         *charp = txt_utf8_column_to_offset((*linep)->line, start);
1627                                         loop = 0;
1628                                         break;
1629                                 }
1630
1631                                 end = i + 1;
1632                                 endj = j + 1;
1633                                 chop = 0;
1634                         }
1635                         i += columns;
1636                 }
1637         }
1638
1639         if (!sel) txt_pop_sel(text);
1640 }
1641
1642 static void txt_wrap_move_eol(SpaceText *st, ARegion *ar, short sel)
1643 {
1644         Text *text = st->text;
1645         TextLine **linep;
1646         int *charp;
1647         int oldc, i, j, max, start, end, endj, chop, loop;
1648         char ch;
1649
1650         text_update_character_width(st);
1651
1652         if (sel) { linep = &text->sell; charp = &text->selc; }
1653         else     { linep = &text->curl; charp = &text->curc; }
1654
1655         oldc = *charp;
1656
1657         max = wrap_width(st, ar);
1658
1659         start = endj = 0;
1660         end = max;
1661         chop = loop = 1;
1662         *charp = 0;
1663
1664         for (i = 0, j = 0; loop; j += BLI_str_utf8_size_safe((*linep)->line + j)) {
1665                 int chars;
1666                 int columns = BLI_str_utf8_char_width_safe((*linep)->line + j); /* = 1 for tab */
1667
1668                 /* Mimic replacement of tabs */
1669                 ch = (*linep)->line[j];
1670                 if (ch == '\t') {
1671                         chars = st->tabnumber - i % st->tabnumber;
1672                         ch = ' ';
1673                 }
1674                 else {
1675                         chars = 1;
1676                 }
1677
1678                 while (chars--) {
1679                         if (i + columns - start > max) {
1680                                 end = MIN2(end, i);
1681
1682                                 if (chop) endj = BLI_str_prev_char_utf8((*linep)->line + j) - (*linep)->line;
1683
1684                                 if (endj >= oldc) {
1685                                         if (ch == '\0') *charp = (*linep)->len;
1686                                         else *charp = endj;
1687                                         loop = 0;
1688                                         break;
1689                                 }
1690
1691                                 start = end;
1692                                 end += max;
1693                                 chop = 1;
1694                         }
1695                         else if (ch == '\0') {
1696                                 *charp = (*linep)->len;
1697                                 loop = 0;
1698                                 break;
1699                         }
1700                         else if (ch == ' ' || ch == '-') {
1701                                 end = i + 1;
1702                                 endj = j;
1703                                 chop = 0;
1704                         }
1705                         i += columns;
1706                 }
1707         }
1708
1709         if (!sel) txt_pop_sel(text);
1710 }
1711
1712 static void txt_wrap_move_up(SpaceText *st, ARegion *ar, short sel)
1713 {
1714         Text *text = st->text;
1715         TextLine **linep;
1716         int *charp;
1717         int offl, offc, col;
1718
1719         text_update_character_width(st);
1720
1721         if (sel) { linep = &text->sell; charp = &text->selc; }
1722         else     { linep = &text->curl; charp = &text->curc; }
1723
1724         wrap_offset_in_line(st, ar, *linep, *charp, &offl, &offc);
1725         col = text_get_char_pos(st, (*linep)->line, *charp) + offc;
1726         if (offl) {
1727                 *charp = text_get_cursor_rel(st, ar, *linep, offl - 1, col);
1728         }
1729         else {
1730                 if ((*linep)->prev) {
1731                         int visible_lines;
1732
1733                         *linep = (*linep)->prev;
1734                         visible_lines = text_get_visible_lines(st, ar, (*linep)->line);
1735                         *charp = text_get_cursor_rel(st, ar, *linep, visible_lines - 1, col);
1736                 }
1737                 else {
1738                         *charp = 0;
1739                 }
1740         }
1741
1742         if (!sel) txt_pop_sel(text);
1743 }
1744
1745 static void txt_wrap_move_down(SpaceText *st, ARegion *ar, short sel)
1746 {
1747         Text *text = st->text;
1748         TextLine **linep;
1749         int *charp;
1750         int offl, offc, col, visible_lines;
1751
1752         text_update_character_width(st);
1753
1754         if (sel) { linep = &text->sell; charp = &text->selc; }
1755         else     { linep = &text->curl; charp = &text->curc; }
1756
1757         wrap_offset_in_line(st, ar, *linep, *charp, &offl, &offc);
1758         col = text_get_char_pos(st, (*linep)->line, *charp) + offc;
1759         visible_lines = text_get_visible_lines(st, ar, (*linep)->line);
1760         if (offl < visible_lines - 1) {
1761                 *charp = text_get_cursor_rel(st, ar, *linep, offl + 1, col);
1762         }
1763         else {
1764                 if ((*linep)->next) {
1765                         *linep = (*linep)->next;
1766                         *charp = text_get_cursor_rel(st, ar, *linep, 0, col);
1767                 }
1768                 else {
1769                         *charp = (*linep)->len;
1770                 }
1771         }
1772
1773         if (!sel) txt_pop_sel(text);
1774 }
1775
1776 /* Moves the cursor vertically by the specified number of lines.
1777  * If the destination line is shorter than the current cursor position, the
1778  * cursor will be positioned at the end of this line.
1779  *
1780  * This is to replace screen_skip for PageUp/Down operations.
1781  */
1782 static void cursor_skip(SpaceText *st, ARegion *ar, Text *text, int lines, int sel)
1783 {
1784         TextLine **linep;
1785         int *charp;
1786         
1787         if (sel) { linep = &text->sell; charp = &text->selc; }
1788         else     { linep = &text->curl; charp = &text->curc; }
1789
1790         if (st && ar && st->wordwrap) {
1791                 int rell, relc;
1792
1793                 /* find line and offsets inside it needed to set cursor position */
1794                 if (cursor_skip_find_line(st, ar, lines, linep, charp, &rell, &relc))
1795                         *charp = text_get_cursor_rel(st, ar, *linep, rell, relc);
1796         }
1797         else {
1798                 while (lines > 0 && (*linep)->next) {
1799                         *linep = (*linep)->next;
1800                         lines--;
1801                 }
1802                 while (lines < 0 && (*linep)->prev) {
1803                         *linep = (*linep)->prev;
1804                         lines++;
1805                 }
1806         }
1807
1808         if (*charp > (*linep)->len) *charp = (*linep)->len;
1809
1810         if (!sel) txt_pop_sel(text);
1811 }
1812
1813 static int text_move_cursor(bContext *C, int type, int select)
1814 {
1815         SpaceText *st = CTX_wm_space_text(C);
1816         Text *text = CTX_data_edit_text(C);
1817         ARegion *ar = CTX_wm_region(C);
1818
1819         /* ensure we have the right region, it's optional */
1820         if (ar && ar->regiontype != RGN_TYPE_WINDOW)
1821                 ar = NULL;
1822
1823         switch (type) {
1824                 case LINE_BEGIN:
1825                         if (st && st->wordwrap && ar) txt_wrap_move_bol(st, ar, select);
1826                         else txt_move_bol(text, select);
1827                         break;
1828                         
1829                 case LINE_END:
1830                         if (st && st->wordwrap && ar) txt_wrap_move_eol(st, ar, select);
1831                         else txt_move_eol(text, select);
1832                         break;
1833
1834                 case FILE_TOP:
1835                         txt_move_bof(text, select);
1836                         break;
1837                         
1838                 case FILE_BOTTOM:
1839                         txt_move_eof(text, select);
1840                         break;
1841
1842                 case PREV_WORD:
1843                         txt_jump_left(text, select, true);
1844                         break;
1845
1846                 case NEXT_WORD:
1847                         txt_jump_right(text, select, true);
1848                         break;
1849
1850                 case PREV_CHAR:
1851                         txt_move_left(text, select);
1852                         break;
1853
1854                 case NEXT_CHAR:
1855                         txt_move_right(text, select);
1856                         break;
1857
1858                 case PREV_LINE:
1859                         if (st && st->wordwrap && ar) txt_wrap_move_up(st, ar, select);
1860                         else txt_move_up(text, select);
1861                         break;
1862                         
1863                 case NEXT_LINE:
1864                         if (st && st->wordwrap && ar) txt_wrap_move_down(st, ar, select);
1865                         else txt_move_down(text, select);
1866                         break;
1867
1868                 case PREV_PAGE:
1869                         if (st) cursor_skip(st, ar, st->text, -st->viewlines, select);
1870                         else cursor_skip(NULL, NULL, text, -10, select);
1871                         break;
1872
1873                 case NEXT_PAGE:
1874                         if (st) cursor_skip(st, ar, st->text, st->viewlines, select);
1875                         else cursor_skip(NULL, NULL, text, 10, select);
1876                         break;
1877         }
1878
1879         text_update_cursor_moved(C);
1880         WM_event_add_notifier(C, NC_TEXT | ND_CURSOR, text);
1881
1882         return OPERATOR_FINISHED;
1883 }
1884
1885 static int text_move_exec(bContext *C, wmOperator *op)
1886 {
1887         int type = RNA_enum_get(op->ptr, "type");
1888
1889         return text_move_cursor(C, type, 0);
1890 }
1891
1892 void TEXT_OT_move(wmOperatorType *ot)
1893 {
1894         /* identifiers */
1895         ot->name = "Move Cursor";
1896         ot->idname = "TEXT_OT_move";
1897         ot->description = "Move cursor to position type";
1898         
1899         /* api callbacks */
1900         ot->exec = text_move_exec;
1901         ot->poll = text_edit_poll;
1902
1903         /* properties */
1904         RNA_def_enum(ot->srna, "type", move_type_items, LINE_BEGIN, "Type", "Where to move cursor to");
1905 }
1906
1907 /******************* move select operator ********************/
1908
1909 static int text_move_select_exec(bContext *C, wmOperator *op)
1910 {
1911         int type = RNA_enum_get(op->ptr, "type");
1912
1913         return text_move_cursor(C, type, 1);
1914 }
1915
1916 void TEXT_OT_move_select(wmOperatorType *ot)
1917 {
1918         /* identifiers */
1919         ot->name = "Move Select";
1920         ot->idname = "TEXT_OT_move_select";
1921         ot->description = "Make selection from current cursor position to new cursor position type";
1922         
1923         /* api callbacks */
1924         ot->exec = text_move_select_exec;
1925         ot->poll = text_space_edit_poll;
1926
1927         /* properties */
1928         RNA_def_enum(ot->srna, "type", move_type_items, LINE_BEGIN, "Type", "Where to move cursor to, to make a selection");
1929 }
1930
1931 /******************* jump operator *********************/
1932
1933 static int text_jump_exec(bContext *C, wmOperator *op)
1934 {
1935         Text *text = CTX_data_edit_text(C);
1936         int line = RNA_int_get(op->ptr, "line");
1937         short nlines = txt_get_span(text->lines.first, text->lines.last) + 1;
1938
1939         if (line < 1)
1940                 txt_move_toline(text, 1, 0);
1941         else if (line > nlines)
1942                 txt_move_toline(text, nlines - 1, 0);
1943         else
1944                 txt_move_toline(text, line - 1, 0);
1945
1946         text_update_cursor_moved(C);
1947         WM_event_add_notifier(C, NC_TEXT | ND_CURSOR, text);
1948
1949         return OPERATOR_FINISHED;
1950 }
1951
1952 static int text_jump_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
1953 {
1954         return WM_operator_props_dialog_popup(C, op, 10 * UI_UNIT_X, 5 * UI_UNIT_Y);
1955
1956 }
1957
1958 void TEXT_OT_jump(wmOperatorType *ot)
1959 {
1960         PropertyRNA *prop;
1961
1962         /* identifiers */
1963         ot->name = "Jump";
1964         ot->idname = "TEXT_OT_jump";
1965         ot->description = "Jump cursor to line";
1966         
1967         /* api callbacks */
1968         ot->invoke = text_jump_invoke;
1969         ot->exec = text_jump_exec;
1970         ot->poll = text_edit_poll;
1971
1972         /* properties */
1973         prop = RNA_def_int(ot->srna, "line", 1, 1, INT_MAX, "Line", "Line number to jump to", 1, 10000);
1974         RNA_def_property_translation_context(prop, BLF_I18NCONTEXT_ID_TEXT);
1975 }
1976
1977 /******************* delete operator **********************/
1978
1979 static EnumPropertyItem delete_type_items[] = {
1980         {DEL_NEXT_CHAR, "NEXT_CHARACTER", 0, "Next Character", ""},
1981         {DEL_PREV_CHAR, "PREVIOUS_CHARACTER", 0, "Previous Character", ""},
1982         {DEL_NEXT_WORD, "NEXT_WORD", 0, "Next Word", ""},
1983         {DEL_PREV_WORD, "PREVIOUS_WORD", 0, "Previous Word", ""},
1984         {0, NULL, 0, NULL, NULL}};
1985
1986 static int text_delete_exec(bContext *C, wmOperator *op)
1987 {
1988         Text *text = CTX_data_edit_text(C);
1989         int type = RNA_enum_get(op->ptr, "type");
1990
1991         text_drawcache_tag_update(CTX_wm_space_text(C), 0);
1992
1993         if (type == DEL_PREV_WORD)
1994                 txt_backspace_word(text);
1995         else if (type == DEL_PREV_CHAR)
1996                 txt_backspace_char(text);
1997         else if (type == DEL_NEXT_WORD)
1998                 txt_delete_word(text);
1999         else if (type == DEL_NEXT_CHAR)
2000                 txt_delete_char(text);
2001
2002         text_update_line_edited(text->curl);
2003
2004         text_update_cursor_moved(C);
2005         WM_event_add_notifier(C, NC_TEXT | NA_EDITED, text);
2006
2007         /* run the script while editing, evil but useful */
2008         if (CTX_wm_space_text(C)->live_edit)
2009                 text_run_script(C, NULL);
2010         
2011         return OPERATOR_FINISHED;
2012 }
2013
2014 void TEXT_OT_delete(wmOperatorType *ot)
2015 {
2016         /* identifiers */
2017         ot->name = "Delete";
2018         ot->idname = "TEXT_OT_delete";
2019         ot->description = "Delete text by cursor position";
2020         
2021         /* api callbacks */
2022         ot->exec = text_delete_exec;
2023         ot->poll = text_edit_poll;
2024
2025         /* properties */
2026         RNA_def_enum(ot->srna, "type", delete_type_items, DEL_NEXT_CHAR, "Type", "Which part of the text to delete");
2027 }
2028
2029 /******************* toggle overwrite operator **********************/
2030
2031 static int text_toggle_overwrite_exec(bContext *C, wmOperator *UNUSED(op))
2032 {
2033         SpaceText *st = CTX_wm_space_text(C);
2034
2035         st->overwrite = !st->overwrite;
2036
2037         WM_event_add_notifier(C, NC_TEXT | ND_CURSOR, st->text);
2038
2039         return OPERATOR_FINISHED;
2040 }
2041
2042 void TEXT_OT_overwrite_toggle(wmOperatorType *ot)
2043 {
2044         /* identifiers */
2045         ot->name = "Toggle Overwrite";
2046         ot->idname = "TEXT_OT_overwrite_toggle";
2047         ot->description = "Toggle overwrite while typing";
2048         
2049         /* api callbacks */
2050         ot->exec = text_toggle_overwrite_exec;
2051         ot->poll = text_space_edit_poll;
2052 }
2053
2054 /******************* scroll operator **********************/
2055
2056 /* Moves the view vertically by the specified number of lines */
2057 static void txt_screen_skip(SpaceText *st, ARegion *ar, int lines)
2058 {
2059         int last;
2060
2061         st->top += lines;
2062
2063         last = text_get_total_lines(st, ar);
2064         last = last - (st->viewlines / 2);
2065         
2066         if (st->top > last) st->top = last;
2067         if (st->top < 0) st->top = 0;
2068 }
2069
2070 /* quick enum for tsc->zone (scroller handles) */
2071 enum {
2072         SCROLLHANDLE_BAR,
2073         SCROLLHANDLE_MIN_OUTSIDE,
2074         SCROLLHANDLE_MAX_OUTSIDE
2075 };
2076
2077 typedef struct TextScroll {
2078         short old[2];
2079         short delta[2];
2080
2081         int first;
2082         int scrollbar;
2083
2084         int zone;
2085 } TextScroll;
2086
2087 static int text_scroll_poll(bContext *C)
2088 {
2089         /* it should be possible to still scroll linked texts to read them, even if they can't be edited... */
2090         return CTX_data_edit_text(C) != NULL;
2091 }
2092
2093 static int text_scroll_exec(bContext *C, wmOperator *op)
2094 {
2095         SpaceText *st = CTX_wm_space_text(C);
2096         ARegion *ar = CTX_wm_region(C);
2097
2098         int lines = RNA_int_get(op->ptr, "lines");
2099
2100         if (lines == 0)
2101                 return OPERATOR_CANCELLED;
2102
2103         txt_screen_skip(st, ar, lines * U.wheellinescroll);
2104
2105         ED_area_tag_redraw(CTX_wm_area(C));
2106
2107         return OPERATOR_FINISHED;
2108 }
2109
2110 static void text_scroll_apply(bContext *C, wmOperator *op, const wmEvent *event)
2111 {
2112         SpaceText *st = CTX_wm_space_text(C);
2113         ARegion *ar = CTX_wm_region(C);
2114         TextScroll *tsc = op->customdata;
2115         int mval[2] = {event->x, event->y};
2116         short txtdelta[2] = {0, 0};
2117
2118         text_update_character_width(st);
2119
2120         if (tsc->first) {
2121                 tsc->old[0] = mval[0];
2122                 tsc->old[1] = mval[1];
2123                 tsc->first = 0;
2124         }
2125
2126         tsc->delta[0] += mval[0] - tsc->old[0];
2127         tsc->delta[1] += mval[1] - tsc->old[1];
2128
2129         if (!tsc->scrollbar) {
2130                 txtdelta[0] = -tsc->delta[0] / st->cwidth;
2131                 txtdelta[1] = tsc->delta[1] / (st->lheight_dpi + TXT_LINE_SPACING);
2132
2133                 tsc->delta[0] %= st->cwidth;
2134                 tsc->delta[1] %= (st->lheight_dpi + TXT_LINE_SPACING);
2135         }
2136         else {
2137                 txtdelta[1] = -tsc->delta[1] * st->pix_per_line;
2138                 tsc->delta[1] += txtdelta[1] / st->pix_per_line;
2139         }
2140
2141         if (txtdelta[0] || txtdelta[1]) {
2142                 txt_screen_skip(st, ar, txtdelta[1]);
2143
2144                 if (st->wordwrap) {
2145                         st->left = 0;
2146                 }
2147                 else {
2148                         st->left += txtdelta[0];
2149                         if (st->left < 0) st->left = 0;
2150                 }
2151
2152                 ED_area_tag_redraw(CTX_wm_area(C));
2153         }
2154
2155         tsc->old[0] = mval[0];
2156         tsc->old[1] = mval[1];
2157 }
2158
2159 static void scroll_exit(bContext *C, wmOperator *op)
2160 {
2161         SpaceText *st = CTX_wm_space_text(C);
2162
2163         st->flags &= ~ST_SCROLL_SELECT;
2164         MEM_freeN(op->customdata);
2165 }
2166
2167 static int text_scroll_modal(bContext *C, wmOperator *op, const wmEvent *event)
2168 {
2169         TextScroll *tsc = op->customdata;
2170         SpaceText *st = CTX_wm_space_text(C);
2171         ARegion *ar = CTX_wm_region(C);
2172
2173         switch (event->type) {
2174                 case MOUSEMOVE:
2175                         if (tsc->zone == SCROLLHANDLE_BAR)
2176                                 text_scroll_apply(C, op, event);
2177                         break;
2178                 case LEFTMOUSE:
2179                 case RIGHTMOUSE:
2180                 case MIDDLEMOUSE:
2181                         if (ELEM(tsc->zone, SCROLLHANDLE_MIN_OUTSIDE, SCROLLHANDLE_MAX_OUTSIDE)) {
2182                                 int last;
2183
2184                                 st->top += st->viewlines * (tsc->zone == SCROLLHANDLE_MIN_OUTSIDE ? 1 : -1);
2185
2186                                 last = text_get_total_lines(st, ar);
2187                                 last = last - (st->viewlines / 2);
2188
2189                                 CLAMP(st->top, 0, last);
2190
2191                                 ED_area_tag_redraw(CTX_wm_area(C));
2192                         }
2193                         scroll_exit(C, op);
2194                         return OPERATOR_FINISHED;
2195         }
2196
2197         return OPERATOR_RUNNING_MODAL;
2198 }
2199
2200 static int text_scroll_cancel(bContext *C, wmOperator *op)
2201 {
2202         scroll_exit(C, op);
2203
2204         return OPERATOR_CANCELLED;
2205 }
2206
2207 static int text_scroll_invoke(bContext *C, wmOperator *op, const wmEvent *event)
2208 {
2209         SpaceText *st = CTX_wm_space_text(C);
2210         TextScroll *tsc;
2211         
2212         if (RNA_struct_property_is_set(op->ptr, "lines"))
2213                 return text_scroll_exec(C, op);
2214         
2215         tsc = MEM_callocN(sizeof(TextScroll), "TextScroll");
2216         tsc->first = 1;
2217         tsc->zone = SCROLLHANDLE_BAR;
2218         op->customdata = tsc;
2219         
2220         st->flags |= ST_SCROLL_SELECT;
2221         
2222         if (event->type == MOUSEPAN) {
2223                 text_update_character_width(st);
2224                 
2225                 tsc->old[0] = event->x;
2226                 tsc->old[1] = event->y;
2227                 /* Sensitivity of scroll set to 4pix per line/char */
2228                 tsc->delta[0] = (event->x - event->prevx) * st->cwidth / 4;
2229                 tsc->delta[1] = (event->y - event->prevy) * st->lheight_dpi / 4;
2230                 tsc->first = 0;
2231                 tsc->scrollbar = 0;
2232                 text_scroll_apply(C, op, event);
2233                 scroll_exit(C, op);
2234                 return OPERATOR_FINISHED;
2235         }
2236
2237         WM_event_add_modal_handler(C, op);
2238         
2239         return OPERATOR_RUNNING_MODAL;
2240 }
2241
2242 void TEXT_OT_scroll(wmOperatorType *ot)
2243 {
2244         /* identifiers */
2245         ot->name = "Scroll";
2246         /* don't really see the difference between this and
2247          * scroll_bar. Both do basically the same thing (aside 
2248          * from keymaps).*/
2249         ot->idname = "TEXT_OT_scroll";
2250         ot->description = "Scroll text screen";
2251         
2252         /* api callbacks */
2253         ot->exec = text_scroll_exec;
2254         ot->invoke = text_scroll_invoke;
2255         ot->modal = text_scroll_modal;
2256         ot->cancel = text_scroll_cancel;
2257         ot->poll = text_scroll_poll;
2258
2259         /* flags */
2260         ot->flag = OPTYPE_BLOCKING | OPTYPE_GRAB_POINTER;
2261
2262         /* properties */
2263         RNA_def_int(ot->srna, "lines", 1, INT_MIN, INT_MAX, "Lines", "Number of lines to scroll", -100, 100);
2264 }
2265
2266 /******************** scroll bar operator *******************/
2267
2268 static int text_region_scroll_poll(bContext *C)
2269 {
2270         /* same as text_region_edit_poll except it works on libdata too */
2271         SpaceText *st = CTX_wm_space_text(C);
2272         Text *text = CTX_data_edit_text(C);
2273         ARegion *ar = CTX_wm_region(C);
2274
2275         if (!st || !text)
2276                 return 0;
2277         
2278         if (!ar || ar->regiontype != RGN_TYPE_WINDOW)
2279                 return 0;
2280         
2281         return 1;
2282 }
2283
2284 static int text_scroll_bar_invoke(bContext *C, wmOperator *op, const wmEvent *event)
2285 {
2286         SpaceText *st = CTX_wm_space_text(C);
2287         ARegion *ar = CTX_wm_region(C);
2288         TextScroll *tsc;
2289         const int *mval = event->mval;
2290         int zone = -1;
2291
2292         if (RNA_struct_property_is_set(op->ptr, "lines"))
2293                 return text_scroll_exec(C, op);
2294         
2295         /* verify we are in the right zone */
2296         if (mval[0] > st->txtbar.xmin && mval[0] < st->txtbar.xmax) {
2297                 if (mval[1] >= st->txtbar.ymin && mval[1] <= st->txtbar.ymax) {
2298                         /* mouse inside scroll handle */
2299                         zone = SCROLLHANDLE_BAR;
2300                 }
2301                 else if (mval[1] > TXT_SCROLL_SPACE && mval[1] < ar->winy - TXT_SCROLL_SPACE) {
2302                         if (mval[1] < st->txtbar.ymin) zone = SCROLLHANDLE_MIN_OUTSIDE;
2303                         else zone = SCROLLHANDLE_MAX_OUTSIDE;
2304                 }
2305         }
2306
2307         if (zone == -1) {
2308                 /* we are outside slider - nothing to do */
2309                 return OPERATOR_PASS_THROUGH;
2310         }
2311
2312         tsc = MEM_callocN(sizeof(TextScroll), "TextScroll");
2313         tsc->first = 1;
2314         tsc->scrollbar = 1;
2315         tsc->zone = zone;
2316         op->customdata = tsc;
2317         st->flags |= ST_SCROLL_SELECT;
2318
2319         /* jump scroll, works in v2d but needs to be added here too :S */
2320         if (event->type == MIDDLEMOUSE) {
2321                 tsc->old[0] = ar->winrct.xmin + BLI_rcti_cent_x(&st->txtbar);
2322                 tsc->old[1] = ar->winrct.ymin + BLI_rcti_cent_y(&st->txtbar);
2323
2324                 tsc->delta[0] = 0;
2325                 tsc->delta[1] = 0;
2326                 tsc->first = 0;
2327                 tsc->zone = SCROLLHANDLE_BAR;
2328                 text_scroll_apply(C, op, event);
2329         }
2330
2331         WM_event_add_modal_handler(C, op);
2332
2333         return OPERATOR_RUNNING_MODAL;
2334 }
2335
2336 void TEXT_OT_scroll_bar(wmOperatorType *ot)
2337 {
2338         /* identifiers */
2339         ot->name = "Scrollbar";
2340         /* don't really see the difference between this and
2341          * scroll. Both do basically the same thing (aside 
2342          * from keymaps).*/
2343         ot->idname = "TEXT_OT_scroll_bar";
2344         ot->description = "Scroll text screen";
2345         
2346         /* api callbacks */
2347         ot->invoke = text_scroll_bar_invoke;
2348         ot->modal = text_scroll_modal;
2349         ot->cancel = text_scroll_cancel;
2350         ot->poll = text_region_scroll_poll;
2351
2352         /* flags */
2353         ot->flag = OPTYPE_BLOCKING;
2354
2355         /* properties */
2356         RNA_def_int(ot->srna, "lines", 1, INT_MIN, INT_MAX, "Lines", "Number of lines to scroll", -100, 100);
2357 }
2358
2359 /******************* set selection operator **********************/
2360
2361 typedef struct SetSelection {
2362         int selecting;
2363         int selc, sell;
2364         short old[2];
2365 } SetSelection;
2366
2367 static int flatten_width(SpaceText *st, const char *str)
2368 {
2369         int i, total = 0;
2370
2371         for (i = 0; str[i]; i += BLI_str_utf8_size_safe(str + i)) {
2372                 if (str[i] == '\t') {
2373                         total += st->tabnumber - total % st->tabnumber;
2374                 }
2375                 else {
2376                         total += BLI_str_utf8_char_width_safe(str + i);
2377                 }
2378         }
2379         
2380         return total;
2381 }
2382
2383 static int flatten_column_to_offset(SpaceText *st, const char *str, int index)
2384 {
2385         int i = 0, j = 0, col;
2386
2387         while (*(str + j)) {
2388                 if (str[j] == '\t')
2389                         col = st->tabnumber - i % st->tabnumber;
2390                 else
2391                         col = BLI_str_utf8_char_width_safe(str + j);
2392                 
2393                 if (i + col > index)
2394                         break;
2395                 
2396                 i += col;
2397                 j += BLI_str_utf8_size_safe(str + j);
2398         }
2399         
2400         return j;
2401 }
2402
2403 static TextLine *get_first_visible_line(SpaceText *st, ARegion *ar, int *y)
2404 {
2405         TextLine *linep = st->text->lines.first;
2406         int i;
2407         for (i = st->top; i > 0 && linep; ) {
2408                 int lines = text_get_visible_lines(st, ar, linep->line);
2409                 
2410                 if (i - lines < 0) {
2411                         *y += i;
2412                         break;
2413                 }
2414                 else {
2415                         linep = linep->next;
2416                         i -= lines;
2417                 }
2418         }
2419         return linep;
2420 }
2421
2422 static void text_cursor_set_to_pos_wrapped(SpaceText *st, ARegion *ar, int x, int y, int sel)
2423 {
2424         Text *text = st->text;
2425         int max = wrap_width(st, ar); /* column */
2426         int charp = -1;               /* mem */
2427         int loop = 1, found = 0;      /* flags */
2428         char ch;
2429         
2430         /* Point to first visible line */
2431         TextLine *linep = get_first_visible_line(st, ar, &y);
2432         
2433         while (loop && linep) {
2434                 int i = 0, start = 0, end = max; /* column */
2435                 int j = 0, curs = 0, endj = 0;   /* mem */
2436                 int chop = 1;                    /* flags */
2437                 
2438                 for (; loop; j += BLI_str_utf8_size_safe(linep->line + j)) {
2439                         int chars;
2440                         int columns = BLI_str_utf8_char_width_safe(linep->line + j); /* = 1 for tab */
2441                         
2442                         /* Mimic replacement of tabs */
2443                         ch = linep->line[j];
2444                         if (ch == '\t') {
2445                                 chars = st->tabnumber - i % st->tabnumber;
2446                                 ch = ' ';
2447                         }
2448                         else {
2449                                 chars = 1;
2450                         }
2451                         
2452                         while (chars--) {
2453                                 /* Gone too far, go back to last wrap point */
2454                                 if (y < 0) {
2455                                         charp = endj;
2456                                         loop = 0;
2457                                         break;
2458                                         /* Exactly at the cursor */
2459                                 }
2460                                 else if (y == 0 && i - start <= x && i + columns - start > x) {
2461                                         /* current position could be wrapped to next line */
2462                                         /* this should be checked when end of current line would be reached */
2463                                         charp = curs = j;
2464                                         found = 1;
2465                                         /* Prepare curs for next wrap */
2466                                 }
2467                                 else if (i - end <= x && i + columns - end > x) {
2468                                         curs = j;
2469                                 }
2470                                 if (i + columns - start > max) {
2471                                         end = MIN2(end, i);
2472                                         
2473                                         if (found) {
2474                                                 /* exact cursor position was found, check if it's */
2475                                                 /* still on needed line (hasn't been wrapped) */
2476                                                 if (charp > endj && !chop && ch != '\0') charp = endj;
2477                                                 loop = 0;
2478                                                 break;
2479                                         }
2480                                         
2481                                         if (chop) endj = j;
2482                                         start = end;
2483                                         end += max;
2484                                         
2485                                         if (j < linep->len)
2486                                                 y--;
2487                                         
2488                                         chop = 1;
2489                                         if (y == 0 && i + columns - start > x) {
2490                                                 charp = curs;
2491                                                 loop = 0;
2492                                                 break;
2493                                         }
2494                                 }
2495                                 else if (ch == ' ' || ch == '-' || ch == '\0') {
2496                                         if (found) {
2497                                                 loop = 0;
2498                                                 break;
2499                                         }
2500                                         
2501                                         if (y == 0 && i + columns - start > x) {
2502                                                 charp = curs;
2503                                                 loop = 0;
2504                                                 break;
2505                                         }
2506                                         end = i + 1;
2507                                         endj = j;
2508                                         chop = 0;
2509                                 }
2510                                 i += columns;
2511                         }
2512                         
2513                         if (ch == '\0') break;
2514                 }
2515                 
2516                 if (!loop || found) break;
2517                 
2518                 if (!linep->next) {
2519                         charp = linep->len;
2520                         break;
2521                 }
2522                 
2523                 /* On correct line but didn't meet cursor, must be at end */
2524                 if (y == 0) {
2525                         charp = linep->len;
2526                         break;
2527                 }
2528                 linep = linep->next;
2529                 
2530                 y--;
2531         }
2532
2533         if (linep && charp != -1) {
2534                 if (sel) { text->sell = linep; text->selc = charp; }
2535                 else     { text->curl = linep; text->curc = charp; }
2536         }
2537 }
2538
2539 static void text_cursor_set_to_pos(SpaceText *st, ARegion *ar, int x, int y, int sel)
2540 {
2541         Text *text = st->text;
2542         text_update_character_width(st);
2543         y = (ar->winy - 2 - y) / (st->lheight_dpi + TXT_LINE_SPACING);
2544
2545         if (st->showlinenrs) x -= TXT_OFFSET + TEXTXLOC;
2546         else x -= TXT_OFFSET;
2547
2548         if (x < 0) x = 0;
2549         x = text_pixel_x_to_column(st, x) + st->left;
2550         
2551         if (st->wordwrap) {
2552                 text_cursor_set_to_pos_wrapped(st, ar, x, y, sel);
2553         }
2554         else {
2555                 TextLine **linep;
2556                 int *charp;
2557                 int w;
2558                 
2559                 if (sel) { linep = &text->sell; charp = &text->selc; }
2560                 else     { linep = &text->curl; charp = &text->curc; }
2561                 
2562                 y -= txt_get_span(text->lines.first, *linep) - st->top;
2563                 
2564                 if (y > 0) {
2565                         while (y-- != 0) if ((*linep)->next) *linep = (*linep)->next;
2566                 }
2567                 else if (y < 0) {
2568                         while (y++ != 0) if ((*linep)->prev) *linep = (*linep)->prev;
2569                 }
2570
2571                 
2572                 w = flatten_width(st, (*linep)->line);
2573                 if (x < w) *charp = flatten_column_to_offset(st, (*linep)->line, x);
2574                 else *charp = (*linep)->len;
2575         }
2576         if (!sel) txt_pop_sel(text);
2577 }
2578
2579 static void text_cursor_set_apply(bContext *C, wmOperator *op, const wmEvent *event)
2580 {
2581         SpaceText *st = CTX_wm_space_text(C);
2582         ARegion *ar = CTX_wm_region(C);
2583         SetSelection *ssel = op->customdata;
2584
2585         if (event->mval[1] < 0 || event->mval[1] > ar->winy) {
2586                 int d = (ssel->old[1] - event->mval[1]) * st->pix_per_line;
2587                 if (d) txt_screen_skip(st, ar, d);
2588
2589                 text_cursor_set_to_pos(st, ar, event->mval[0], event->mval[1] < 0 ? 0 : ar->winy, 1);
2590
2591                 text_update_cursor_moved(C);
2592                 WM_event_add_notifier(C, NC_TEXT | ND_CURSOR, st->text);
2593         }
2594         else if (!st->wordwrap && (event->mval[0] < 0 || event->mval[0] > ar->winx)) {
2595                 if (event->mval[0] > ar->winx) st->left++;
2596                 else if (event->mval[0] < 0 && st->left > 0) st->left--;
2597                 
2598                 text_cursor_set_to_pos(st, ar, event->mval[0], event->mval[1], 1);
2599                 
2600                 text_update_cursor_moved(C);
2601                 WM_event_add_notifier(C, NC_TEXT | ND_CURSOR, st->text);
2602                 // XXX PIL_sleep_ms(10);
2603         }
2604         else {
2605                 text_cursor_set_to_pos(st, ar, event->mval[0], event->mval[1], 1);
2606
2607                 text_update_cursor_moved(C);
2608                 WM_event_add_notifier(C, NC_TEXT | ND_CURSOR, st->text);
2609
2610                 ssel->old[0] = event->mval[0];
2611                 ssel->old[1] = event->mval[1];
2612         }
2613 }
2614
2615 static void text_cursor_set_exit(bContext *C, wmOperator *op)
2616 {
2617         SpaceText *st = CTX_wm_space_text(C);
2618         Text *text = st->text;
2619         SetSelection *ssel = op->customdata;
2620         char *buffer;
2621
2622         if (txt_has_sel(text)) {
2623                 buffer = txt_sel_to_buf(text);
2624                 WM_clipboard_text_set(buffer, 1);
2625                 MEM_freeN(buffer);
2626         }
2627
2628         text_update_cursor_moved(C);
2629         WM_event_add_notifier(C, NC_TEXT | ND_CURSOR, st->text);
2630
2631         MEM_freeN(ssel);
2632 }
2633
2634 static int text_set_selection_invoke(bContext *C, wmOperator *op, const wmEvent *event)
2635 {
2636         SpaceText *st = CTX_wm_space_text(C);
2637         SetSelection *ssel;
2638
2639         if (event->mval[0] >= st->txtbar.xmin)
2640                 return OPERATOR_PASS_THROUGH;
2641
2642         op->customdata = MEM_callocN(sizeof(SetSelection), "SetCursor");
2643         ssel = op->customdata;
2644         ssel->selecting = RNA_boolean_get(op->ptr, "select");
2645
2646         ssel->old[0] = event->mval[0];
2647         ssel->old[1] = event->mval[1];
2648
2649         ssel->sell = txt_get_span(st->text->lines.first, st->text->sell);
2650         ssel->selc = st->text->selc;
2651
2652         WM_event_add_modal_handler(C, op);
2653
2654         text_cursor_set_apply(C, op, event);
2655
2656         return OPERATOR_RUNNING_MODAL;
2657 }
2658
2659 static int text_set_selection_modal(bContext *C, wmOperator *op, const wmEvent *event)
2660 {
2661         switch (event->type) {
2662                 case LEFTMOUSE:
2663                 case MIDDLEMOUSE:
2664                 case RIGHTMOUSE:
2665                         text_cursor_set_exit(C, op);
2666                         return OPERATOR_FINISHED;
2667                 case MOUSEMOVE:
2668                         text_cursor_set_apply(C, op, event);
2669                         break;
2670         }
2671
2672         return OPERATOR_RUNNING_MODAL;
2673 }
2674
2675 static int text_set_selection_cancel(bContext *C, wmOperator *op)
2676 {
2677         text_cursor_set_exit(C, op);
2678         return OPERATOR_FINISHED;
2679 }
2680
2681 void TEXT_OT_selection_set(wmOperatorType *ot)
2682 {
2683         /* identifiers */
2684         ot->name = "Set Selection";
2685         ot->idname = "TEXT_OT_selection_set";
2686         ot->description = "Set cursor selection";
2687
2688         /* api callbacks */
2689         ot->invoke = text_set_selection_invoke;
2690         ot->modal = text_set_selection_modal;
2691         ot->cancel = text_set_selection_cancel;
2692         ot->poll = text_region_edit_poll;
2693
2694         /* properties */
2695         RNA_def_boolean(ot->srna, "select", 0, "Select", "Set selection end rather than cursor");
2696 }
2697
2698 /******************* set cursor operator **********************/
2699
2700 static int text_cursor_set_exec(bContext *C, wmOperator *op)
2701 {
2702         SpaceText *st = CTX_wm_space_text(C);
2703         ARegion *ar = CTX_wm_region(C);
2704         int x = RNA_int_get(op->ptr, "x");
2705         int y = RNA_int_get(op->ptr, "y");
2706
2707         text_cursor_set_to_pos(st, ar, x, y, 0);
2708
2709         text_update_cursor_moved(C);
2710         WM_event_add_notifier(C, NC_TEXT | ND_CURSOR, st->text);
2711
2712         return OPERATOR_PASS_THROUGH;
2713 }
2714
2715 static int text_cursor_set_invoke(bContext *C, wmOperator *op, const wmEvent *event)
2716 {
2717         SpaceText *st = CTX_wm_space_text(C);
2718
2719         if (event->mval[0] >= st->txtbar.xmin)
2720                 return OPERATOR_PASS_THROUGH;
2721
2722         RNA_int_set(op->ptr, "x", event->mval[0]);
2723         RNA_int_set(op->ptr, "y", event->mval[1]);
2724
2725         return text_cursor_set_exec(C, op);
2726 }
2727
2728 void TEXT_OT_cursor_set(wmOperatorType *ot)
2729 {
2730         /* identifiers */
2731         ot->name = "Set Cursor";
2732         ot->idname = "TEXT_OT_cursor_set";
2733         ot->description = "Set cursor position";
2734
2735         /* api callbacks */
2736         ot->invoke = text_cursor_set_invoke;
2737         ot->exec = text_cursor_set_exec;
2738         ot->poll = text_region_edit_poll;
2739
2740         /* properties */
2741         RNA_def_int(ot->srna, "x", 0, INT_MIN, INT_MAX, "X", "", INT_MIN, INT_MAX);
2742         RNA_def_int(ot->srna, "y", 0, INT_MIN, INT_MAX, "Y", "", INT_MIN, INT_MAX);
2743 }
2744
2745 /******************* line number operator **********************/
2746
2747 static int text_line_number_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event)
2748 {
2749         SpaceText *st = CTX_wm_space_text(C);
2750         Text *text = CTX_data_edit_text(C);
2751         ARegion *ar = CTX_wm_region(C);
2752         const int *mval = event->mval;
2753         double time;
2754         static int jump_to = 0;
2755         static double last_jump = 0;
2756
2757         text_update_character_width(st);
2758
2759         if (!st->showlinenrs)
2760                 return OPERATOR_PASS_THROUGH;
2761
2762         if (!(mval[0] > 2 && mval[0] < (TXT_OFFSET + TEXTXLOC) && mval[1] > 2 && mval[1] < ar->winy - 2))
2763                 return OPERATOR_PASS_THROUGH;
2764
2765         if (!(event->ascii >= '0' && event->ascii <= '9'))
2766                 return OPERATOR_PASS_THROUGH;
2767
2768         time = PIL_check_seconds_timer();
2769         if (last_jump < time - 1)
2770                 jump_to = 0;
2771
2772         jump_to *= 10;
2773         jump_to += (int)(event->ascii - '0');
2774
2775         txt_move_toline(text, jump_to - 1, 0);
2776         last_jump = time;
2777
2778         text_update_cursor_moved(C);
2779         WM_event_add_notifier(C, NC_TEXT | ND_CURSOR, text);
2780
2781         return OPERATOR_FINISHED;
2782 }
2783
2784 void TEXT_OT_line_number(wmOperatorType *ot)
2785 {
2786         /* identifiers */
2787         ot->name = "Line Number";
2788         ot->idname = "TEXT_OT_line_number";
2789         ot->description = "The current line number";
2790         
2791         /* api callbacks */
2792         ot->invoke = text_line_number_invoke;
2793         ot->poll = text_region_edit_poll;
2794 }
2795
2796 /******************* insert operator **********************/
2797
2798 static int text_insert_exec(bContext *C, wmOperator *op)
2799 {
2800         SpaceText *st = CTX_wm_space_text(C);
2801         Text *text = CTX_data_edit_text(C);
2802         char *str;
2803         int done = FALSE;
2804         size_t i = 0;
2805         unsigned int code;
2806
2807         text_drawcache_tag_update(st, 0);
2808
2809         str = RNA_string_get_alloc(op->ptr, "text", NULL, 0);
2810
2811         if (st && st->overwrite) {
2812                 while (str[i]) {
2813                         code = BLI_str_utf8_as_unicode_step(str, &i);
2814                         done |= txt_replace_char(text, code);
2815                 }
2816         }
2817         else {
2818                 while (str[i]) {
2819                         code = BLI_str_utf8_as_unicode_step(str, &i);
2820                         done |= txt_add_char(text, code);
2821                 }
2822         }
2823
2824         MEM_freeN(str);
2825         
2826         if (!done)
2827                 return OPERATOR_CANCELLED;
2828
2829         text_update_line_edited(text->curl);
2830
2831         text_update_cursor_moved(C);
2832         WM_event_add_notifier(C, NC_TEXT | NA_EDITED, text);
2833
2834         return OPERATOR_FINISHED;
2835 }
2836
2837 static int text_insert_invoke(bContext *C, wmOperator *op, const wmEvent *event)
2838 {
2839         int ret;
2840
2841         // if (!RNA_struct_property_is_set(op->ptr, "text")) { /* always set from keymap XXX */
2842         if (!RNA_string_length(op->ptr, "text")) {
2843                 /* if alt/ctrl/super are pressed pass through except for utf8 character event
2844                  * (when input method are used for utf8 inputs, the user may assign key event
2845                  * including alt/ctrl/super like ctrl+m to commit utf8 string.  in such case,
2846                  * the modifiers in the utf8 character event make no sense.) */
2847                 if ((event->ctrl || event->oskey) && !event->utf8_buf[0]) {
2848                         return OPERATOR_PASS_THROUGH;
2849                 }
2850                 else {
2851                         char str[BLI_UTF8_MAX + 1];
2852                         size_t len;
2853                         
2854                         if (event->utf8_buf[0]) {
2855                                 len = BLI_str_utf8_size_safe(event->utf8_buf);
2856                                 memcpy(str, event->utf8_buf, len);
2857                         }
2858                         else {
2859                                 /* in theory, ghost can set value to extended ascii here */
2860                                 len = BLI_str_utf8_from_unicode(event->ascii, str);
2861                         }
2862                         str[len] = '\0';
2863                         RNA_string_set(op->ptr, "text", str);
2864                 }
2865         }
2866
2867         ret = text_insert_exec(C, op);
2868         
2869         /* run the script while editing, evil but useful */
2870         if (ret == OPERATOR_FINISHED && CTX_wm_space_text(C)->live_edit)
2871                 text_run_script(C, NULL);
2872
2873         return ret;
2874 }
2875
2876 void TEXT_OT_insert(wmOperatorType *ot)
2877 {
2878         PropertyRNA *prop;
2879
2880         /* identifiers */
2881         ot->name = "Insert";
2882         ot->idname = "TEXT_OT_insert";
2883         ot->description = "Insert text at cursor position";
2884         
2885         /* api callbacks */
2886         ot->exec = text_insert_exec;
2887         ot->invoke = text_insert_invoke;
2888         ot->poll = text_edit_poll;
2889
2890         /* properties */
2891         prop = RNA_def_string(ot->srna, "text", "", 0, "Text", "Text to insert at the cursor position");
2892         RNA_def_property_flag(prop, PROP_SKIP_SAVE);
2893 }
2894
2895 /******************* find operator *********************/
2896
2897 /* mode */
2898 #define TEXT_FIND       0
2899 #define TEXT_REPLACE    1
2900
2901 static int text_find_and_replace(bContext *C, wmOperator *op, short mode)
2902 {
2903         Main *bmain = CTX_data_main(C);
2904         SpaceText *st = CTX_wm_space_text(C);
2905         Text *text = st->text;
2906         int flags;
2907         int found = 0;
2908         char *tmp;
2909
2910         if (!st->findstr[0] || (mode == TEXT_REPLACE && !st->replacestr[0]))
2911                 return OPERATOR_CANCELLED;
2912
2913         flags = st->flags;
2914         if (flags & ST_FIND_ALL)
2915                 flags &= ~ST_FIND_WRAP;
2916
2917         /* Replace current */
2918         if (mode != TEXT_FIND && txt_has_sel(text)) {
2919                 tmp = txt_sel_to_buf(text);
2920
2921                 if (flags & ST_MATCH_CASE) found = strcmp(st->findstr, tmp) == 0;
2922                 else found = BLI_strcasecmp(st->findstr, tmp) == 0;
2923
2924                 if (found) {
2925                         if (mode == TEXT_REPLACE) {
2926                                 txt_insert_buf(text, st->replacestr);
2927                                 if (text->curl && text->curl->format) {
2928                                         MEM_freeN(text->curl->format);
2929                                         text->curl->format = NULL;
2930                                 }
2931                                 text_update_cursor_moved(C);
2932                                 WM_event_add_notifier(C, NC_TEXT | NA_EDITED, text);
2933                                 text_drawcache_tag_update(CTX_wm_space_text(C), 1);
2934                         }
2935                 }
2936                 MEM_freeN(tmp);
2937                 tmp = NULL;
2938         }
2939
2940         /* Find next */
2941         if (txt_find_string(text, st->findstr, flags & ST_FIND_WRAP, flags & ST_MATCH_CASE)) {
2942                 text_update_cursor_moved(C);
2943                 WM_event_add_notifier(C, NC_TEXT | ND_CURSOR, text);
2944         }
2945         else if (flags & ST_FIND_ALL) {
2946                 if (text->id.next)
2947                         text = st->text = text->id.next;
2948                 else
2949                         text = st->text = bmain->text.first;
2950                 txt_move_toline(text, 0, 0);
2951                 text_update_cursor_moved(C);
2952                 WM_event_add_notifier(C, NC_TEXT | ND_CURSOR, text);
2953         }
2954         else {
2955                 if (!found) BKE_reportf(op->reports, RPT_ERROR, "Text not found: %s", st->findstr);
2956         }
2957
2958         return OPERATOR_FINISHED;
2959 }
2960
2961 static int text_find_exec(bContext *C, wmOperator *op)
2962 {
2963         return text_find_and_replace(C, op, TEXT_FIND);
2964 }
2965
2966 void TEXT_OT_find(wmOperatorType *ot)
2967 {
2968         /* identifiers */
2969         ot->name = "Find";
2970         ot->idname = "TEXT_OT_find";
2971         ot->description = "Find specified text";
2972         
2973         /* api callbacks */
2974         ot->exec = text_find_exec;
2975         ot->poll = text_space_edit_poll;
2976 }
2977
2978 /******************* replace operator *********************/
2979
2980 static int text_replace_exec(bContext *C, wmOperator *op)
2981 {
2982         return text_find_and_replace(C, op, TEXT_REPLACE);
2983 }
2984
2985 void TEXT_OT_replace(wmOperatorType *ot)
2986 {
2987         /* identifiers */
2988         ot->name = "Replace";
2989         ot->idname = "TEXT_OT_replace";
2990         ot->description = "Replace text with the specified text";
2991
2992         /* api callbacks */
2993         ot->exec = text_replace_exec;
2994         ot->poll = text_space_edit_poll;
2995 }
2996
2997 /******************* find set selected *********************/
2998
2999 static int text_find_set_selected_exec(bContext *C, wmOperator *op)
3000 {
3001         SpaceText *st = CTX_wm_space_text(C);
3002         Text *text = CTX_data_edit_text(C);
3003         char *tmp;
3004
3005         tmp = txt_sel_to_buf(text);
3006         BLI_strncpy(st->findstr, tmp, ST_MAX_FIND_STR);
3007         MEM_freeN(tmp);
3008
3009         if (!st->findstr[0])
3010                 return OPERATOR_FINISHED;
3011
3012         return text_find_and_replace(C, op, TEXT_FIND);
3013 }
3014
3015 void TEXT_OT_find_set_selected(wmOperatorType *ot)
3016 {
3017         /* identifiers */
3018         ot->name = "Find Set Selected";
3019         ot->idname = "TEXT_OT_find_set_selected";
3020         ot->description = "Find specified text and set as selected";
3021         
3022         /* api callbacks */
3023         ot->exec = text_find_set_selected_exec;
3024         ot->poll = text_space_edit_poll;
3025 }
3026
3027 /******************* replace set selected *********************/
3028
3029 static int text_replace_set_selected_exec(bContext *C, wmOperator *UNUSED(op))
3030 {
3031         SpaceText *st = CTX_wm_space_text(C);
3032         Text *text = CTX_data_edit_text(C);
3033         char *tmp;
3034
3035         tmp = txt_sel_to_buf(text);
3036         BLI_strncpy(st->replacestr, tmp, ST_MAX_FIND_STR);
3037         MEM_freeN(tmp);
3038
3039         return OPERATOR_FINISHED;
3040 }
3041
3042 void TEXT_OT_replace_set_selected(wmOperatorType *ot)
3043 {
3044         /* identifiers */
3045         ot->name = "Replace Set Selected";
3046         ot->idname = "TEXT_OT_replace_set_selected";
3047         ot->description = "Replace text with specified text and set as selected";
3048         
3049         /* api callbacks */
3050         ot->exec = text_replace_set_selected_exec;
3051         ot->poll = text_space_edit_poll;
3052 }
3053
3054 /****************** resolve conflict operator ******************/
3055
3056 enum { RESOLVE_IGNORE, RESOLVE_RELOAD, RESOLVE_SAVE, RESOLVE_MAKE_INTERNAL };
3057 static EnumPropertyItem resolution_items[] = {
3058         {RESOLVE_IGNORE, "IGNORE", 0, "Ignore", ""},
3059         {RESOLVE_RELOAD, "RELOAD", 0, "Reload", ""},
3060         {RESOLVE_SAVE, "SAVE", 0, "Save", ""},
3061         {RESOLVE_MAKE_INTERNAL, "MAKE_INTERNAL", 0, "Make Internal", ""},
3062         {0, NULL, 0, NULL, NULL}
3063 };
3064
3065 /* returns 0 if file on disk is the same or Text is in memory only
3066  * returns 1 if file has been modified on disk since last local edit
3067  * returns 2 if file on disk has been deleted
3068  * -1 is returned if an error occurs */
3069
3070 int text_file_modified(Text *text)
3071 {
3072         struct stat st;
3073         int result;
3074         char file[FILE_MAX];
3075
3076         if (!text || !text->name)
3077                 return 0;
3078
3079         BLI_strncpy(file, text->name, FILE_MAX);
3080         BLI_path_abs(file, G.main->name);
3081
3082         if (!BLI_exists(file))
3083                 return 2;
3084
3085         result = stat(file, &st);
3086         
3087         if (result == -1)
3088                 return -1;
3089
3090         if ((st.st_mode & S_IFMT) != S_IFREG)
3091                 return -1;
3092
3093         if (st.st_mtime > text->mtime)
3094                 return 1;
3095
3096         return 0;
3097 }
3098
3099 static void text_ignore_modified(Text *text)
3100 {
3101         struct stat st;
3102         int result;
3103         char file[FILE_MAX];
3104
3105         if (!text || !text->name) return;
3106
3107         BLI_strncpy(file, text->name, FILE_MAX);
3108         BLI_path_abs(file, G.main->name);
3109
3110         if (!BLI_exists(file)) return;
3111
3112         result = stat(file, &st);
3113         
3114         if (result == -1 || (st.st_mode & S_IFMT) != S_IFREG)
3115                 return;
3116
3117         text->mtime = st.st_mtime;
3118 }
3119
3120 static int text_resolve_conflict_exec(bContext *C, wmOperator *op)
3121 {
3122         Text *text = CTX_data_edit_text(C);
3123         int resolution = RNA_enum_get(op->ptr, "resolution");
3124
3125         switch (resolution) {
3126                 case RESOLVE_RELOAD:
3127                         return text_reload_exec(C, op);
3128                 case RESOLVE_SAVE:
3129                         return text_save_exec(C, op);
3130                 case RESOLVE_MAKE_INTERNAL:
3131                         return text_make_internal_exec(C, op);
3132                 case RESOLVE_IGNORE:
3133                         text_ignore_modified(text);
3134                         return OPERATOR_FINISHED;
3135         }
3136
3137         return OPERATOR_CANCELLED;
3138 }
3139
3140 static int text_resolve_conflict_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
3141 {
3142         Text *text = CTX_data_edit_text(C);
3143         uiPopupMenu *pup;
3144         uiLayout *layout;
3145
3146         switch (text_file_modified(text)) {
3147                 case 1:
3148                         if (text->flags & TXT_ISDIRTY) {
3149                                 /* modified locally and externally, ahhh. offer more possibilites. */
3150                                 pup = uiPupMenuBegin(C, IFACE_("File Modified Outside and Inside Blender"), ICON_NONE);
3151                                 layout = uiPupMenuLayout(pup);
3152                                 uiItemEnumO_ptr(layout, op->type, IFACE_("Reload from disk (ignore local changes)"),
3153                                                 0, "resolution", RESOLVE_RELOAD);
3154                                 uiItemEnumO_ptr(layout, op->type, IFACE_("Save to disk (ignore outside changes)"),
3155                                                 0, "resolution", RESOLVE_SAVE);
3156                                 uiItemEnumO_ptr(layout, op->type, IFACE_("Make text internal (separate copy)"),
3157                                                 0, "resolution", RESOLVE_MAKE_INTERNAL);
3158                                 uiPupMenuEnd(C, pup);
3159                         }
3160                         else {
3161                                 pup = uiPupMenuBegin(C, IFACE_("File Modified Outside Blender"), ICON_NONE);
3162                                 layout = uiPupMenuLayout(pup);
3163                                 uiItemEnumO_ptr(layout, op->type, IFACE_("Reload from disk"), 0, "resolution", RESOLVE_RELOAD);
3164                                 uiItemEnumO_ptr(layout, op->type, IFACE_("Make text internal (separate copy)"),
3165                                                 0, "resolution", RESOLVE_MAKE_INTERNAL);
3166                                 uiItemEnumO_ptr(layout, op->type, IFACE_("Ignore"), 0, "resolution", RESOLVE_IGNORE);
3167                                 uiPupMenuEnd(C, pup);
3168                         }
3169                         break;
3170                 case 2:
3171                         pup = uiPupMenuBegin(C, IFACE_("File Deleted Outside Blender"), ICON_NONE);
3172                         layout = uiPupMenuLayout(pup);
3173                         uiItemEnumO_ptr(layout, op->type, IFACE_("Make text internal"), 0, "resolution", RESOLVE_MAKE_INTERNAL);
3174                         uiItemEnumO_ptr(layout, op->type, IFACE_("Recreate file"), 0, "resolution", RESOLVE_SAVE);
3175                         uiPupMenuEnd(C, pup);
3176                         break;
3177         }
3178
3179         return OPERATOR_CANCELLED;
3180 }
3181
3182 void TEXT_OT_resolve_conflict(wmOperatorType *ot)
3183 {
3184         /* identifiers */
3185         ot->name = "Resolve Conflict";
3186         ot->idname = "TEXT_OT_resolve_conflict";
3187         ot->description = "When external text is out of sync, resolve the conflict";
3188
3189         /* api callbacks */
3190         ot->exec = text_resolve_conflict_exec;
3191         ot->invoke = text_resolve_conflict_invoke;
3192         ot->poll = text_save_poll;
3193
3194         /* properties */
3195         RNA_def_enum(ot->srna, "resolution", resolution_items, RESOLVE_IGNORE, "Resolution", "How to solve conflict due to differences in internal and external text");
3196 }
3197
3198 /********************** to 3d object operator *****************/
3199
3200 static int text_to_3d_object_exec(bContext *C, wmOperator *op)
3201 {
3202         Text *text = CTX_data_edit_text(C);
3203         int split_lines = RNA_boolean_get(op->ptr, "split_lines");
3204
3205         ED_text_to_object(C, text, split_lines);
3206
3207         return OPERATOR_FINISHED;
3208 }
3209
3210 void TEXT_OT_to_3d_object(wmOperatorType *ot)
3211 {
3212         /* identifiers */
3213         ot->name = "To 3D Object";
3214         ot->idname = "TEXT_OT_to_3d_object";
3215         ot->description = "Create 3D text object from active text data block";
3216         
3217         /* api callbacks */
3218         ot->exec = text_to_3d_object_exec;
3219         ot->poll = text_edit_poll;
3220         
3221         /* flags */
3222         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
3223
3224         /* properties */
3225         RNA_def_boolean(ot->srna, "split_lines", 0, "Split Lines", "Create one object per line in the text");
3226 }
3227
3228
3229 /************************ undo ******************************/
3230
3231 void ED_text_undo_step(bContext *C, int step)
3232 {
3233         Text *text = CTX_data_edit_text(C);
3234
3235         if (!text)
3236                 return;
3237
3238         if (step == 1)
3239                 txt_do_undo(text);
3240         else if (step == -1)
3241                 txt_do_redo(text);
3242
3243         text_update_edited(text);
3244
3245         text_update_cursor_moved(C);
3246         text_drawcache_tag_update(CTX_wm_space_text(C), 1);
3247         WM_event_add_notifier(C, NC_TEXT | NA_EDITED, text);
3248 }
3249