/* Class : selectable_statictext SuperClass : scrollable_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. */ #include #include "track.h" #include "selectable_statictext.h" #include "extensions.h" /* Whether or not selections are made visible */ boolean visible_selection = true; persist(visible_selection) /* The position of the insertion point. This is a pair [line_number,offset_from_start_of_line] */ selection selection_point = [0,0]; /* The start and end of the selection range. */ selection start_selection = [0,0]; selection end_selection = [0,0]; /* Used to determine the direction of the selection, and thus what part to undraw, and which to draw when the selection is extended */ boolean prev_forward; /* Flags to indicate start and end of lines, defined in selectable_statictext.h*/ /* #define START -1 #define END -1 */ /*********************************************************************** Internal Conversion Routines ***********************************************************************/ /* Returns the horizonal position in the line of a selection point */ int xpos_from_selection(selection curr_selection) { char *str,*line; if (value == "") return(margin); setfont(textfont); line = getinterval(value,MAPLINE(curr_selection[LINE_NO]),MAPLENGTH(curr_selection[LINE_NO])); str = headinterval(line,curr_selection[OFFSET]); return(margin+text_width(str)+justification_indent(line)); } /* Used by the kernel "stringpos" routine to return the index of the */ /* nearest character to position x */ /* Used instead of the kernel version as it uses text_width which takes into */ /* account any tabs that may be in the line, and justfication */ int stringpos_bin(char *str, float x) { int len, mid; char *s; float width; if ((len = length(str)) == 0) return 0; if (len == 1) return (x > (text_width(str)/2)) ? 1 : 0; if (x < (width = text_width(s = getinterval(str,0,mid = len/2)))) return stringpos_bin(s,x); else return mid + stringpos_bin(getinterval(str,mid,len-mid),x-width); } /* Returns a selection point from a co-ordinate */ selection char_from_point(float xpos, float ypos) { int line; char *from; setfont(textfont); line = max(0,idiv(cvi(h-margin-ypos),cvi(fontheight(textfont)+line_gap))+ display_top_line); if (line > display_bottom_line) return ([display_bottom_line,MAPLENGTH(display_bottom_line)]); from = getinterval(value,MAPLINE(line),MAPLENGTH(line)); return([line, stringpos(textfont,from,xpos-margin-justification_indent(from))]); } /* These routines are used to convert selection points to indexes (or positions in the value) and back again. The index is useful as it is the same regardless of the display map. So when the display map is changing, but a selection is to be maintained, the selection should be changed to an inndex before the display map is update, and converted back to a (new) selection point after the display map has been changed */ /* Routine to convert a selection into an index */ int selection_to_index(selection select) { return(MAPLINE(select[LINE_NO])+select[OFFSET]); } /* Routine to convert an index to a selection point */ selection index_to_selection(int ind) { int i; for(i = 1; i <= total_display_lines; i++) if (MAPLINE(i) > ind) return([i-1,ind-MAPLINE(i-1)]); return([total_display_lines,ind-MAPLINE(total_display_lines)]); } /*********************************************************************** Routines to Manipulate Ranges ***********************************************************************/ void reset_range() { start_selection = [0,0]; end_selection = [0,0]; } boolean gt_range(selection range1, selection range2) { if (range1[LINE_NO] == range2[LINE_NO]) return(range1[OFFSET] > range2[OFFSET]); else return(range1[LINE_NO] > range2[LINE_NO]); } boolean eq_range(selection range1, selection range2) { return ((range1[LINE_NO] == range2[LINE_NO]) && (range1[OFFSET] == range2[OFFSET])); } /* Returns true if there is currently a range rather than a point */ /* selected */ boolean selection_range() { return(not(eq_range(start_selection,end_selection))); } /* Ensures that the start_selection comes before the end_selection */ void normalize_selection() { selection tmp; if (gt_range(start_selection,end_selection)) { tmp = start_selection; start_selection = end_selection; end_selection = tmp; set_selection(start_selection); } } /*********************************************************************** Routines to Manipulate Text Structures (i.e .words ..) ***********************************************************************/ /* Returns the index of the start of the word at "pos". Treats */ /* multiple white space as a word */ int start_of_word_at(int pos) { boolean not_start = true; int eol = length(value); if (is_word_space(value[pos])) { while((pos < eol) && (pos >= 0) && (not_start)) { /* Word is spaces */ if ((is_word_space(value[pos])) || (value[pos] == 0)) pos--; else not_start = false; } not_start = true; } while((pos < eol) && (pos > 0) && (not_start)) { /* word is text */ if (is_word_space(value[pos])) not_start = false; else pos--; } if (!not_start) pos++; return(pos); } /* Returns the index of the end of word at pos. Treats multiple */ /* spaces as a word */ int end_of_word_at(int pos) { boolean not_end = true; int eol = length(value); if (is_word_space(value[pos])) { while((pos < eol) && (not_end)) { if (is_word_space(value[pos])) pos++; else not_end = false; } not_end = true; } while((pos < eol) && (not_end)) { if (is_word_space(value[pos])) not_end = false; else pos++; } return(pos); } /* Returns the display text line that the hard line "line" starts on */ int start_of_line_at(int line) { /* Look for previous REAL '\n' */ while((line > 0) && (value[MAPLINE(line)-1] != '\n')) line--; return(line); } /* Returns the display text line that the hard line "line" ends on */ int end_of_line_at(int line) { while((line < total_display_lines) && (value[MAPLINE(line)+MAPLENGTH(line)] != '\n')) line++; return(line); } /*********************************************************************** Routines to Redraw Part of the Text ***********************************************************************/ /* If start_offset == START draw from start of line if end_offset == END draw to end of line */ void draw_part_text_line(int line, int start_offset, int stop_offset,colortype textcolor) { int xpos; selection range = [line,start_offset]; if ((line >= display_top_line) && (line <= display_bottom_line) && (value != "")) { setcolor(textcolor); setfont(textfont); if (start_offset == START) { start_offset = 0; range[OFFSET] = 0; } xpos = xpos_from_selection(range); if (stop_offset == END) stop_offset = MAPLENGTH(line); moveto(xpos,ypos_from_line(line)+fontdescent(textfont)); text_show(getinterval(value,MAPLINE(line)+start_offset,stop_offset- start_offset)); } } void draw_text_line(int line,colortype textcolor) { char *str_line; if ((line >= display_top_line) && (line <= display_bottom_line)) { setcolor(textcolor); setfont(textfont); str_line = getinterval(value,MAPLINE(line),MAPLENGTH(line)); moveto(margin + justification_indent(str_line), ypos_from_line(line)+fontdescent(textfont)); text_show(str_line); } } /* Draws the text for the given rnage, which may extend over several lines */ void draw_text_range(selection start_range, selection end_range, colortype textcolor) { int i; setfont(textfont); setcolor(textcolor); gsave(); clip_textarea(); if (start_range[LINE_NO] == end_range[LINE_NO]) draw_part_text_line(start_range[LINE_NO],start_range[OFFSET],end_range[OFFSET],textcolor); else { draw_part_text_line(start_range[LINE_NO],start_range[OFFSET],END,textcolor); for( i=start_range[LINE_NO]+1; i= display_top_line) && (line <= display_bottom_line)) { range[OFFSET] = start_pos; if (start_pos == START) xpos = margin; else xpos = xpos_from_selection(range); range[OFFSET] = stop_pos; if (stop_pos == END) width = textarea_width + margin; else width = xpos_from_selection(range); rectpath(xpos,ypos_from_line(line), width - xpos, fontheight(textfont)+line_gap); setcolor(draw_color); fill(); } } /* Selects a whole (without updating the text */ void select_line(int line, colortype draw_color) { if ((line >= display_top_line) && (line <= display_bottom_line)) { setcolor(draw_color); rectpath(margin,ypos_from_line(line),textarea_width,fontheight(textfont)+line_gap); fill(); } } /* Selects a range by drawing in the given colour. the range can beoverseveral lines. It does not fix damage to the text, so this routine must be followed by draw_text_range. The colour determines wether the range is being highlighted or not */ void select_range(selection start_range, selection end_range, colortype draw_color) { int i; gsave(); clip_textarea(); if (start_range[LINE_NO] == end_range[LINE_NO]) select_part_line(start_range[LINE_NO],start_range[OFFSET],end_range[OFFSET],draw_color); else { select_part_line(start_range[LINE_NO],start_range[OFFSET],END,draw_color); for (i=start_range[LINE_NO]+1; i < end_range[LINE_NO]; i++) select_line(i,draw_color); select_part_line(end_range[LINE_NO],START,end_range[OFFSET],draw_color); } grestore(); } void fix_caret_damage() { /* Fix damge caused by undrawing caret, by drawing the character before and after the selection_point */ gsave(); clip_textarea(); draw_part_text_line(selection_point[LINE_NO], max(0,selection_point[OFFSET]-1), min(MAPLENGTH(selection_point[LINE_NO]), selection_point[OFFSET]+1),FG); grestore(); } void clip_cursorarea() { rectpath(OLCaretW/2, OLCaretH/2,boxarea_width - OLCaretW,h-OLCaretH); clip(); newpath(); } void draw_cursor(boolean flag) { int xpos, ypos; if (!visible_selection) return; if (selection_range()) { select_range(start_selection,end_selection,flag?selBGcolor:BG); draw_text_range(start_selection,end_selection,flag?selFGcolor:FG); } else { /* Ensure the selectionm point is visible */ if ((selection_point[LINE_NO] >= display_top_line) && (selection_point[LINE_NO] <= display_bottom_line)) { gsave(); clip_cursorarea(); xpos = xpos_from_selection(selection_point); ypos = ypos_from_line(selection_point[LINE_NO]); moveto(xpos,ypos+1); if (flag) /* Draw caret : colour depends on focus_stat focus_state == inactive or none => false focus_state == active => true */ OLCaret(!(focus_state() != /active)); else { undrawOLCaret(); fix_caret_damage(); } grestore(); } } } /*********************************************************************** Routines to Set Selections ***********************************************************************/ void set_selection(selection sel) { promote(/selection_point,sel); } void select_word(float xpos ,float ypos) { selection ch = char_from_point(xpos, ypos); /* [line, offset ] */ int ind = selection_to_index(ch); /* index into value string */ int start_word,end_word; start_word = start_of_word_at(ind); end_word = end_of_word_at(ind); /* now have start and end of word as indexes into value array. need them as selection ranges ... i.e. [LINE,offset] */ start_selection = index_to_selection(start_word); end_selection = index_to_selection(end_word); normalize_selection(); set_selection(start_selection); } void select_text_line(float xpos, float ypos) { selection ch = char_from_point(xpos,ypos); int line = ch[LINE_NO]; int end_line; start_selection = [start_of_line_at(line), 0]; end_line = end_of_line_at(line); end_selection = [end_line, MAPLENGTH(end_line)]; set_selection(start_selection); } void select_all() { start_selection = [0,0]; end_selection = [total_display_lines, MAPLENGTH(total_display_lines)]; set_selection(start_selection); } void do_selection(float xpos, float ypos) { switch( mouselevel()) { default : case 1 : break; case 2 : select_word(xpos, ypos); draw_cursor(true); break; case 3 : select_text_line(xpos, ypos); draw_cursor(true); break ; case 4 : select_all(); draw_cursor(true); break; } } void OnSelect() { float xpos = mouseevent.XLocation; float ypos = mouseevent.YLocation; if (focus_state() == /none) request_focus(); /* if cursor is not visible, no point doing calculations */ if (!visible_selection) return; /* Undraw Cursor */ draw_cursor(false); set_selection(char_from_point(xpos,ypos)); start_selection = selection_point; end_selection = start_selection; if (track_state == /mouse_click) { do_selection(xpos,ypos); draw_cursor(true); } else if (track_state == /mouse_drag) { prev_forward = true; track(/extend_selection_proc); normalize_selection(); /* If there is a selection, it will ahve already been drawn as part of the "extend_selection_proc" */ if (!selection_range()) draw_cursor(true); } } /* Return a range "rounded" to nearest range depending on click level */ selection round_range(selection old_range, boolean forward) { int ind; int line; switch( mouselevel()) { /* Will be drag and drop, but for now extend by char */ default : case 1 : return(old_range); case 2 : /* Word boundaries */ ind = selection_to_index(old_range); if (forward) return(index_to_selection(start_of_word_at(ind))); else return(index_to_selection(end_of_word_at(ind))); case 3 : /* line boundaries */ if (forward) return([start_of_line_at(old_range[LINE_NO]), 0]); else { line = end_of_line_at(old_range[LINE_NO]); return([line,MAPLENGTH(line)]); } case 4 : /* Whole text */ if (forward) return([0,0]); else return( [total_display_lines, MAPLENGTH(total_display_lines)]); } } void extend_selection(selection new_range) { boolean curr_forward; curr_forward = gt_range(new_range,start_selection); /* select_position(xpos,ypos);*/ /* Find appropraite "boundary" for seleciton level */ /* Commented out for now : not quite working */ /* new_range = round_range(new_range,curr_forward);*/ if (eq_range(new_range,end_selection)) return; if (prev_forward == curr_forward) { if (gt_range(new_range,end_selection)) { select_range(end_selection,new_range,curr_forward?selBGcolor:BG); draw_text_range(end_selection,new_range,curr_forward?selFGcolor:FG); } else { select_range(new_range,end_selection,curr_forward?BG:selBGcolor); draw_text_range(new_range,end_selection,curr_forward?FG:selFGcolor); } } else { if (gt_range(start_selection,new_range)) { select_range(new_range,start_selection,selBGcolor); draw_text_range(new_range,start_selection,selFGcolor); } else { select_range(start_selection,new_range,selBGcolor); draw_text_range(start_selection,new_range,selFGcolor); } if (gt_range(start_selection,end_selection)) { select_range(end_selection,start_selection,BG); draw_text_range(end_selection,start_selection,FG); } else { select_range(start_selection,end_selection,BG); draw_text_range(start_selection,end_selection,FG); } } prev_forward = curr_forward; end_selection = new_range; } void extend_selection_proc() { extend_selection(char_from_point(trackx,tracky)); } /* Adjust commented tou until it works properly */ void OnAdjust() { if (focus_state() == /none) request_focus(); /* if cursor is not visible, no point doing calculations */ if (!visible_selection) return; if (!selection_range()) draw_cursor(false); if (track_state == /mouse_click) extend_selection(char_from_point(mouseevent.XLocation,mouseevent.YLocation)); /* else if (track_state == /mouse_drag) { prev_forward = true; track(/extend_selection_proc); }*/ normalize_selection(); set_selection(start_selection); if (!selection_range()) draw_cursor(true); } /*********************************************************************** Focusing Routines ***********************************************************************/ void focus_change() { /* If there is a selection the undraw is not necessary, however as */ /* the focused and unfocused caret of differnt shapes, an undraw */ /* is necessary. */ if (!selection_range()) { /* Undraw cursor */ gexeca(/draw_cursor,[false]); /* Draw in new focus */ gexeca(/draw_cursor,[true]); } } /*********************************************************************** Routine to Manipulate Caret ***********************************************************************/ void do_move_to(name place) { int end_line; draw_cursor(false); reset_range(); switch(place) { default : case /start_text : set_selection([0,0]); if (display_top_line != 0) scroll_to_top(); break; case /end_text : set_selection([total_display_lines,MAPLENGTH(total_display_lines)]); if (display_bottom_line < total_display_lines) scroll_to_bottom(); break; case /left : if (selection_point[OFFSET] > 0) set_selection([selection_point[LINE_NO],selection_point[OFFSET]-1]); else if (selection_point[LINE_NO] > 0) { set_selection([selection_point[LINE_NO]-1, MAPLENGTH(selection_point[LINE_NO]-1)]); if (display_top_line > selection_point[LINE_NO]) scroll_up_line(); } break; case /right : if (selection_point[OFFSET] < MAPLENGTH(selection_point[LINE_NO])) set_selection([selection_point[LINE_NO],selection_point[OFFSET]+ 1]); else if (selection_point[LINE_NO] < total_display_lines) { set_selection([selection_point[LINE_NO]+1,0]); if (selection_point[LINE_NO] > display_bottom_line) scroll_down_line(); } break; case /up : if (selection_point[LINE_NO] > 0) set_selection([selection_point[LINE_NO]-1, min(selection_point[OFFSET], MAPLENGTH(selection_point[LINE_NO]-1))]); if (display_top_line > selection_point[LINE_NO]) scroll_up_line(); break; case /down : if (selection_point[LINE_NO] < total_display_lines) set_selection([selection_point[LINE_NO]+1, min(selection_point[OFFSET], MAPLENGTH(selection_point[LINE_NO]+1))]); if (selection_point[LINE_NO] > display_bottom_line) scroll_down_line(); break; case /start_word : set_selection(index_to_selection(start_of_word_at(max(0,selection_to_index(selection_point)-1)))); if (display_top_line > selection_point[LINE_NO]) scroll_up_line(); break; case /end_word : set_selection(index_to_selection(end_of_word_at(selection_to_index(selection_point)))); if (selection_point[LINE_NO] > display_bottom_line) scroll_down_line(); break; case /start_line : set_selection([start_of_line_at(selection_point[LINE_NO]),0]); if (display_top_line > selection_point[LINE_NO]) scroll_up_line(); break; case /end_line : end_line = end_of_line_at(selection_point[LINE_NO]); set_selection([end_line,MAPLENGTH(end_line)]); if (selection_point[LINE_NO] > display_bottom_line) scroll_down_line(); break; } draw_cursor(true); } void move_to(name place) { gexeca(/do_move_to,[place]); } /*********************************************************************** Methods from super classes extended to deal with selections ***********************************************************************/ void set_visible_selection(boolean flag) { if (flag && !visible_selection) { softpromote(/visible_selection,flag); gexeca(/draw_cursor,[true]); } if (!flag && visible_selection) { gexeca(/draw_cursor,[false]); softpromote(/visible_selection,flag); } } void paint_text_from_line(int line) { draw_cursor(false); super.paint_text_from_line(line); draw_cursor(true); } void compute_display_map_from(int line_no, int char_count, char *rest_of_text) { int start_index,stop_index,point_index; start_index = selection_to_index(start_selection); stop_index = selection_to_index(end_selection); point_index = selection_to_index(selection_point); super.compute_display_map_from(line_no, char_count, rest_of_text); start_selection = index_to_selection(start_index); end_selection = index_to_selection(stop_index); /*set_selection(index_to_selection(point_index));*/ /* Don't use set_selection here, as has not actualy changing the */ /* position of selection_point */ selection_point = index_to_selection(point_index); } /*********************************************************************** Routine to add a text to the end of the current value, scrolling to ensure it is in view ***********************************************************************/ void append_string(char *new_string) { int old_total = total_display_lines; value = append(value,new_string); compute_updated_display_map(old_total); gexeca(/paint_text_from_line,[old_total]); move_to(/end_text); } void set_value(char *new_string) { reset_range(); set_selection(start_selection); super.set_value(new_string); } void do_set_top_line(int new_top) { draw_cursor(false); super.do_set_top_line(new_top); draw_cursor(true); } void do_select_line_at(int line) { int end_line; if ((line >= 0) && (line <= total_display_lines)) { draw_cursor(false); start_selection = [start_of_line_at(line), 0]; end_line = end_of_line_at(line); end_selection = [end_line, MAPLENGTH(end_line)]; set_selection(start_selection); draw_cursor(true); if ((line < display_top_line) || (line > display_bottom_line)) set_top_line(line); } } void select_line_at(int line) { gexeca(/do_select_line_at,[line]); }