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