#include "../include/xcv_license.h" //PCH namespace Widgets95 {//-. //<-' /****************************** WIDGETS_95_EditText::draw() **********/ void ui::textbox::_draw() { _dump("-> DRAW"); box_interface::_draw(); //2019: Don't do this here. /** Find where to draw the _text **/ //update_substr_bounds(); _CLAMP_substr(); draw_substr(); draw_insertion_pt(); _dump("-> DRAW"); } void text_interface::_CLAMP_substr() { int len = char_size(); CLAMP(_substr_start,0,len); //CLAMP(_substr_end,0,len); CLAMP(_substr_end,_substr_start,len); } /********************************* WIDGETS_95_EditText::draw_substr() ****************/ void text_interface::draw_substr(int y) { //REMOVE ME //Change default to 0. Not -1. bool et_like = y==-1; y+=_y_off_top+ui_boxinnermarginx; //HACK bool basic = !_box_type; if(basic&&!et_like) y-=ui_boxinnermarginx+1; /** Find where to draw the _text **/ const int text_x = _x_lr+ui_boxinnermarginx; /** Find lower and upper selection bounds **/ const int sel_lo = std::min(_sel_start,_sel_end); const int sel_hi = std::max(_sel_start,_sel_end); const int sss = _substr_start; const int sse = _substr_end; const int tw = _tab; const char *text = _text.c_str(); /** Draw selection area dark **/ if(_sel_start!=_sel_end) { bool sel_empty = true; int sel_x_start = text_x; int i,sel_x_end = text_x; for(i=sss;i0) digits = std::max(digits,(int)(std::log10(_limits2)+1.5)); if(!digits) digits = 1; if(sign) digits++; ms = digits*_font._width['1']+ui_boxinnermarginx*2; ms+=2*ui_boxoutermarginx; if(spinner) ms+=spinner->span(); } } if(!ms) ms = ui_textbox_span-ui_textbox_name; //SAME AS dropdown::compact. if(_placement&(top|bottom)) //NEW { _x_lr = _x_rl = ui_boxoutermarginx; //Reminder: Want exact size given by ms. _w = ms; } else { //2022: This had included spacing //even with an empty name. It was //a problem that _update_area had //removed this spacing. int name_w = name_span(); if(name_w) name_w+=ui_namespacing; _x_lr = name_w; _x_rl = ui_boxoutermarginx; _w = _x_lr-ui_boxoutermarginx+ms; //HACK: For some reason in MM3D the param //boxes tend to be 1px wider than the zoom //boxes. _w-=1; } return *this; } void ui::textbox::_update_area() { //This could use more thought. //box_interface::_update_area(); int name_w = 0; if(0==(_placement&(top|bottom))) //HACK name_w = name_span(); int min_size = ui_min_text_span; if(_data_type==edit_int) min_size = ui_min_int_span; //NEW (May want to put name on top?) if(name_w) { _x_lr = std::max(_x_lr,name_w+ui_namespacing); } else { if(_x_lr>ui_boxoutermarginx) { switch(_data_type) //HACK { case edit_int: case edit_float: _w-=_x_lr; break; //??? } } if(_box_type) _x_lr = ui_boxoutermarginx; //HACK if(_alignment) { _w = std::max(_w,ui_textbox_span-ui_textbox_name); } else //UNFINISHED { //How to prop up with min size? } } _w = std::max(_w,_x_lr+min_size+_x_rl); int hack = _box_type?16:15; if(control*ch=first_child()) if(dynamic_cast(ch)) hack = 17; box_interface::_update_area(); //??? _h = _y_off_top+hack+_y_off_bot; //NEW /*SPINNER_UP_ON poses a problem for this. //NOTE: get_page_length needs to be nonzero. _h = std::max(_h,font.height+_y_off_top+_y_off_bot);*/ update_substr_bounds(); } /********************************** WIDGETS_95_EditText::set_int_val() ************/ void text_interface::_evaluate_limits() { if(!_has_limits||_data_type==ui_edit_text||!_data_type) return; double new_val = _float_val; if(_has_limits==limit_clamp) { /*** Clamp the new value to the existing limits ***/ CLAMP(new_val,_limits1,_limits2); } else if(_has_limits==limit_wrap) { /*** Clamp the value cyclically to the limits - that is, if the value exceeds the max, set it the the minimum, and conversely ***/ if(new_val<_limits1) new_val = _limits2; if(new_val>_limits2) new_val = _limits1; } ((ui::textbox*)this)->set_val(new_val); } void ui::textbox::_update_live() { //set_float_val //set_int_val { _evaluate_limits(); } //set_text { //_text = new_text; _substr_start = 0; _substr_end = char_size(); e::reset_caret(this); _sel_start = 0; _sel_end = 0; update_substr_bounds(); /* update_and_draw_text(); */ /** Update the spinner, if we have one **/ if(ui::spinner*s=spinner) { if(!s->_data_type) //NEW: plain text mode { s->set_text(_text); } else s->set_val(_float_val); } } } /********************************* WIDGETS_95_EditText::set_float_limits() *********/ ti &text_interface::limit(double l1, double l2, int limit_type) { if(_data_type>=edit_int) { if(si*s=spinner) { s->set_range(l1,l2); } if(l1>l2) std::swap(l1,l2); } _limits1 = l1; _limits2 = l2; _has_limits = limit_type; if(_data_type&&_data_type!=ui_edit_text) { double new_val = _float_val; CLAMP(new_val,l1,l2); ((ui::textbox*)this)->set_val(new_val); } return *this; } /**************************************** WIDGETS_95_EditText::mouse_over() ********/ bool text_interface::_mouse_over(bool state, int local_x, int local_y) { //HACK: Simulating noninteractive text? //NOTE: This is useful because no other control implements //wrapping text at this moment. if(!_can_activate) return true; if(state&&_box_type&&box_test(local_x,local_y)) { glutSetCursor(GLUT_CURSOR_TEXT); } else return true; return false; } /****************************** WIDGETS_95_EditText::mouse_down_handler() **********/ namespace textbox_found_ip { static ui::control *c = nullptr; static int x,y,meridian; } bool text_interface::_mouse_down_handler(int local_x, int local_y) { //HACK: Simulating noninteractive text? //NOTE: This is useful because no other control implements //wrapping text at this moment. if(!_can_activate) return true; _dump("-> MOUSE DOWN"); textbox_found_ip::c = this; textbox_found_ip::meridian = 0; //find_insertion_pt tests this. //if(!box_test(local_x,local_y)) return true; int tmp_insertion_pt = find_insertion_pt(local_x,local_y); if(-1==tmp_insertion_pt) { textbox_found_ip::c = nullptr; //if(ui) ui->deactivate_current_control(); return true; //return false; //2019 } //Need to wait until mouse_up_handler to be activated. //e::set_caret(this,_sel_start=_sel_end=tmp_insertion_pt); _sel_start = _sel_end = tmp_insertion_pt; //update_and_draw_text(); //Can wait until held_down/up. if(2==e.get_click()) //NEW { int se = _sel_end; _sel_start = find_word_break(se-1,-1); se = find_word_break(se+1,1); if(se>0&&se HELD DOWN"); int tmp_pt = find_insertion_pt(local_x,local_y,true); if(tmp_pt!=-1&&tmp_pt!=_sel_end) { _sel_end = tmp_pt; e::set_caret(this); update_and_draw_text(); } _dump("<- HELD DOWN"); return false; } /***************************** WIDGETS_95_EditText::mouse_up_handler() ******/ bool text_interface::_mouse_up_handler(int local_x, int local_y, bool inside) { if(this==textbox_found_ip::c) if(e::set_caret(this,_sel_start==_sel_end?_sel_end:-1)) { //TODO: Have to adjust these and call caret_callback somehow. //e.caret_x = local_x-_x_abs; e.caret_y = local_y-_y_abs; e.caret_x = textbox_found_ip::x; e.caret_y = textbox_found_ip::y; } redraw(); return false; } /****************************** WIDGETS_95_EditText::key_handler() **********/ inline void text_interface::_kh_erase(int start, int end) { if(!_enabled) return; CLAMP(start,0,char_size()); assert(start<=end); CLAMP(end,0,char_size()); if(start==end) return; e::set_edited(this); _text.erase(start,end-start); //e::set_caret(this,_sel_start=_sel_end=start); _sel_start = _sel_end = start; } bool text_interface::_key_handler(int key, int modifiers) { _dump("-> KEY HANDLER"); textbox_found_ip::meridian = 0; int len = char_size(); int leftmost = sel_min(), rightmost = sel_max(); //I've turned key into int so user/control code //can negate its keys to act like Unix Readline. //or vice versa when WIDGETS_95_READLINE_MODIFIERS is //defined at build time. history might be a //way to demonstrate this option. #ifdef WIDGETS_95_READLINE_MODIFIERS int rl_key = -key; #else int rl_key = +key; #endif int abs_key = std::abs(key); if(abs_key<=127) key = abs_key; if(GLUT_ACTIVE_ALT&modifiers) //2022: hotkeys? { if(rl_key>=0) return true; } //NEW: Normalizing key codes to //reduce frustration. if(modifiers&GLUT_ACTIVE_CTRL) { if(abs_key>='a'&&abs_key<='z' ||abs_key>='A'&&abs_key<='Z') { abs_key = ctrl(abs_key); rl_key = key<0?-abs_key:abs_key; } } if(iscntrl(abs_key)) switch(rl_key) { case +'\r': /* RETURN */ if(modifiers&GLUT_ACTIVE_CTRL) { return true; //Ctrl+M? //Ctrl+\r? } //break; case -'\r': //-CTRL('m') switch(_return_input_model) { case ui::ACTIVATE_OTHER: //textbox if(!_enabled) return false; //NEW: Letting UI handle default-buttons for example. if(!e::get_edited()||this!=e::get_active()) return true; deactivate(); /** Force callbacks, etc **/ //NEW: Don't resend callback on unfocus. e::set_edited(); activate(ui::ACTIVATE_OTHER); /** Reselect all _text **/ return false; case '\r': case '\n': case 0x0D0A: //if(multiline) //wordproc { /* RETURNS are written as newlines*/ key = _return_input_model; } goto whitespace; } default: return true; //keyboard_callback or wrapper? case -27: //CTRL('[') case +27: /* ESCAPE */ //WIDGETS_95_Main::keyboard returns control to the container. assert(0); break; case +'\t': /* TAB */ if(modifiers&GLUT_ACTIVE_CTRL) { return true; //Ctrl+I? //Ctrl+\t? } //break; case -'\t': //CTRL('i'): { if(!_enabled) return false; if(!_box_type) return true; e::set_edited(this); if(!_tab) key = ' '; //Ctrl+Tab is used to override tab_naviate. if(modifiers&&modifiers!=GLUT_ACTIVE_SHIFT) { modifiers = 0; //return true; } const char *text = _text.c_str(); //Visual Studio does this tab on multi-line selections. for(int i=leftmost;i0) //leftmost = e.curr_caret_pt-1; if(leftmost>0) leftmost--; } goto del; //-CTRL('a') Appears below. case +ctrl('a'): //SELECT-ALL leftmost = _sel_start = 0; rightmost = _sel_end = len; //e.curr_caret_pt break; //NOTE: Ctrl+Insert CAN ALSE COPY. HOW TO DETECT IT? case -ctrl('x'): case +ctrl('x'): case -ctrl('c'): case +ctrl('c'): //COPY if(leftmost==rightmost) return false; clipboard.set_text(_text.c_str()+leftmost,rightmost-leftmost); if(key==ctrl('x')) goto del; //CUT? break; case -ctrl('v'): case +ctrl('v'): //"PASTE" { std::string v; if(_enabled&&clipboard.get_text(v)) { _kh_erase(leftmost,rightmost); e::set_edited(this); _text.insert(leftmost,v.c_str()); _sel_start = _sel_end = leftmost+(int)v.size(); //_substr_end+=(int)v.size(); //Hint to update_and_draw_text? } break; } //WIDGETS_95_READLINE_MODIFIERS case -ctrl('a'): return special_handler(GLUT_KEY_HOME,0); case -ctrl('e'): return special_handler(GLUT_KEY_END,0); case -ctrl('b'): return special_handler(GLUT_KEY_LEFT,0); case -ctrl('f'): return special_handler(GLUT_KEY_RIGHT,0); case -ctrl('p'): return special_handler(GLUT_KEY_UP,0); case -ctrl('n'): return special_handler(GLUT_KEY_DOWN,0); case -ctrl('u'): /* ERASE LINE */ leftmost = 0; //_kh_erase(0,len); break; case -ctrl('k'): /* KILL TO END OF LINE */ _kh_erase(leftmost/*e.curr_caret_pt*/,len); break; } else if(isprint(key)||abs_key>127) whitespace: { //WIDGETS_95_READLINE_MODIFIERS if(modifiers==GLUT_ACTIVE_ALT) /* ALT ONLY */ { if(-'b'==rl_key) // Backward word { return special_handler(GLUT_KEY_LEFT,GLUT_ACTIVE_CTRL); } if(-'f'==rl_key) // Forward word { return special_handler(GLUT_KEY_RIGHT,GLUT_ACTIVE_CTRL); } if(-'d'==rl_key) goto delete_word_forward; //NEW: Alt based keyboard/menu navigation? return true; } if(modifiers&(GLUT_ACTIVE_CTRL|GLUT_ACTIVE_ALT)) { /** ignore other keys with modifiers */ return true; } else if(modifiers==GLUT_ACTIVE_SHIFT) { if(key>='a'&&key<='z') key-=32; } /**** If there's a current selection, erase it ******/ _kh_erase(leftmost,rightmost); /******** We insert the character into the string ***/ //This is for return_input_model (0x0D0A) but can be used for //UTF-8 if a control manages its own input and renders itself. char big_endian_or_4B_sequence[8] = { char(0xFF&key),char(0xFF&key>>8), char(0xFF&key>>16),char(0xFF&key>>24) }; int adv = strlen(big_endian_or_4B_sequence); if(_enabled) { e::set_edited(this); _text.insert(_sel_start,big_endian_or_4B_sequence); //_substr_end+=adv; //Hint to update_and_draw_text? /******** Move the insertion point and _substr_end one over ******/ //e.curr_caret_pt++; //_sel_start = _sel_end = ++e.curr_caret_pt; _sel_start = _sel_end+=adv; } } else return true; caret: if(e::set_caret(this,_sel_start==_sel_end?_sel_end:-1)) { find_insertion_xy(&e.caret_x,&e.caret_y); } /******** Now redraw _text ***********/ update_and_draw_text(); _dump("<- KEY HANDLER"); return false; } bool ui::textbox::_key_handler(int key, int modifiers) { int dt = _data_type; //NEW if(ui::spinner*s=spinner) if(!s->_data_type) { //RFC: Why does a spinner trump the data //type? dt = 0; //NEW: plain text mode //??? } if(dt!=ui_edit_text) //??? //FIX ME? if(dt&&modifiers==GLUT_ACTIVE_SHIFT) { //Foreign keyboard maybe? I don't know? if((key<'0'||key>'9')&&key!='-'&&key!='0') { modifiers = 0; assert(key>0); } } if(modifiers) ret: return ti::_key_handler(key,modifiers); //FILTERS /** Check if we only accept numbers **/ if(dt==edit_float) { if(key=='.'||key==-'.') { /** We're trying to type a period, but the _text already contains a period. Check whether the period is contained within is current selection(thus it will be safely replaced) */ const char *text = _text.c_str(); if(const char*dp=strchr(text,'.')) if(dp-text=sel_max()) return false; goto decimal_point; } goto floating_point; } if(dt==edit_int) { floating_point: { int ak = std::abs(key); if((key<'0'||key>'9')&&key!='-'/*&&key!='.'*/) { if(!iscntrl(ak)) return true; } } decimal_point: if(key=='-'||key==-'-') /* User typed a '-' */ { if(_has_limits&&_limits1>=0) //NEW { return false; } /* If user has first character selected, then '-' is allowed */ if(sel_min()||'-'==*_text.c_str()&&!sel_max()) { /* Can only place negative at beginning of _text, and only one of them */ return false; } } } goto ret; } /********************************* WIDGETS_95_EditText::special_handler() **********/ bool ui::textbox::_special_handler(int key, int modifiers) { switch(key) { case GLUT_KEY_PAGE_UP: case GLUT_KEY_PAGE_DOWN: return true; case GLUT_KEY_UP: case GLUT_KEY_DOWN: key = key==GLUT_KEY_UP?GLUT_KEY_LEFT:GLUT_KEY_RIGHT; //Normally I'd choose GLUT_ACTIVE_CTRL for this, but //GLUI is using that for reducing the increment. if(modifiers&~GLUT_ACTIVE_SHIFT) if(ui::spinner*s=spinner) { if(!s->_data_type) //NEW: Make flexible spinbox. { if(_data_type==edit_int) { modifiers&=~GLUT_ACTIVE_CTRL; } return s->_special_handler(key,modifiers); } } break; } return text_interface::_special_handler(key,modifiers); } bool text_interface::_special_handler(int key, int modifiers) { _dump("-> SPECIAL HANDLER"); int selector = 0; int ss = _sel_start; int se = _sel_end, len = char_size(); if(key==GLUT_KEY_UP||key==GLUT_KEY_DOWN) { selector = key; goto selector; } else textbox_found_ip::meridian = 0; switch(key) { case GLUT_KEY_LEFT: case GLUT_KEY_RIGHT: { int dx = key==GLUT_KEY_LEFT?-1:1; if(modifiers&GLUT_ACTIVE_CTRL) { if(dx==-1) se-=2; se = find_word_break(se,dx); } else if(ss==se||modifiers&GLUT_ACTIVE_SHIFT) { se+=dx; } else if(dx<0) //GLUT_KEY_LEFT { se = std::min(ss,se); } else //GLUT_KEY_RIGHT { se = std::max(ss,se); } break; } case GLUT_KEY_PAGE_UP: case GLUT_KEY_PAGE_DOWN: if(!scrollbar) return true; //... case GLUT_KEY_HOME: case GLUT_KEY_END: if(10) { x+=chr_span(_text.c_str()[se])/2; } textbox_found_ip::meridian = x; } else x = textbox_found_ip::meridian; y+=_font.height*(key==GLUT_KEY_UP?-1:1); break; case GLUT_KEY_HOME: x = -1000; break; case GLUT_KEY_END: x = m[2]; break; case GLUT_KEY_PAGE_UP: case GLUT_KEY_PAGE_DOWN: selector = key==GLUT_KEY_PAGE_UP?-1:1; y+=m[3]*selector; assert(scrollbar); selector*=m[3]/_font.height; scrollbar->set_int_val(selector+scrollbar->_int_val); break; } x+=_x_abs+m[0]; y+=_y_abs+m[1]-scrollbar_val()*_font.height; se = find_insertion_pt(x,y,true); if(se==-1) return false; } } /* Make sure e.curr_caret_pt is in bounds */ CLAMP(se,0,len); /*** Update selection if shift key is down ***/ if(~modifiers&GLUT_ACTIVE_SHIFT) _sel_start = se; if(se!=_sel_end||ss!=_sel_start) { _sel_end = se; if(e::set_caret(this,se==_sel_start?se:-1)) { if(selector) { e.caret_x = textbox_found_ip::x; e.caret_y = textbox_found_ip::y; } else find_insertion_xy(&e.caret_x,&e.caret_y); } /******** Now redraw _text ***********/ update_and_draw_text(); } _dump("<- SPECIAL HANDLER"); return false; //return true; //2019 } /****************************** WIDGETS_95_TextBox::activate() **********/ bool text_interface::_activate(int how) { if(how==ui::ACTIVATE_TAB&&!_tab_navigate) { /*2021: Mistake, not sure what I meant? if(!e.curr_modifiers&GLUT_ACTIVE_CTRL)*/ if(~e.curr_modifiers&GLUT_ACTIVE_CTRL) { return false; //2019: new default behavior } } _dump(how?"-> ACTIVATE":"-> DEACTIVATE"); if(how) //Identical to textbox { //prev_text = _text; /* Don't select everything if activated with mouse */ if(how==ui::ACTIVATE_MOUSE) return true; if(_tab_navigate) { _sel_start = 0; _sel_end = char_size(); } if(_sel_start==_sel_end) { e::set_caret(this,_sel_end); } } else //deactivate() { if(_tab_navigate) //2019: Why discard selection? { _sel_start = _sel_end = 0; //-1; } //2019: Moving this check higher up. //if(prev_text!=_text) if(e::get_edited()&&e::get_edited()->_text!=_text) { /***** Retrieve the current value from the _text *****/ /***** The live variable will be updated by set_text() ****/ //THIS FEELS WRONG /* This will force callbacks and gfx refresh */ //This resets the scrollbar position as if the _text was reset //by the user. //set_text(_text); if(_has_limits&&_data_type<=ui_edit_text) //NEW { //_evaluate_limits(); stage_live(_text.c_str()); output_live(); //This doesn't. //set_text would update this too. if(ui::spinner*s=spinner) { if(!s->_data_type) //NEW: plain text mode { s->set_text(_text); } else s->set_val(_float_val); } } else set_text(_text); //OLD /***** Now do callbacks if value changed ******/ //if(prev_text!=_text) { execute_callback(); } } } _dump(how?"<- ACTIVATE":"<- DEACTIVATE"); return true; } /************************** WIDGETS_95_EditText::update_substr_bounds() *********/ bool text_interface::update_substr_bounds() { _dump("-> UPDATE SS"); bool auto_hscroll = 1>=line_limit(); _CLAMP_substr(); int old_start = _substr_start, old_end = _substr_end; int se = _sel_end, len = _text.size(); int x,y; if(!auto_hscroll) //wordproc? { //The TextEdit code only called this from inside its activate method. //I don't think maintains _substr_start/_substr_end as it is now. //return false; (Update: It does now...) //EXPERIMENTAL //NEW: Count lines, and set _substr_start/end to the visible range. _find_insertion_pt(&x,&y,len); common_return: //auto_hscroll _dump("<- UPDATE SS"); if(_substr_start==old_start&&_substr_end==old_end) { return false; /*** bounds did not change ***/ } else //return true; /*** bounds did change ***/ { //Assuming single line, caret may slide left/right. if(auto_hscroll&&this==e::get_caret()) { int pt = _find_insertion_pt(&x,&y,se); if(se==pt||e::set_caret(this,pt)) { e.caret_x = x; e.caret_y = y; } } return true; } } ////// The rest applies to textbox or single-line ///// /*** Calculate the width of the usable area of the edit box ***/ int box_w = box_span(); box_w-=3; //Making (minimum) right selection margin match. int sw = substr_span(); //e.curr_caret_pt; const char *text = _text.c_str(); assert(se>=0); //_CLAMP_substr if(/*se>=0&&*/se<_substr_start) /* cursor moved left */ { while(se<_substr_start) sw+=substr_push_front(text); while(sw>box_w) sw-=substr_pop_back(text); } else if(se>_substr_end) /* cursor moved right */ { while(_substr_endbox_w) sw-=substr_pop_front(text); } else /* cursor between start/end */ { while(sw>box_w) { sw-=substr_pop_back(text); } for(int cw;_substr_endbox_w) { substr_pop_back(text); break; } } } //2019: Adding this (auto-scroll logic) for textbox. //It may not make sense for wordproc, but it shouldn't //hurt (text_interface doesn't distinguish among them.) for(int i=_substr_start;i-->0&&swbox_w) sw-=substr_pop_back(); { //Favor back, unless cursor is very near front, then keep //the string legible. float pc = (se-_substr_start)/ float(_substr_end-_substr_start); //zero divide if(old_end<=_substr_end&&pc>0.15f) //if(se>=_substr_end) { while(sw>box_w) sw-=substr_pop_front(text); } else while(sw>box_w) sw-=substr_pop_back(text); } goto common_return; } /****************************** WIDGETS_95_EditText::find_word_break() **********/ /* It looks either left or right(depending on value of 'direction' */ /* for the beginning of the next 'word', where word are characters */ /* separated by one of the following tokens: " :-.," */ /* If there is no next word in the specified direction, this returns */ /* the beginning of '_text', or the very end. */ int text_interface::find_word_break(int start, int direction) { if(start<=0) return 0; //I feel like (for English) these punctuation marks will have spaces. //const char *breaks = " :-.," "\n\t"; const char breaks[] = " -/\\" "\n\t"; //TODO: handle \r and \r\n digraph. int num_break_chars = sizeof(breaks)-1; //(int)strlen(breaks); int text_len = char_size(); /** If we're moving left, we have to start two back, in case we're either already at the beginning of a word, or on a separating token. Otherwise, this function would just return the word we're already at **/ /*2019: Callers can do this subtraction at their call site if they want. if(direction==-1) start-=2;*/ const char *text = _text.c_str(); /***** Iterate over _text in the specified direction *****/ for(int i=start;i>=0&&i0) /* Return the end of string */ { return text_len; } else return 0; /* Return the beginning of the _text */ } /******************************** WIDGETS_95_EditText::find_insertion_pt() *********/ /******************************** WIDGETS_95_TextBox::find_insertion_pt() *********/ /* This function returns the character numer *before which* the insertion */ /* point goes */ bool text_interface::substr_wrap_compare(int str_span, int box_span) { //This common comparison must be pixel perfect across algorithms. return str_span>box_span-4; //-4: Matches right/left margin. } int text_interface::_find_insertion_pt(int *found_x, int *found_y, int mode) { int len = char_size(); bool held_down = false; bool find_xy = mode>=-1, find_pt = !find_xy; int local_x, local_y; if(find_pt) //find_pt { local_x = *found_x, local_y = *found_y; held_down = mode==-3; /*** See if we clicked outside box ***/ if(!held_down&&!box_test(local_x,local_y)) return -1; found_x = &textbox_found_ip::x; found_y = &textbox_found_ip::y; } else //find_xy { *found_x = *found_y = 0; local_x = local_y = INT_MAX; if(mode<=0||mode>len) return mode==0?0:-1; } //REMOVE ME? /*** See if we clicked in an empty box ***/ if(!len) { if(find_pt) *found_x = *found_y = 0; //NEW return 0; } const char *text = _text.c_str(); if(1>=line_limit()) //textbox path? { //The idea is to merge these paths as soon //as they're better understood. /* We move from right to left, looking to see if the mouse was clicked to the right of the ith character */ int x,xx = _x_abs +_x_lr //The textbox box has a 2-pixel margin +ui_boxinnermarginx; /** find mouse click in _text **/ _CLAMP_substr(); if(held_down) xx-=substr_span(0,_substr_start); int pt = held_down?0:_substr_start; int se = held_down?len:_substr_end; if(find_xy) { if(modese) { update_substr_bounds(); pt = _substr_start; if(mode_substr_end) { return -1; } } se = mode; } int cw = 0; for(x=xx;ptx;pt++) { cw = chr_span(text[pt]); x+=cw; } if(find_pt&&local_x0) //NEW { /* printf("-> %d\n",se); */ x-=cw; pt--; } } *found_x = x-xx; *found_y = 0; return pt; } bool follow_cursor = false; int x,y, m[4], lh = line_feed(); margin_dims(m,ui_boxinnermarginx); //ARBITRARY: This is matching titlebar. I think //it's safe, but there is potential for overflow //if update_substr_bounds disagrees on line count. if(!_box_type) m[3]+=ui_boxinnermarginx*2+3; //These implement update_substr_bounds so that //the draw subroutine doesn't have to process the //entire _text. int visible_lines = std::max(1,m[3]/lh); int sss_line = scrollbar_val(); int sse_line = sss_line+visible_lines-1; if(!sss_line) _substr_start = 0; int stop_ln; int stop_len; if(find_pt) //find_pt { x = local_x-_x_abs-m[0]; y = local_y-_y_abs-m[1]; stop_len = len; stop_ln = std::max(0,sss_line+y/lh); } else //find_xy { stop_len = std::min(len,mode+1); stop_ln = x = y = INT_MAX; if(stop_len==len) //update_substr_bounds? { //HACK: Must filter out wheel events, and scrollbar I think?? //TODO: Prefer textbox_found_ip::c instead of active_control? follow_cursor = this==e.active_control&&!e.wheel_event; } } int box_w = box_span(); //Degenerate case can't end well. assert(box_w>=ui_min_text_span-ui_bar_arrow_size); if(box_w<8*chr_span(' ')) return 0; //CAUTION: THIS MUST PRECISELY AGREE //WITH _draw (wordproc) //CAUTION: THIS MUST PRECISELY AGREE //WITH _draw (wordproc) //CAUTION: THIS MUST PRECISELY AGREE //WITH _draw (wordproc) const int se = _sel_end; /* Find the line clicked. */ int sol = 0, eol = 0, line = 0, sel = 0; for(;;sol=eol,line++) { if(line==sss_line) //update_substr_bounds? { if(stop_len==len) _substr_start = sol; restart:; //Move scrollbar down? } //TRY TO MIRROR draw ALGORITHM HERE. int tw,sw = 0; do { if(tw=substr_tab_span(sw,text[eol])) { sw+=tw; if(substr_wrap_compare(sw,box_w)) break; } else break; /* Skip newline */ }while(++eolsel) //Move scrollbar up? { sse_line-=sss_line-sel; sss_line = sel; _substr_start = sol; } else if(sse_line=stop_len //Beyond final line? ||line==stop_ln) //Cap end of wrapped line? { if(find_pt) stop_len = eol; eol = sol; break; } } // Now search to the end of this line for the closest insertion point int tw,sw; for(tw=sw=0;eol=x) break; } else if('\n'==text[eol]) { break; } } sw-=tw; if(eol==stop_len) { eol--; } // did we go far enough? (see if click was >1/2 width of last char) if(tw) { if(find_pt) { int cmp = '\t'!=text[eol]?tw/2:tw-chr_span(' ')/2; if(x>sw+cmp) { eol++; sw+=tw; //NOTE: eol may be char_size. } } } else sw++; //Set caret aside. if(stop_len>=len-1) //update_substr_bounds? { if(lineset_range(bot,0); if(follow_cursor) { int lo = sb->_int_val; int hi = sb->_int_val+(visible_lines-1); if(selhi) lo+=(sel-hi); if(lo!=sb->_int_val) sb->set_int_val(lo); } } else if(line>=visible_lines) //NEW { //REMINDER: ui::message needs to grow larger //to accommodate the prompt message, but not //smaller. if(~_lock&2) { //HACK: Must precisely agree with //wordproc::_update_area. int h = lh+lh*line; if(!_box_type) { //ARBITRARY: This is matching titlebar. I think //it's safe, but there is potential for overflow //if update_substr_bounds disagrees on line count. h-=3; } else h+=2*ui_boxinnermarginx; _h = h; offset_dims(nullptr,&_h); update_area(); repack(); } } } *found_x = sw; *found_y = line*lh; return eol; } /*************************************** WIDGETS_95_EditText::_dump() **************/ #ifdef WIDGETS_95_DEBUG_STDOUT void text_interface::_dump(const char *name) { int len = char_size(); fprintf(_dump_FILE,"%s(_text@%p): mod:%d ins_pt:%d subs:%d/%d sel:%d/%d len:%d\n", name,this,e.curr_modifiers,e.curr_caret_pt,_substr_start,_substr_end,_sel_start,_sel_end,len); } #endif //---. }//<-'