#include "../include/xcv_license.h" //PCH namespace Widgets95 {//-. //<-' //dropdown needs this system to destroy //its glutMenu automatically. li::item &li::item::id(int i) { //What if the parent is another item? if(i!=_id) if(_parent_node) list().es()|=es_msb; _id = i; return *this; } li::item &li::item::set_text(c_string str) { if(!str.str) str.str = ""; //What if the parent is another item? if(_parent_node) { li &l = list(); int es = list().es(); //OPTIMIZING: Lists would need to turn on //a bit other than es_msb to enable tests. bool msb = es&&~es&es_msb; if(str.str!=c_str()) { if(msb||_text!=str.str) { _text.assign(str.str); msb: if(msb) l.es()|=es_msb; } } else goto msb; l.redraw(); //May as well? } else if(str.str!=c_str()) { _text.assign(str.str); } return *this; } /************************************ WIDGETS_95_Listbox::mouse_over() **********/ static void xcv_dropdown_glutCreateMenu_callback(int i) { ui::dropdown *my = dynamic_cast(e.menu_control); if(!my){ assert(0); return; } e.set_menu(); //NEW //2022: should set_int_val be unconditional? //if(i==my->_int_val) { /*2022: depending on the box's semantics //selecting the same value is meaningful //NOTE: get_edited can be used if needed if(my->_box_type) { if(my->_curr_item) return; } else //EXPERIMENTAL { if(my->parent()->_text==my->_text) return; }*/ } e::set_edited(my); //2022 my->set_int_val(i); my->execute_callback(); if(!my->_box_type) //EXPERIMENTAL { //REMINDER: Not sure why I wrote //this but I am sure now it's no //good. //(FYI: Disabling this broke the //"Paint Texture" tool in MM3D.) //my->parent()->execute_callback(); } } static bool xcv_dropdown_context_menu = false; extern bool xcv_widgets_context_menu(int,int,int); bool ui::dropdown::_mouse_down_handler(int local_x, int local_y) { //spacebar_mouse_click? if(local_x>0&&!box_test(local_x,local_y)) return true; if(glut::get_wxWidgets_enabled()) //HACK { //GLUT doesn't have its own manual menu system. //TODO: Will need this to render button pressed. xcv_dropdown_context_menu = true; //EXPERIMENTAL //This facilitates combination textbox/dropdown. int x = -1, y = -1; if(!_box_type) if(control*p=parent()) { x = p->_x_abs+p->_x_lr; y = p->_y_abs+p->_h-p->_y_off_bot; } local_x = _x_abs+_x_lr; local_y = _y_abs+_h-_y_off_bot; _mouse_over(true,local_x,local_y); xcv_widgets_context_menu(GLUT_LEFT_BUTTON,x>=0?x:local_x,y>=0?y:local_y); _mouse_over(false,local_x,local_y); xcv_dropdown_context_menu = false; } return false; } bool ui::dropdown::_mouse_over(bool inside, int local_x, int local_y) { if(!xcv_dropdown_context_menu &&glut::get_wxWidgets_enabled()) { /*EXPERIMENTAL (UNFINISHED BUSINESS) //https://forums.wxwidgets.org/viewtopic.php?f=1&t=47824&p=202848#p202848 if(!e.curr_button_down) { int bt = xcv_bitmaps[bitmap::dropdown_up]->width; _w-=bt; if(box_test(local_x,local_y)) { if(this!=e.title_control) { e.title_control = this; if(item*it=_curr_item) //_curr_text if(str_span(it->c_str())>box_span()) { e::show_title(it->c_str()); } } } else if(this==e.title_control) { e.title_control = nullptr; e::show_title(); } _w+=bt; }*/ return true; } /* printf("x/y: %d/%d\n",x,y); */ dropdown *r = reference(); if(!r) r = this; int &gmid = r->extra_data._glut_menu_id; if(inside&&_enabled&&box_test(local_x,local_y)) { /**** Build a GLUT menu for this dropdown ***/ /* printf("%d %d\n",x,y); */ if(gmid<0) //NEW { if(gmid!=-1) //Marked stale? { glutDestroyMenu(gmid&~es_msb); } gmid = glutCreateMenu(xcv_dropdown_glutCreateMenu_callback); for(li::item*it=first_item();it;it=it->next()) { auto str = it->_text.c_str(); //Separator? glutAddMenuEntry(*str?str:nullptr,it->_id); } } glutSetMenu(gmid); //NEW glutAttachMenu(GLUT_LEFT_BUTTON); e::set_menu(this); return false; } else if(-1!=gmid) { /* printf("OUT\n"); */ //??? /*2019: Retaining menu resource. //Seems to confuse Nvidia's glut. //glutDetachMenu(GLUT_LEFT_BUTTON); { glutDestroyMenu(gmid); gmid = -1; } //Doesn't work here either... it seems to make a //phantom menu as if glutDestroyMenu did nothing. //glutDetachMenu(GLUT_LEFT_BUTTON); */ glutSetMenu(gmid); glutDetachMenu(GLUT_LEFT_BUTTON); e::set_menu(); } return true; } /****************************** WIDGETS_95_Listbox::special_handler() **********/ bool ui::dropdown::_special_handler(int key, int modifiers) { item *node = find_item_ptr(_int_val); if(!node) return true; //??? item *new_node = nullptr; switch(key) { case GLUT_KEY_DOWN: new_node = node->next(); break; case GLUT_KEY_UP: new_node = node->prev(); break; case GLUT_KEY_HOME: new_node = first_item(); break; case GLUT_KEY_END: new_node = last_item(); break; default: return true; //signal keycode unprocessed } if(new_node&&new_node!=node) { set_int_val(new_node->_id); execute_callback(); if(!_box_type) //EXPERIMENTAL parent()->execute_callback(); } return false; //return true; //2019 } /****************************** WIDGETS_95_Listbox::draw() **********/ void ui::dropdown::_draw() { int i = _enabled?bitmap::dropdown_up:bitmap::dropdown_up_dis; //EXPERIMENTAL if(!_box_type) return xcv_bitmaps[i]->draw(2,2); bool active = this==e::get_active(); int l = active?255:_enabled?0:64; _box_type = l==255?BOX_ACTIVE:BOX_WHITE; box_interface::_draw(); window::draw_color(l); _box_type = box; if(item*it=_curr_item) //_curr_text { int y = _y_off_top+1; if(control*p=parent()) if(p->_box_behavior&top) { //y++; //Bottom align? } int bs = box_span(); bs-=17; //dropdown_up window::begin_to_draw_str(_x_lr+4,y); for(const char*p=it->c_str();*p;p++) { if('&'==*p) continue; bs-=chr_span(*p); if(bs<0) break; //TODO: 0-terminate the text buffer. //glutBitmapCharacter(font(),text[i]); window::draw_chr(*p); } } xcv_bitmaps[i]->draw(_w-xcv_bitmaps[i]->width-_x_rl,_y_off_top); } /************************************ WIDGETS_95_Listbox::update_si() **********/ ui::dropdown &ui::dropdown::compact(int ms) { if(!ms) { update_area(); //HACK ms = active_area<2>(); ms+=2*ui_boxoutermarginx; } //SAME AS textbox::compact. if(_placement&(top|bottom)) //NEW { _x_lr = _x_rl = ui_boxoutermarginx; //Reminder: Want exact size given by ms. _w = ms; } else { _x_lr = name_span()+ui_namespacing; _x_rl = ui_boxoutermarginx; _w = _x_lr-ui_boxoutermarginx+ms; } return *this; } void ui::dropdown::_update_area() { if(!_box_type) //EXPERIMENTAL { space(-3,0,-2,-2,-2); _w = _h = 2+17+2; //bitmap::dropdown_up return; } //box_interface::_update_area(); /* Find the title size */ int name_w = 0; if(0==(_placement&(top|bottom))) //HACK name_w = name_span(); if(name_w) { name_w+=ui_namespacing; _x_lr = std::max(_x_lr,name_w); } else _x_lr = ui_boxoutermarginx; /* Find the longest item string ***/ int item_text_size = 0; char swap = 0; //HACK std::swap(swap,_font._width['&']); for(class item*it=first_item();it;it=it->next()) item_text_size = std::max(item_text_size,str_span(it->_text)); std::swap(swap,_font._width['&']); box_interface::_update_area(); /* Sum up our layout: name, item, and drop-down marker */ int new_w = _x_lr+std::max(ui_min_text_span,item_text_size)+26+_x_rl; if(/*_alignment?_w!=new_w:*/_w_parent_node) { //Not sure how to properly remove this item? if(&_items_list!=new_node->_parent_node) return false; } //Mark stale? int &gmid = extra_data._glut_menu_id; if(gmid!=-1) gmid|=es_msb; new_node->_link_this_to_parent_last(&_items_list); if(_int_val==new_node->_id) { //2021: This is trying to ignore separators without //causing further problems by assuming the text is //set at insertion time. A good policy either way. if(!_curr_item||_int_val!=_curr_item->_id) { _curr_item = new_node; } } _post_repack(); return true; } /************************************** WIDGETS_95_Listbox::delete_item() **********/ bool ui::dropdown::_delete_item(node *ptr) { //REMINDER: ~dropdown destructor will _delete_item(nullptr) //in order to destroy glut_menu_id. int &gmid = extra_data._glut_menu_id; if(gmid!=-1) { //Marked stale? if(gmid<0) gmid^=es_msb; glutDestroyMenu(gmid); gmid = -1; } if(reference()) if(!ptr||ptr==&_items_list) { //Assume ~dropdown or clear() as deleting is disallowed. new(&_items_list) node; ptr = _curr_item = nullptr; } if(!ptr) return false; assert(!reference()); if(ptr->_parent_node==&_items_list) { ptr->_delete(); if(ptr==_curr_item) _curr_item = nullptr; } else if(ptr==&_items_list) { _delete_all(); _curr_item = nullptr; } else return false; _post_repack(); return true; } /************************************ WIDGETS_95_Listbox::do_selection() **********/ bool ui::dropdown::_do_selection(int id) { /*** Is this item already selected? ***/ if(_last_int_val==id&&_curr_item&&_curr_item->_id==id) { //2021: I think this block is an optimization //when using -1 for separators I actually saw //a scenario where the separator was selected //because -1 was a default value if(!_curr_item->_text.empty()) { if(!_box_type) //EXPERIMENTAL if(parent()->_text!=_curr_item->_text) parent()->set_text(_curr_item->c_str()); return false; } } //2021: Reenable tooltip. if(this==e.title_control) e.title_control = nullptr; item *sel_item = find_item_ptr(id); if(!sel_item) { if(_int_val==id) //NEW { _curr_item = nullptr; redraw(); //EXPERIMENTAL if(!_box_type) parent()->set_text(); } return false; } //2019: This is needed to tell when _update_live //is not responding to set_int_value. The reason //this is not implemented is _upate_live is just //so the caller can process the bool return code. _last_int_val = id; //Means call did not originate from _update_live. if(_int_val!=id) set_int_val(id); //_curr_text = sel_item->_text; _curr_item = sel_item; /* printf("-> %s\n",curr_text.c_str()); */ //EXPERIMENTAL if(!_box_type) parent()->set_text(*sel_item); redraw(); return true; } /********************************************* WIDGETS_95_Listbox::_dump() **********/ #ifdef WIDGETS_95_DEBUG_STDOUT void ui::dropdown::_dump() { /* printf("%p\n",(char*)name); */ fprintf(_dump_FILE,"dropdown: %s\n",name.c_str()); for(item*it=first_item();it;it=it->next()) { fprintf(_dump_FILE," %3d : %s\n",it->_id,it->_text.c_str()); } } #endif //---. }//<-'