svn merge -r40197:40311 ^/trunk/blender
[blender.git] / source / blender / editors / space_console / console_ops.c
1 /*
2  * $Id$
3  *
4  * ***** BEGIN GPL LICENSE BLOCK *****
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software Foundation,
18  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19  *
20  * Contributor(s): Campbell Barton
21  *
22  * ***** END GPL LICENSE BLOCK *****
23  */
24
25 /** \file blender/editors/space_console/console_ops.c
26  *  \ingroup spconsole
27  */
28
29
30 #include <stdlib.h>
31 #include <string.h>
32 #include <ctype.h> /* ispunct */
33 #include <sys/stat.h>
34
35 #include "MEM_guardedalloc.h"
36
37 #include "DNA_userdef_types.h"
38
39 #include "BLI_blenlib.h"
40 #include "BLI_dynstr.h"
41 #include "BLI_utildefines.h"
42
43 #include "BKE_context.h"
44 #include "BKE_text.h" /* only for character utility funcs */
45
46 #include "WM_api.h"
47 #include "WM_types.h"
48
49 #include "UI_view2d.h"
50 #include "ED_screen.h"
51
52 #include "RNA_access.h"
53 #include "RNA_define.h"
54
55 #include "console_intern.h"
56
57 /* so when we type - the view scrolls to the bottom */
58 static void console_scroll_bottom(ARegion *ar)
59 {
60         View2D *v2d= &ar->v2d;
61         v2d->cur.ymin = 0.0;
62         v2d->cur.ymax =(float)v2d->winy;
63 }
64
65 static void console_textview_update_rect(SpaceConsole *sc, ARegion *ar)
66 {
67         View2D *v2d= &ar->v2d;
68
69         UI_view2d_totRect_set(v2d, ar->winx-1, console_textview_height(sc, ar));
70 }
71
72 static void console_select_offset(SpaceConsole *sc, const int offset)
73 {
74         sc->sel_start += offset;
75         sc->sel_end += offset;
76 }
77
78 void console_history_free(SpaceConsole *sc, ConsoleLine *cl)
79 {
80         BLI_remlink(&sc->history, cl);
81         MEM_freeN(cl->line);
82         MEM_freeN(cl);
83 }
84 void console_scrollback_free(SpaceConsole *sc, ConsoleLine *cl)
85 {
86         BLI_remlink(&sc->scrollback, cl);
87         MEM_freeN(cl->line);
88         MEM_freeN(cl);
89 }
90
91 static void console_scrollback_limit(SpaceConsole *sc)
92 {
93         int tot;
94         
95         if (U.scrollback < 32) U.scrollback= 256; // XXX - save in user defaults
96         
97         for(tot= BLI_countlist(&sc->scrollback); tot > U.scrollback; tot--)
98                 console_scrollback_free(sc, sc->scrollback.first);
99 }
100
101 static ConsoleLine * console_history_find(SpaceConsole *sc, const char *str, ConsoleLine *cl_ignore)
102 {
103         ConsoleLine *cl;
104
105         for(cl= sc->history.last; cl; cl= cl->prev) {
106                 if (cl==cl_ignore)
107                         continue;
108
109                 if(strcmp(str, cl->line)==0)
110                         return cl;
111         }
112
113         return NULL;
114 }
115
116 /* return 0 if no change made, clamps the range */
117 static int console_line_cursor_set(ConsoleLine *cl, int cursor)
118 {
119         int cursor_new;
120         
121         if(cursor < 0)                          cursor_new= 0;
122         else if(cursor > cl->len)       cursor_new= cl->len;
123         else                                            cursor_new= cursor;
124         
125         if(cursor_new == cl->cursor)
126                 return 0;
127         
128         cl->cursor= cursor_new;
129         return 1;
130 }
131
132 static char cursor_char(ConsoleLine *cl)
133 {
134         /* assume cursor is clamped */
135         return cl->line[cl->cursor];
136 }
137
138 static char cursor_char_prev(ConsoleLine *cl)
139 {
140         /* assume cursor is clamped */
141         if(cl->cursor <= 0)
142                 return '\0';
143
144         return cl->line[cl->cursor-1];
145 }
146
147 #if 0 // XXX unused 
148 static char cursor_char_next(ConsoleLine *cl)
149 {
150         /* assume cursor is clamped */
151         if(cl->cursor + 1 >= cl->len)
152                 return '\0';
153
154         return cl->line[cl->cursor+1];
155 }
156
157 static void console_lb_debug__internal(ListBase *lb)
158 {
159         ConsoleLine *cl;
160
161         printf("%d: ", BLI_countlist(lb));
162         for(cl= lb->first; cl; cl= cl->next)
163                 printf("<%s> ", cl->line);
164         printf("\n");
165
166 }
167
168 static void console_history_debug(const bContext *C)
169 {
170         SpaceConsole *sc= CTX_wm_space_console(C);
171
172         
173         console_lb_debug__internal(&sc->history);
174 }
175 #endif
176
177 static ConsoleLine *console_lb_add__internal(ListBase *lb, ConsoleLine *from)
178 {
179         ConsoleLine *ci= MEM_callocN(sizeof(ConsoleLine), "ConsoleLine Add");
180         
181         if(from) {
182                 ci->line= BLI_strdup(from->line);
183                 ci->len= strlen(ci->line);
184                 ci->len_alloc= ci->len;
185                 
186                 ci->cursor= from->cursor;
187                 ci->type= from->type;
188         } else {
189                 ci->line= MEM_callocN(64, "console-in-line");
190                 ci->len_alloc= 64;
191                 ci->len= 0;
192         }
193         
194         BLI_addtail(lb, ci);
195         return ci;
196 }
197
198 static ConsoleLine *console_history_add(const bContext *C, ConsoleLine *from)
199 {
200         SpaceConsole *sc= CTX_wm_space_console(C);
201         
202         return console_lb_add__internal(&sc->history, from);
203 }
204
205 #if 0 /* may use later ? */
206 static ConsoleLine *console_scrollback_add(const bContext *C, ConsoleLine *from)
207 {
208         SpaceConsole *sc= CTX_wm_space_console(C);
209         
210         return console_lb_add__internal(&sc->scrollback, from);
211 }
212 #endif
213
214 static ConsoleLine *console_lb_add_str__internal(ListBase *lb, char *str, int own)
215 {
216         ConsoleLine *ci= MEM_callocN(sizeof(ConsoleLine), "ConsoleLine Add");
217         if(own)         ci->line= str;
218         else            ci->line= BLI_strdup(str);
219         
220         ci->len = ci->len_alloc = strlen(str);
221         
222         BLI_addtail(lb, ci);
223         return ci;
224 }
225 ConsoleLine *console_history_add_str(SpaceConsole *sc, char *str, int own)
226 {
227         return console_lb_add_str__internal(&sc->history, str, own);
228 }
229 ConsoleLine *console_scrollback_add_str(SpaceConsole *sc, char *str, int own)
230 {
231         ConsoleLine *ci= console_lb_add_str__internal(&sc->scrollback, str, own);
232         console_select_offset(sc, ci->len + 1);
233         return ci;
234 }
235
236 ConsoleLine *console_history_verify(const bContext *C)
237 {
238         SpaceConsole *sc= CTX_wm_space_console(C);
239         ConsoleLine *ci= sc->history.last;
240         if(ci==NULL)
241                 ci= console_history_add(C, NULL);
242         
243         return ci;
244 }
245
246
247 static void console_line_verify_length(ConsoleLine *ci, int len)
248 {
249         /* resize the buffer if needed */
250         if(len >= ci->len_alloc) {
251                 int new_len= len * 2; /* new length */
252                 char *new_line= MEM_callocN(new_len, "console line");
253                 memcpy(new_line, ci->line, ci->len);
254                 MEM_freeN(ci->line);
255                 
256                 ci->line= new_line;
257                 ci->len_alloc= new_len;
258         }
259 }
260
261 static int console_line_insert(ConsoleLine *ci, char *str)
262 {
263         int len = strlen(str);
264         
265         if(len>0 && str[len-1]=='\n') {/* stop new lines being pasted at the end of lines */
266                 str[len-1]= '\0';
267                 len--;
268         }
269
270         if(len==0)
271                 return 0;
272         
273         console_line_verify_length(ci, len + ci->len);
274         
275         memmove(ci->line+ci->cursor+len, ci->line+ci->cursor, (ci->len - ci->cursor)+1);
276         memcpy(ci->line+ci->cursor, str, len);
277         
278         ci->len += len;
279         ci->cursor += len;
280         
281         return len;
282 }
283
284 /* static funcs for text editing */
285
286 /* similar to the text editor, with some not used. keep compatible */
287 static EnumPropertyItem move_type_items[]= {
288         {LINE_BEGIN, "LINE_BEGIN", 0, "Line Begin", ""},
289         {LINE_END, "LINE_END", 0, "Line End", ""},
290         {PREV_CHAR, "PREVIOUS_CHARACTER", 0, "Previous Character", ""},
291         {NEXT_CHAR, "NEXT_CHARACTER", 0, "Next Character", ""},
292         {PREV_WORD, "PREVIOUS_WORD", 0, "Previous Word", ""},
293         {NEXT_WORD, "NEXT_WORD", 0, "Next Word", ""},
294         {0, NULL, 0, NULL, NULL}};
295
296 static int move_exec(bContext *C, wmOperator *op)
297 {
298         ConsoleLine *ci= console_history_verify(C);
299         
300         int type= RNA_enum_get(op->ptr, "type");
301         int done= 0;
302         
303         switch(type) {
304         case LINE_BEGIN:
305                 done= console_line_cursor_set(ci, 0);
306                 break;
307         case LINE_END:
308                 done= console_line_cursor_set(ci, INT_MAX);
309                 break;
310         case PREV_CHAR:
311                 done= console_line_cursor_set(ci, ci->cursor-1);
312                 break;
313         case NEXT_CHAR:
314                 done= console_line_cursor_set(ci, ci->cursor+1);
315                 break;
316
317         /* - if the character is a delimiter then skip delimiters (including white space)
318          * - when jump over the word */
319         case PREV_WORD:
320                 while(text_check_delim(cursor_char_prev(ci)))
321                         if(console_line_cursor_set(ci, ci->cursor-1)==FALSE)
322                                 break;
323
324                 while(text_check_delim(cursor_char_prev(ci))==FALSE)
325                         if(console_line_cursor_set(ci, ci->cursor-1)==FALSE)
326                                 break;
327
328                 /* This isnt used for NEXT_WORD because when going back
329                  * its more useful to have the cursor directly after a word then whitespace */
330                 while(text_check_whitespace(cursor_char_prev(ci))==TRUE)
331                         if(console_line_cursor_set(ci, ci->cursor-1)==FALSE)
332                                 break;
333
334                 done= 1; /* assume changed */
335                 break;
336         case NEXT_WORD:
337                 while(text_check_delim(cursor_char(ci))==TRUE)
338                         if (console_line_cursor_set(ci, ci->cursor+1)==FALSE)
339                                 break;
340
341                 while(text_check_delim(cursor_char(ci))==FALSE)
342                         if (console_line_cursor_set(ci, ci->cursor+1)==FALSE)
343                                 break;
344
345                 done= 1; /* assume changed */
346                 break;
347         }
348         
349         if(done) {
350                 ScrArea *sa= CTX_wm_area(C);
351                 ARegion *ar= CTX_wm_region(C);
352
353                 ED_area_tag_redraw(sa);
354                 console_scroll_bottom(ar);
355         }
356
357
358         return OPERATOR_FINISHED;
359 }
360
361 void CONSOLE_OT_move(wmOperatorType *ot)
362 {
363         /* identifiers */
364         ot->name= "Move Cursor";
365         ot->description= "Move cursor position";
366         ot->idname= "CONSOLE_OT_move";
367         
368         /* api callbacks */
369         ot->exec= move_exec;
370         ot->poll= ED_operator_console_active;
371
372         /* properties */
373         RNA_def_enum(ot->srna, "type", move_type_items, LINE_BEGIN, "Type", "Where to move cursor to.");
374 }
375
376 #define TAB_LENGTH 4
377 static int insert_exec(bContext *C, wmOperator *op)
378 {
379         SpaceConsole *sc= CTX_wm_space_console(C);
380         ARegion *ar= CTX_wm_region(C);
381         ConsoleLine *ci= console_history_verify(C);
382         char *str= RNA_string_get_alloc(op->ptr, "text", NULL, 0);
383         int len;
384
385         // XXX, alligned tab key hack
386         if(str[0]=='\t' && str[1]=='\0') {
387                 len= TAB_LENGTH - (ci->cursor % TAB_LENGTH);
388                 MEM_freeN(str);
389                 str= MEM_mallocN(len + 1, "insert_exec");
390                 memset(str, ' ', len);
391                 str[len]= '\0';
392         }
393
394         len= console_line_insert(ci, str);
395         
396         MEM_freeN(str);
397         
398         if(len==0) {
399                 return OPERATOR_CANCELLED;
400         }
401         else {
402                 console_select_offset(sc, len);
403         }
404
405         console_textview_update_rect(sc, ar);
406         ED_area_tag_redraw(CTX_wm_area(C));
407
408         console_scroll_bottom(ar);
409
410         return OPERATOR_FINISHED;
411 }
412
413 static int insert_invoke(bContext *C, wmOperator *op, wmEvent *event)
414 {
415         // if(!RNA_property_is_set(op->ptr, "text")) { /* always set from keymap XXX */
416         if(!RNA_string_length(op->ptr, "text")) {
417                 /* if alt/ctrl/super are pressed pass through */
418                 if(event->ctrl || event->oskey) {
419                         return OPERATOR_PASS_THROUGH;
420                 }
421                 else {
422                         char str[2];
423                         str[0]= event->ascii;
424                         str[1]= '\0';
425
426                         RNA_string_set(op->ptr, "text", str);
427                 }
428         }
429         return insert_exec(C, op);
430 }
431
432 void CONSOLE_OT_insert(wmOperatorType *ot)
433 {
434         /* identifiers */
435         ot->name= "Insert";
436         ot->description= "Insert text at cursor position";
437         ot->idname= "CONSOLE_OT_insert";
438         
439         /* api callbacks */
440         ot->exec= insert_exec;
441         ot->invoke= insert_invoke;
442         ot->poll= ED_operator_console_active;
443
444         /* properties */
445         RNA_def_string(ot->srna, "text", "", 0, "Text", "Text to insert at the cursor position.");
446 }
447
448
449 static EnumPropertyItem delete_type_items[]= {
450         {DEL_NEXT_CHAR, "NEXT_CHARACTER", 0, "Next Character", ""},
451         {DEL_PREV_CHAR, "PREVIOUS_CHARACTER", 0, "Previous Character", ""},
452 //      {DEL_NEXT_WORD, "NEXT_WORD", 0, "Next Word", ""},
453 //      {DEL_PREV_WORD, "PREVIOUS_WORD", 0, "Previous Word", ""},
454         {0, NULL, 0, NULL, NULL}};
455
456 static int delete_exec(bContext *C, wmOperator *op)
457 {
458         SpaceConsole *sc= CTX_wm_space_console(C);
459         ARegion *ar= CTX_wm_region(C);
460         ConsoleLine *ci= console_history_verify(C);
461
462         const short type= RNA_enum_get(op->ptr, "type");
463         int done = 0;
464         
465         if(ci->len==0) {
466                 return OPERATOR_CANCELLED;
467         }
468         
469         switch(type) {
470         case DEL_NEXT_CHAR:
471                 if(ci->cursor < ci->len) {
472                         memmove(ci->line + ci->cursor, ci->line + ci->cursor+1, (ci->len - ci->cursor)+1);
473                         ci->len--;
474                         done= 1;
475                 }
476                 break;
477         case DEL_PREV_CHAR:
478                 if(ci->cursor > 0) {
479                         ci->cursor--; /* same as above */
480                         memmove(ci->line + ci->cursor, ci->line + ci->cursor+1, (ci->len - ci->cursor)+1);
481                         ci->len--;
482                         done= 1;
483                 }
484                 break;
485         }
486         
487         if(!done) {
488                 return OPERATOR_CANCELLED;
489         }
490         else {
491                 console_select_offset(sc, -1);
492         }
493
494         console_textview_update_rect(sc, ar);
495         ED_area_tag_redraw(CTX_wm_area(C));
496
497         console_scroll_bottom(ar);
498         
499         return OPERATOR_FINISHED;
500 }
501
502
503 void CONSOLE_OT_delete(wmOperatorType *ot)
504 {
505         /* identifiers */
506         ot->name= "Delete";
507         ot->description= "Delete text by cursor position";
508         ot->idname= "CONSOLE_OT_delete";
509         
510         /* api callbacks */
511         ot->exec= delete_exec;
512         ot->poll= ED_operator_console_active;
513
514         /* properties */
515         RNA_def_enum(ot->srna, "type", delete_type_items, DEL_NEXT_CHAR, "Type", "Which part of the text to delete.");
516 }
517
518
519 /* the python exec operator uses this */
520 static int clear_exec(bContext *C, wmOperator *op)
521 {
522         SpaceConsole *sc= CTX_wm_space_console(C);
523         ARegion *ar= CTX_wm_region(C);
524         
525         short scrollback= RNA_boolean_get(op->ptr, "scrollback");
526         short history= RNA_boolean_get(op->ptr, "history");
527         
528         /*ConsoleLine *ci= */ console_history_verify(C);
529         
530         if(scrollback) { /* last item in mistory */
531                 while(sc->scrollback.first)
532                         console_scrollback_free(sc, sc->scrollback.first);
533         }
534         
535         if(history) {
536                 while(sc->history.first)
537                         console_history_free(sc, sc->history.first);
538         }
539
540         console_textview_update_rect(sc, ar);
541         ED_area_tag_redraw(CTX_wm_area(C));
542
543         return OPERATOR_FINISHED;
544 }
545
546 void CONSOLE_OT_clear(wmOperatorType *ot)
547 {
548         /* identifiers */
549         ot->name= "Clear";
550         ot->description= "Clear text by type";
551         ot->idname= "CONSOLE_OT_clear";
552         
553         /* api callbacks */
554         ot->exec= clear_exec;
555         ot->poll= ED_operator_console_active;
556         
557         /* properties */
558         RNA_def_boolean(ot->srna, "scrollback", 1, "Scrollback", "Clear the scrollback history");
559         RNA_def_boolean(ot->srna, "history", 0, "History", "Clear the command history");
560 }
561
562
563
564 /* the python exec operator uses this */
565 static int history_cycle_exec(bContext *C, wmOperator *op)
566 {
567         SpaceConsole *sc= CTX_wm_space_console(C);
568         ARegion *ar= CTX_wm_region(C);
569
570         ConsoleLine *ci= console_history_verify(C); /* TODO - stupid, just prevernts crashes when no command line */
571         short reverse= RNA_boolean_get(op->ptr, "reverse"); /* assumes down, reverse is up */
572         int prev_len= ci->len;
573
574         /* keep a copy of the line above so when history is cycled
575          * this is the only function that needs to know about the double-up */
576         if(ci->prev) {
577                 ConsoleLine *ci_prev= (ConsoleLine *)ci->prev;
578
579                 if(strcmp(ci->line, ci_prev->line)==0)
580                         console_history_free(sc, ci_prev);
581         }
582
583         if(reverse) { /* last item in mistory */
584                 ci= sc->history.last;
585                 BLI_remlink(&sc->history, ci);
586                 BLI_addhead(&sc->history, ci);
587         }
588         else {
589                 ci= sc->history.first;
590                 BLI_remlink(&sc->history, ci);
591                 BLI_addtail(&sc->history, ci);
592         }
593
594         {       /* add a duplicate of the new arg and remove all other instances */
595                 ConsoleLine *cl;
596                 while((cl= console_history_find(sc, ci->line, ci)))
597                         console_history_free(sc, cl);
598
599                 console_history_add(C, (ConsoleLine *)sc->history.last);
600         }
601         
602         ci= sc->history.last;
603         console_select_offset(sc, ci->len - prev_len);
604
605         /* could be wrapped so update scroll rect */
606         console_textview_update_rect(sc, ar);
607         ED_area_tag_redraw(CTX_wm_area(C));
608
609         console_scroll_bottom(ar);
610
611         return OPERATOR_FINISHED;
612 }
613
614 void CONSOLE_OT_history_cycle(wmOperatorType *ot)
615 {
616         /* identifiers */
617         ot->name= "History Cycle";
618         ot->description= "Cycle through history";
619         ot->idname= "CONSOLE_OT_history_cycle";
620         
621         /* api callbacks */
622         ot->exec= history_cycle_exec;
623         ot->poll= ED_operator_console_active;
624         
625         /* properties */
626         RNA_def_boolean(ot->srna, "reverse", 0, "Reverse", "Reverse cycle history");
627 }
628
629
630 /* the python exec operator uses this */
631 static int history_append_exec(bContext *C, wmOperator *op)
632 {
633         SpaceConsole *sc= CTX_wm_space_console(C);
634         ARegion *ar= CTX_wm_region(C);
635         ScrArea *sa= CTX_wm_area(C);
636         ConsoleLine *ci= console_history_verify(C);
637         char *str= RNA_string_get_alloc(op->ptr, "text", NULL, 0); /* own this text in the new line, dont free */
638         int cursor= RNA_int_get(op->ptr, "current_character");
639         short rem_dupes= RNA_boolean_get(op->ptr, "remove_duplicates");
640         int prev_len= ci->len;
641
642         if(rem_dupes) {
643                 ConsoleLine *cl;
644
645                 while((cl= console_history_find(sc, ci->line, ci)))
646                         console_history_free(sc, cl);
647
648                 if(strcmp(str, ci->line)==0) {
649                         MEM_freeN(str);
650                         return OPERATOR_FINISHED;
651                 }
652         }
653
654         ci= console_history_add_str(sc, str, 1); /* own the string */
655         console_select_offset(sc, ci->len - prev_len);
656         console_line_cursor_set(ci, cursor);
657
658         ED_area_tag_redraw(sa);
659
660         console_scroll_bottom(ar);
661
662         return OPERATOR_FINISHED;
663 }
664
665 void CONSOLE_OT_history_append(wmOperatorType *ot)
666 {
667         /* identifiers */
668         ot->name= "History Append";
669         ot->description= "Append history at cursor position";
670         ot->idname= "CONSOLE_OT_history_append";
671         
672         /* api callbacks */
673         ot->exec= history_append_exec;
674         ot->poll= ED_operator_console_active;
675         
676         /* properties */
677         RNA_def_string(ot->srna, "text", "", 0, "Text", "Text to insert at the cursor position.");      
678         RNA_def_int(ot->srna, "current_character", 0, 0, INT_MAX, "Cursor", "The index of the cursor.", 0, 10000);
679         RNA_def_boolean(ot->srna, "remove_duplicates", 0, "Remove Duplicates", "Remove duplicate items in the history");
680 }
681
682
683 /* the python exec operator uses this */
684 static int scrollback_append_exec(bContext *C, wmOperator *op)
685 {
686         SpaceConsole *sc= CTX_wm_space_console(C);
687         ARegion *ar= CTX_wm_region(C);
688         ConsoleLine *ci;
689         
690         char *str= RNA_string_get_alloc(op->ptr, "text", NULL, 0); /* own this text in the new line, dont free */
691         int type= RNA_enum_get(op->ptr, "type");
692
693         console_history_verify(C);
694         
695         ci= console_scrollback_add_str(sc, str, 1); /* own the string */
696         ci->type= type;
697         
698         console_scrollback_limit(sc);
699
700         /* 'ar' can be null depending on the operator that runs
701          * rendering with invoke default for eg causes this */
702         if(ar) {
703                 console_textview_update_rect(sc, ar);
704         }
705
706         ED_area_tag_redraw(CTX_wm_area(C));
707         
708         return OPERATOR_FINISHED;
709 }
710
711 void CONSOLE_OT_scrollback_append(wmOperatorType *ot)
712 {
713         /* defined in DNA_space_types.h */
714         static EnumPropertyItem console_line_type_items[] = {
715                 {CONSOLE_LINE_OUTPUT,   "OUTPUT", 0, "Output", ""},
716                 {CONSOLE_LINE_INPUT,    "INPUT", 0, "Input", ""},
717                 {CONSOLE_LINE_INFO,             "INFO", 0, "Information", ""},
718                 {CONSOLE_LINE_ERROR,    "ERROR", 0, "Error", ""},
719                 {0, NULL, 0, NULL, NULL}};
720
721         /* identifiers */
722         ot->name= "Scrollback Append";
723         ot->description= "Append scrollback text by type";
724         ot->idname= "CONSOLE_OT_scrollback_append";
725         
726         /* api callbacks */
727         ot->exec= scrollback_append_exec;
728         ot->poll= ED_operator_console_active;
729         
730         /* properties */
731         RNA_def_string(ot->srna, "text", "", 0, "Text", "Text to insert at the cursor position.");      
732         RNA_def_enum(ot->srna, "type", console_line_type_items, CONSOLE_LINE_OUTPUT, "Type", "Console output type.");
733 }
734
735
736 static int copy_exec(bContext *C, wmOperator *UNUSED(op))
737 {
738         SpaceConsole *sc= CTX_wm_space_console(C);
739
740         DynStr *buf_dyn= BLI_dynstr_new();
741         char *buf_str;
742         
743         ConsoleLine *cl;
744         int sel[2];
745         int offset= 0;
746
747         ConsoleLine cl_dummy= {NULL};
748
749 #if 0
750         /* copy whole file */
751         for(cl= sc->scrollback.first; cl; cl= cl->next) {
752                 BLI_dynstr_append(buf_dyn, cl->line);
753                 BLI_dynstr_append(buf_dyn, "\n");
754         }
755 #endif
756
757         if(sc->sel_start == sc->sel_end)
758                 return OPERATOR_CANCELLED;
759
760         console_scrollback_prompt_begin(sc, &cl_dummy);
761
762         for(cl= sc->scrollback.first; cl; cl= cl->next) {
763                 offset += cl->len + 1;
764         }
765
766         if(offset==0) {
767                 console_scrollback_prompt_end(sc, &cl_dummy);
768                 return OPERATOR_CANCELLED;
769         }
770
771         offset -= 1;
772         sel[0]= offset - sc->sel_end;
773         sel[1]= offset - sc->sel_start;
774
775         for(cl= sc->scrollback.first; cl; cl= cl->next) {
776                 if(sel[0] <= cl->len && sel[1] >= 0) {
777                         int sta= MAX2(sel[0], 0);
778                         int end= MIN2(sel[1], cl->len);
779
780                         if(BLI_dynstr_get_len(buf_dyn))
781                                 BLI_dynstr_append(buf_dyn, "\n");
782
783                         BLI_dynstr_nappend(buf_dyn, cl->line + sta, end - sta);
784                 }
785
786                 sel[0] -= cl->len + 1;
787                 sel[1] -= cl->len + 1;
788         }
789
790         buf_str= BLI_dynstr_get_cstring(buf_dyn);
791
792         BLI_dynstr_free(buf_dyn);
793         WM_clipboard_text_set(buf_str, 0);
794
795         MEM_freeN(buf_str);
796
797         console_scrollback_prompt_end(sc, &cl_dummy);
798
799         return OPERATOR_FINISHED;
800 }
801
802 void CONSOLE_OT_copy(wmOperatorType *ot)
803 {
804         /* identifiers */
805         ot->name= "Copy to Clipboard";
806         ot->description= "Copy selected text to clipboard";
807         ot->idname= "CONSOLE_OT_copy";
808
809         /* api callbacks */
810         ot->poll= ED_operator_console_active;
811         ot->exec= copy_exec;
812
813         /* properties */
814 }
815
816 static int paste_exec(bContext *C, wmOperator *UNUSED(op))
817 {
818         SpaceConsole *sc= CTX_wm_space_console(C);
819         ARegion *ar= CTX_wm_region(C);
820         ConsoleLine *ci= console_history_verify(C);
821
822         char *buf_str= WM_clipboard_text_get(0);
823         char *buf_step, *buf_next;
824
825         if(buf_str==NULL)
826                 return OPERATOR_CANCELLED;
827
828         buf_step= buf_str;
829
830         while((buf_next=buf_step) && buf_next[0] != '\0') {
831                 buf_step= strchr(buf_next, '\n');
832                 if(buf_step) {
833                         *buf_step= '\0';
834                         buf_step++;
835                 }
836
837                 if(buf_next != buf_str) {
838                         WM_operator_name_call(C, "CONSOLE_OT_execute", WM_OP_EXEC_DEFAULT, NULL);
839                         ci= console_history_verify(C);
840                 }
841
842                 console_select_offset(sc, console_line_insert(ci, buf_next));
843         }
844
845         MEM_freeN(buf_str);
846
847         console_textview_update_rect(sc, ar);
848         ED_area_tag_redraw(CTX_wm_area(C));
849
850         console_scroll_bottom(ar);
851
852         return OPERATOR_FINISHED;
853 }
854
855 void CONSOLE_OT_paste(wmOperatorType *ot)
856 {
857         /* identifiers */
858         ot->name= "Paste from Clipboard";
859         ot->description= "Paste text from clipboard";
860         ot->idname= "CONSOLE_OT_paste";
861
862         /* api callbacks */
863         ot->poll= ED_operator_console_active;
864         ot->exec= paste_exec;
865
866         /* properties */
867 }
868
869 typedef struct SetConsoleCursor {
870         int sel_old[2];
871         int sel_init;
872 } SetConsoleCursor;
873
874 // TODO, cursor placement without selection
875 static void set_cursor_to_pos(SpaceConsole *sc, ARegion *ar, SetConsoleCursor *scu, int mval[2], int UNUSED(sel))
876 {
877         int pos;
878         pos= console_char_pick(sc, ar, mval);
879
880         if(scu->sel_init == INT_MAX) {
881                 scu->sel_init= pos;
882                 sc->sel_start = sc->sel_end = pos;
883                 return;
884         }
885
886         if (pos < scu->sel_init) {
887                 sc->sel_start = pos;
888                 sc->sel_end = scu->sel_init;
889         }
890         else if (pos > sc->sel_start) {
891                 sc->sel_start = scu->sel_init;
892                 sc->sel_end = pos;
893         }
894         else {
895                 sc->sel_start = sc->sel_end = pos;
896         }
897 }
898
899 static void console_modal_select_apply(bContext *C, wmOperator *op, wmEvent *event)
900 {
901         SpaceConsole *sc= CTX_wm_space_console(C);
902         ARegion *ar= CTX_wm_region(C);
903         SetConsoleCursor *scu= op->customdata;
904         int mval[2];
905         int sel_prev[2];
906
907         mval[0]= event->mval[0];
908         mval[1]= event->mval[1];
909
910         sel_prev[0]= sc->sel_start;
911         sel_prev[1]= sc->sel_end;
912         
913         set_cursor_to_pos(sc, ar, scu, mval, TRUE);
914
915         /* only redraw if the selection changed */
916         if(sel_prev[0] != sc->sel_start || sel_prev[1] != sc->sel_end) {
917                 ED_area_tag_redraw(CTX_wm_area(C));
918         }
919 }
920
921 static void set_cursor_exit(bContext *UNUSED(C), wmOperator *op)
922 {
923 //      SpaceConsole *sc= CTX_wm_space_console(C);
924         SetConsoleCursor *scu= op->customdata;
925
926         /*
927         if(txt_has_sel(text)) {
928                 buffer = txt_sel_to_buf(text);
929                 WM_clipboard_text_set(buffer, 1);
930                 MEM_freeN(buffer);
931         }*/
932
933         MEM_freeN(scu);
934 }
935
936 static int console_modal_select_invoke(bContext *C, wmOperator *op, wmEvent *event)
937 {
938         SpaceConsole *sc= CTX_wm_space_console(C);
939 //      ARegion *ar= CTX_wm_region(C);
940         SetConsoleCursor *scu;
941
942         op->customdata= MEM_callocN(sizeof(SetConsoleCursor), "SetConsoleCursor");
943         scu= op->customdata;
944
945         scu->sel_old[0]= sc->sel_start;
946         scu->sel_old[1]= sc->sel_end;
947
948         scu->sel_init = INT_MAX;
949
950         WM_event_add_modal_handler(C, op);
951
952         console_modal_select_apply(C, op, event);
953
954         return OPERATOR_RUNNING_MODAL;
955 }
956
957 static int console_modal_select(bContext *C, wmOperator *op, wmEvent *event)
958 {
959         switch(event->type) {
960                 case LEFTMOUSE:
961                 case MIDDLEMOUSE:
962                 case RIGHTMOUSE:
963                         set_cursor_exit(C, op);
964                         return OPERATOR_FINISHED;
965                 case MOUSEMOVE:
966                         console_modal_select_apply(C, op, event);
967                         break;
968         }
969
970         return OPERATOR_RUNNING_MODAL;
971 }
972
973 static int console_modal_select_cancel(bContext *C, wmOperator *op)
974 {
975         set_cursor_exit(C, op);
976         return OPERATOR_FINISHED;
977 }
978
979 void CONSOLE_OT_select_set(wmOperatorType *ot)
980 {
981         /* identifiers */
982         ot->name= "Set Selection";
983         ot->idname= "CONSOLE_OT_select_set";
984         ot->description= "Set the console selection";
985
986         /* api callbacks */
987         ot->invoke= console_modal_select_invoke;
988         ot->modal= console_modal_select;
989         ot->cancel= console_modal_select_cancel;
990         ot->poll= ED_operator_console_active;
991 }