/* Class : edittext SuperClass : selectable_statictext Author : Cathy Waite Turing Institute Date : Tue Jan 29 14:51:46 GMT 1991 Funtionality : Can display and format several lines of text. The text is scrollable. There is a selection point and/or a selection range than can be set explicity, or using the mouse. Can be edited interactively */ #include "edittext.h" #define NULL 0 #define EDIT_BUFFER_CHUNK_LENGTH 10 #define NEW_LINE 10 #define CARRIAGE_RETURN 13 #define TAB 9 #define SPACE 32 #define DELCHAR 127 #define DELWORD 23 #define DELLINE 21 /************************************************************************** Instance Variables ***************************************************************************/ boolean editable = true; /* Wether or not the text is editable */ /* [T h e C a t s a t o NULL NULL NULL NULL n t h e m a t ] ^ ^ edit_pos rest_index (generally also selection_point) |---------------| rest_length /* The position of the start of the edit buffer in the value array */ int edit_pos = 0; /* The position of the first charracter after the editbuffer (i.e. the */ /* position of the other half of the sting */ int rest_index = 0; /* The length of the "other half" or the part of the string that comes */ /* after the edit_buffer */ int rest_length = 0; /************************************************************************ Routines to manipulate the Edit Buffer *************************************************************************/ /* Returns the value without the edit_buffer. Does not change the */ /* value or the position ofthe editbuffer */ char *getvalue() { return(append(headinterval(value,edit_pos),tailinterval(value,rest_index))); } /* Moves the buffer to the end of the value. This is necessary before */ /* the edit_buffer can be moved to a new position. */ /* Perhaps it is only nevessary when the new edit buffer overlaps with */ /* the old one ? */ /* dont't recompute edit map as this is done as part of a larger move, display text recomputed after it ... */ void close_gap() { char *rest_of_value, *buffer; if (rest_length != 0) /* i.e. buffer not already at end */ { buffer = string(rest_index-edit_pos); rest_of_value = getinterval(value,rest_index,rest_length); putinterval(value,edit_pos,rest_of_value); edit_pos = edit_pos+ rest_length; putinterval(value,edit_pos, buffer); rest_index = length(value); rest_length = 0; } } void set_edit_pos(int new_pos) { char *rest; int old_pos = edit_pos; if (new_pos == edit_pos) return; /* If new pos is beyond edit buffer, compensate for legnth of edit buffer */ if (new_pos > edit_pos) new_pos -= rest_index - edit_pos; close_gap(); /* should this subtract the edit_buffer from new_pos ? */ /* Edit pos is currently end of text i.e. the maximum pos allowed */ if ((new_pos >= 0) && (new_pos < edit_pos)) { rest_length = edit_pos - new_pos; edit_pos = new_pos; rest = getinterval(value,edit_pos,rest_length); rest_index = length(value) - rest_length; putinterval(value,rest_index,rest); putinterval(value,edit_pos,string(rest_index-edit_pos)); } /* else : leave edit buffer and edit_pos at last point */ /* Need to compute from min of old edit pos and new edit pos line */ compute_updated_display_map(index_to_selection(min(edit_pos,old_pos))[LINE_NO]); } /* Add another chunk to the edit buffer, as it has all been filled */ void grow_edit_buffer() { value = append(append(headinterval(value,edit_pos), string(EDIT_BUFFER_CHUNK_LENGTH)), getinterval(value,rest_index,rest_length)); rest_index = edit_pos + EDIT_BUFFER_CHUNK_LENGTH; compute_updated_display_map((index_to_selection(edit_pos))[LINE_NO]); } /* Inserts the edit_buffer at the current selection point */ void make_editable() { edit_pos = length(value); value = append(value,string(EDIT_BUFFER_CHUNK_LENGTH)); rest_index = length(value); rest_length = 0; set_edit_pos(selection_to_index(selection_point)); compute_display_map(); } /* Removes the edit buffer from the value */ void non_editable() { value = getvalue(); compute_updated_display_map((index_to_selection(edit_pos))[LINE_NO]); } /*********************************************************************** Routines to delete part of the text ***********************************************************************/ /* Deltes backwards "no" numberof chars from the selection_point */ void old_del_backwards_to(int new_index) { int i,line; selection old_selection_point = selection_point; if (new_index < 0) return; for (i = edit_pos; i >= new_index; i--) value[i] = NULL; edit_pos = new_index; selection_point = index_to_selection(edit_pos); line = selection_point[LINE_NO]; if (line != old_selection_point[LINE_NO]) { compute_updated_display_map(line); paint_text_from_line(line); /* Scroll into view if required */ if (line < display_top_line) set_top_line(line); } else { gsave(); clip_textarea(); if (justification == /Left) { /* Undraw range by selecting in background */ select_part_line(line,selection_point[OFFSET],END,BG); /* Draw from new char to end of line */ draw_part_text_line(line,selection_point[OFFSET],END,FG); } else { select_line(line,BG); draw_text_line(line,FG); } grestore(); } } /* Deletes backwards "no" numberof chars from the selection_point */ void del_backwards_to(int new_index) { int i,line; selection old_selection_point = selection_point; int next_line; if (new_index < 0) return; for (i = edit_pos; i >= new_index; i--) value[i] = NULL; edit_pos = new_index; selection_point = index_to_selection(edit_pos); line = selection_point[LINE_NO]; next_line = MAPLINE(min(total_display_lines,selection_point[LINE_NO]+1)); compute_updated_display_map(line); if ((line != old_selection_point[LINE_NO]) || (next_line != MAPLINE(min(total_display_lines,selection_point[LINE_NO]+1)))) { paint_text_from_line(line); /* Scroll into view if required */ if (line < display_top_line) set_top_line(line); } else { gsave(); clip_textarea(); if (justification == /Left) { /* Undraw range by selecting in background */ select_part_line(line,selection_point[OFFSET],END,BG); /* Draw from new char to end of line */ draw_part_text_line(line,selection_point[OFFSET],END,FG); } else { select_line(line,BG); draw_text_line(line,FG); } grestore(); } } void del_char() { del_backwards_to(edit_pos-1); } void del_word() { int start_word = start_of_word_at(max(0,edit_pos - 1)); del_backwards_to(start_word); } void del_line() { int start_line = MAPLINE(start_of_line_at(selection_point[LINE_NO])); del_backwards_to(start_line); } /*********************************************************************** Routines to insert chracters or strings into the text ***********************************************************************/ void paint_changes_from(int *old_map, int line) { gsave(); clip_textarea(); line = max(display_top_line,line); while (line <= display_bottom_line) { if (MAPLINE(line) != old_map[line<<1]) { DEBUG("Drawing line %",[line]); select_line(line-1,BG); /* Draw from new char to end of line */ draw_text_line(line-1,FG); } line++; } grestore(); } void insert_key(char ch) { int line = selection_point[LINE_NO]; char *char_line; int start_line; value[edit_pos] = ch; edit_pos = edit_pos+1; selection_point = index_to_selection(edit_pos); /* If the buffer is full, add to it */ if (edit_pos == rest_index) grow_edit_buffer(); /* Need to test whether line needs to be wrapped*/ char_line = getinterval(value,MAPLINE(line),MAPLENGTH(line)); if ((text_width(char_line) <= textarea_width()) && (ch != NEW_LINE)) { if (is_word_space(ch)) { start_line = MAPLINE(line); compute_updated_display_map(max(0 ,line-1)); if (start_line != MAPLINE(line)) { paint_text_from_line(max(0,line-1)); return; } } /* Just redraw part of line */ gsave(); clip_textarea(); if (justification == /Left) { /* Undraw range by selecting in background */ select_part_line(line,max(0,selection_point[OFFSET]-1),END,BG); /* Draw from new char to end of line */ draw_part_text_line(line,max(0,selection_point[OFFSET]-1),END,FG); } else { select_line(line,BG); draw_text_line(line,FG); } grestore(); } else { compute_updated_display_map(line); paint_text_from_line(line); /* Scroll into viiw if required */ if (selection_point[LINE_NO] > display_bottom_line) scroll_down_line(); } } void tmpinsert_key(char ch) { int line = selection_point[LINE_NO]; /* int *old_map = (int *)copy((any *)display_map, array(length(display_map))); */ int line_index,line_after; value[edit_pos] = ch; edit_pos = edit_pos+1; /* If the buffer is full, add to it */ if (edit_pos == rest_index) grow_edit_buffer(); line_index = MAPLINE(line); line_after = MAPLINE(min(total_display_lines,line+1)); /* Recompute all that could be change (i.e. from previous line) */ /* compute_updated_display_map(max(0,line-1));*/ selection_point = index_to_selection(edit_pos); /* Draw only as much as required */ /* If start of line has changed, need to redraw from previous line */ if (line_index != MAPLINE(line)) paint_text_from_line(line-1); else if (line_after != MAPLINE(min(total_display_lines,line+1))) paint_text_from_line(line); else { /* Just redraw part of line */ gsave(); clip_textarea(); if (justification == /Left) { /* Undraw range by selecting in background */ select_part_line(line,selection_point[OFFSET]-1,END,BG); /* Draw from new char to end of line */ draw_part_text_line(line,selection_point[OFFSET]-1,END,FG); } else { select_line(line,BG); draw_text_line(line,FG); } grestore(); } if (selection_point[LINE_NO] > display_bottom_line) scroll_down_line(); } /* This does not use the edit buffer, it just appends the strings together */ void insert_string(char *new_string) { int added_length = length(new_string); int old_line = selection_point[LINE_NO]; if (added_length == 0) return; /* Insert string after current edit_pos */ value = append( append(headinterval(value,edit_pos),new_string), getinterval(value,edit_pos,length(value)-edit_pos) ); edit_pos += added_length; rest_index += added_length; compute_updated_display_map(old_line); selection_point = index_to_selection(edit_pos); reset_range(); paint_text_from_line(old_line); } void align_with_previous() { int start,ch; int prev_line = selection_point[LINE_NO] -1; if (prev_line >= 0) { draw_cursor(false); start = MAPLINE(prev_line); while( ( (ch = value[start++]) == TAB) || (ch == SPACE)) insert_key(ch); } } /*********************************************************************** Routines to cut and paste selections ***********************************************************************/ char *clipboard = ""; void clear_selection() { gexec(/do_clear_selection); } void do_clear_selection() { int start_index,end_index; int amount,i; if (!editable) return; start_index = selection_to_index(start_selection); end_index = selection_to_index(end_selection); if (start_index == end_index) return; draw_cursor(false); if (start_index == edit_pos) { /* Jump over edit buffer and start at "rest_index"*/ amount = end_index - rest_index; rest_index = end_index; rest_length = rest_length - amount; /* Do not need to change edit_pos */ } else { /* Edit buffer will be at end of selection ... */ edit_pos = start_index; selection_point = start_selection; } for (i=start_index;i<=end_index; i++) value[i] = NULL; reset_range(); compute_updated_display_map(start_selection[LINE_NO]); paint_text_from_line(start_selection[LINE_NO]); } void copy_selection() { gexec(/do_copy_selection); } void do_copy_selection() { int start_index,end_index,amount; char *copy_string; if ((!editable) || (!selection_range())) return; start_index = selection_to_index(start_selection); end_index = selection_to_index(end_selection); if (start_index == edit_pos) { /* Jump over edit buffer and start at "rest_index"*/ amount = end_index - rest_index; copy_string = string(amount); copy(getinterval(value,rest_index,amount),copy_string); } else { /* Edit buffer will be at end of selection ... */ amount = end_index - start_index - (rest_index-edit_pos); copy_string = string(amount); copy(getinterval(value,start_index,amount),copy_string); } clipboard = copy_string; } void paste_selection() { gexec(/do_paste_selection); } void do_paste_selection() { if (!editable) return; if (selection_range()) do_clear_selection(); draw_cursor(false); insert_string(clipboard); } void cut_selection() { gexec(/do_cut_selection); } void do_cut_selection() { if ((!editable) || (!selection_range())) return; draw_cursor(false); do_copy_selection(); do_clear_selection(); } char *clipboard_contents() { return(clipboard); } /*********************************************************************** Sub-classed Routines that are differnt due to the Edit Buffer **********************************************************************/ /* These are routines to move the caret, they are different as they may move several places at once to junmp over the edit_buffer */ /* Returns the index of the end of word at pos. Treats multiple */ /* spaces as a word */ int end_of_word_at(int pos) { /* Jump over edit buffer*/ if (pos == edit_pos) pos = rest_index; return(super.end_of_word_at(pos)); } /* Move Left should be the same as the election point ishould always */ /* be to the left of the edit buffer, so not moving over it */ void move_right_over_buffer() { draw_cursor(false); reset_range(); if (rest_length != 0) set_selection(index_to_selection(rest_index+1)); draw_cursor(true); } void do_move_to(name place) { if (editable && (place == /right)) gexec(/move_right_over_buffer); else super.do_move_to(place); } /* Due to edit buffer, ficiing dmage is not just a case of drawing the */ /* character before and after the selction point. It should be before */ /* and rest_index (i.e. first character aftereditbuffer */ void fix_caret_damage() { if (editable) { gsave(); clip_textarea(); draw_part_text_line(selection_point[LINE_NO], max(0,selection_point[OFFSET]-1), min(MAPLENGTH(selection_point[LINE_NO]), index_to_selection(rest_index)[OFFSET]+1), FG); grestore(); } else super.fix_caret_damage(); } /* Moves the edit buffer to the new position, then sets the selection point */ void set_selection(selection new_selection) { if (editable) { set_edit_pos(selection_to_index(new_selection)); super.set_selection(index_to_selection(edit_pos)); } else super.set_selection(new_selection); } /* Changes as if they swap sapces the edit buffer is moved, and the end selection must have the length of the edit buffer added to it */ void normalize_selection() { selection tmp; if (gt_range(start_selection,end_selection)) { set_selection(end_selection); tmp = start_selection; start_selection = end_selection; end_selection = tmp; end_selection = index_to_selection(selection_to_index(end_selection)+rest_index-edit_pos); } } /* Adds the edit buffer, if the value is editable */ void set_value(char *new_value) { super.set_value(new_value); if (editable) make_editable(); } /* Adds the new_string to the "other half" of the edit buffer. The edit buffer will be moved to the appropraite place with the set_position in super.append_string */ void append_string(char *new_string) { if (editable) rest_length += length(new_string); super.append_string(new_string); } void set_editable(boolean flag) { if (flag == editable) return; softpromote(/editable, flag); if (editable) make_editable(); else non_editable(); } /* Called each time a key is pressed, and the text object has the focus */ void do_onkey(int key) { if (!editable) return; if (selection_range()) do_clear_selection(); draw_cursor(false); switch(key) { case CARRIAGE_RETURN : insert_key(NEW_LINE); break; case NEW_LINE : insert_key(NEW_LINE); align_with_previous(); break; case DELCHAR : del_char(); break; case DELWORD : del_word(); break; case DELLINE : del_line(); break; default : insert_key(key); } draw_cursor(true); } void onkey(int key) { gexeca(/do_onkey,[key]); } void OnInit() { super.OnInit(); set_editable(true); }