diff --git a/README.md b/README.md index 0b034cf6..dcd10fe6 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,10 @@ Run it! All dependencies will be resovled automatically by biicode! Amazing, isn The best way to get help with Nana library is by visiting http://nanapro.org/help.htm +## Sending a Pull Request ? + +This project is encourage you to contribute it through sending a pull request! There is a simple rule, please **don't** directly commit your contributions to the **master** branch. According to your commits, please choose the **hotfixes** branch or the **develop** branch. Thank you! + ## Introduction to the Repository There are two main branches with an infinite lifetime: diff --git a/include/nana/gui/widgets/listbox.hpp b/include/nana/gui/widgets/listbox.hpp index 8a2f1938..87309008 100644 --- a/include/nana/gui/widgets/listbox.hpp +++ b/include/nana/gui/widgets/listbox.hpp @@ -163,9 +163,9 @@ namespace nana typedef std::vector selection; - //struct essence_t - //@brief: this struct gives many data for listbox, - // the state of the struct does not effect on member funcions, therefore all data members are public. + /// struct essence_t + ///@brief: this struct gives many data for listbox, + /// the state of the struct does not effect on member funcions, therefore all data members are public. struct essence_t; struct category_t; @@ -366,13 +366,17 @@ namespace nana return iter; } - void append(std::initializer_list); + /// Appends one item at the end of this category with the specifies text in the column fields + void append(std::initializer_list); size_type columns() const; cat_proxy& text(nana::string); nana::string text() const; + cat_proxy & select(bool); + bool selected() const; + /// Behavior of a container void push_back(nana::string); @@ -473,16 +477,54 @@ namespace nana color_proxy header_grabbed{ static_cast(0x8BD6F6)}; color_proxy header_floated{ static_cast(0xBABBBC)}; color_proxy item_selected{ static_cast(0xD5EFFC) }; + + unsigned max_header_width{3000}, /// \todo how to implement some geometrical parameters ?? + ext_w = 5; }; } }//end namespace drawerbase -/*! \brief A rectangle containing a list of strings from which the user can select. This widget contain a list of \a categories, with in turn contain a list of \a items. +/*! \class listbox +\brief A rectangle containing a list of strings from which the user can select. This widget contain a list of \a categories, with in turn contain a list of \a items. A category is a text with can be \a selected, \a checked and \a expanded to show the items. An item is formed by \a column-fields, each corresponding to one of the \a headers. An item can be \a selected and \a checked. -The user can \a drag the header to \a reisize it or to \a reorganize it. -By \a clicking on a header the list get \a reordered, first up, and then down alternatively. +The user can \a drag the header to \a resize it or to \a reorganize it. +By \a clicking on one header the list get \a reordered, first up, and then down alternatively. + +1. The resolver is used to resolute an object of the specified type for a listbox item. +3. nana::listbox creates the category 0 by default. The member functions without the categ parameter operate the items that belong to category 0. +4. A sort compare is used for sorting the items. It is a strict weak ordering comparer that must meet the requirement: + Irreflexivity (comp(x, x) returns false) + and + antisymmetry(comp(a, b) != comp(b, a) returns true) + A simple example. + bool sort_compare( const nana::string& s1, nana::any*, + const nana::string& s2, nana::any*, bool reverse) + { + return (reverse ? s1 > s2 : s1 < s2); + } + listbox.set_sort_compare(0, sort_compare); + The listbox supports attaching a customer's object for each item, therefore the items can be + sorted by comparing these customer's object. + bool sort_compare( const nana::string&, nana::any* o1, + const nana::string&, nana::any* o2, bool reverse) + { + if(o1 && o2) //some items may not attach a customer object. + { + int * i1 = o1->get(); + int * i2 = o2->get(); + return (i1 && i2 && (reverse ? *i1 > *i2 : *i1 < *i2)); + ;//some types may not be int. + } + return false; + } + listbox.anyobj(0, 0, 10); //the type of customer's object is int. + listbox.anyobj(0, 0, 20); +\todo doc: actualize this example listbox.at(0)... +\see nana::drawerbase::listbox::cat_proxy +\see nana::drawerbase::listbox::item_proxy +\example listbox_Resolver.cpp */ class listbox : public widget_object, @@ -504,14 +546,17 @@ By \a clicking on a header the list get \a reordered, first up, and then down al listbox(window, bool visible); listbox(window, const rectangle& = {}, bool visible = true); - void auto_draw(bool); ///); ///); ///< Appends categories at the end cat_proxy insert(cat_proxy, nana::string); cat_proxy at(size_type pos) const; diff --git a/source/gui/widgets/listbox.cpp b/source/gui/widgets/listbox.cpp index 53ee6eff..9c8eccf7 100644 --- a/source/gui/widgets/listbox.cpp +++ b/source/gui/widgets/listbox.cpp @@ -399,21 +399,20 @@ namespace nana return{}; } - void create(nana::string&& text, unsigned pixels) + size_type create(nana::string&& text, unsigned pixels) { cont_.emplace_back(std::move(text), pixels, static_cast(cont_.size())); + return cont_.back().index; } void item_width(size_type pos, unsigned width) { - if (pos >= cont_.size()) - return; - for(auto & m : cont_) - { if(m.index == pos) + { m.pixels = width; - } + return; + } } unsigned item_width(size_type pos) const @@ -1592,31 +1591,7 @@ namespace nana } /// set all items in cat to selection sel, emiting events, actualizing last_selected_abs, but not check for single_selection_ - bool categ_selected(size_type cat, bool sel) - { - bool changed = false; - auto & items = _m_at(cat)->items; - - index_pair pos(cat, 0); - for(auto & m : items) - { - if(m.flags.selected != sel) - { - m.flags.selected = sel; - - arg_listbox arg{ item_proxy(ess_, pos), sel }; - wd_ptr()->events().selected.emit(arg); - changed = true; - - if (sel) // not check for single_selection_ - last_selected_abs = pos; - else if (last_selected_abs == pos) - last_selected_abs.set_both(npos); - } - ++pos.item; - } - return changed; - } + bool categ_selected(size_type cat, bool sel); void reverse_categ_selected(size_type categ) { @@ -1644,9 +1619,22 @@ namespace nana /// absolute position of the last displayed item index_pair last_displ() const { - return absolute ( last_displ() ); + return absolute ( last() ); } + /// can be used as the absolute position of the first absolute item, or as the display pos of the first displayed item + index_pair first() const + { + index_pair fst{0,npos}; + good_item(fst,fst); + return fst; + } + /// absolute position of the first displayed item + index_pair first_displ() const + { + return absolute ( first() ); + } + bool good(size_type cat) const { return (cat < list_.size()); @@ -1656,32 +1644,32 @@ namespace nana { return ((pos.cat < list_.size()) && (pos.item < size_item(pos.cat))); } - + /// if good return the same item (in arg item), or just the next cat and true, but If fail return false bool good_item(index_pair pos, index_pair& item) const { if (!good(pos.cat)) - return false; + return false; // cat out of range if (pos.is_category()) { - item = pos; - if (0 == pos.cat) - item.item = 0; - - return true; + item = pos; // return the cat self + if (0 == pos.cat) // but for cat 0 return first item + item.item = 0; // let check this is good + else + return true; } - auto i = _m_at(pos.cat); + auto i = _m_at(pos.cat); // pos is not a cat and i point to it cat if (pos.item < i->items.size()) { - item = pos; + item = pos; // good item, return it return true; } - if (++i == list_.end()) + if (++i == list_.end()) // item out of range and no more cat return false; - item.cat = pos.cat + 1; + item.cat = pos.cat + 1; // select the next cat item.item = npos; return true; } @@ -1689,7 +1677,7 @@ namespace nana ///Translate relative position (position in display) into absolute position (original data order) size_type absolute(const index_pair& display_pos) const { - if(sorted_index_ == npos) + if(sorted_index_ == npos || display_pos.item == npos) return display_pos.item ; auto & catobj = *_m_at(display_pos.cat); @@ -1730,7 +1718,7 @@ namespace nana /// all arg are relative to display order, or all are absolute, but not mixed bool forward(index_pair from, size_type offs, index_pair& item) const { - if(!good_item(from, from)) + if(!good_item(from, from)) return false; if(offs == 0) @@ -1739,13 +1727,12 @@ namespace nana return true; } - if(list_.size() <= from.cat) return false; - if(from.is_category()) { // this is a category, so... // and offs is not 0, this category would not be candidated. // the algorithm above to calc the offset item is always starting with a item. + // we can not select, navigate or highlight begining from a cat? --offs; from.item = 0; } @@ -1754,7 +1741,7 @@ namespace nana if(icat->expand) { - std::size_t item_left_in_this_cat = icat->items.size() -1- from.item; + std::size_t item_left_in_this_cat = icat->items.size()- from.item -1; if(offs <= item_left_in_this_cat ) { item = from; @@ -1763,14 +1750,15 @@ namespace nana } else { - offs -= item_left_in_this_cat ; - item = from; - item.item += item_left_in_this_cat ; + offs -= (item_left_in_this_cat+1) ; + item = from; + item.item += item_left_in_this_cat ;// select the last item } } ++from.cat; ++icat; + for(; icat != list_.end(); ++icat, ++from.cat) { item.cat = from.cat; @@ -2195,7 +2183,7 @@ namespace nana } else { - new_where.second = (y - header_visible_px() + 1) / item_size; + new_where.second = ((y + 1) - header_visible_px()) / item_size; // y>1 ! new_where.first = parts::lister; if(checkable) { @@ -2316,7 +2304,29 @@ namespace nana break; } } - private: + + unsigned auto_width(size_type pos, unsigned max=3000) /// \todo introduce parametr max_header_width + { + unsigned max_w{0} ; + for (const auto &cat : lister.cat_container()) + for (const auto &it : cat.items ) + { + if (pos >= it.cells.size()) continue; + // precalcule text geometry + unsigned ts = static_cast ( graph->text_extent_size(it.cells[pos].text).width); + if (max_w < ts) + max_w = ts; + } + if (!max_w) return 0; + + unsigned ext_w = scheme_ptr->ext_w ; + if( pos == 0 && checkable) // only before the first column (display_order=0 ?) + ext_w += 18; + header.item_width(pos, max_w + ext_w + 1 < max ? max_w + ext_w + 1 : max); + return max_w; + } + + private: void _m_answer_scroll(const arg_mouse& arg) { if(arg.evt_code == event_code::mouse_move && arg.left_button == false) return; @@ -2450,7 +2460,7 @@ namespace nana } } - nana::string es_lister::to_string(const export_options& exp_opt) const + nana::string es_lister::to_string(const export_options& exp_opt) const { nana::string list_str; bool first{true}; @@ -2471,6 +2481,22 @@ namespace nana return list_str ; } + bool es_lister::categ_selected(size_type cat, bool sel) + { + bool changed = false; // we need this?? + + cat_proxy cpx{ess_,cat}; + for (item_proxy &it : cpx ) + { + if (it.selected() != sel) + changed = true; + it.select(sel); + } + + last_selected_abs = last_selected_dpl = index_pair {cat, npos}; + + return changed; // we need this?? + } class drawer_header_impl { @@ -2903,7 +2929,7 @@ namespace nana cell_txtcolor = m_cell.custom_format->fgcolor; } - int ext_w = 5; + int ext_w = essence_->scheme_ptr->ext_w; if(first && essence_->checkable) // draw the checkbox if need, only before the first column (display_order=0 ?) { ext_w += 18; @@ -3302,6 +3328,14 @@ namespace nana void trigger::dbl_click(graph_reference graph, const arg_mouse&) { + if (essence_->pointer_where.first == essence_t::parts::header) + if (cursor::size_we == essence_->lister.wd_ptr()->cursor()) + { + if (essence(). auto_width(drawer_header_->item_spliter() )) // ? in order + essence().update(); + return; + } + if (essence_->pointer_where.first != essence_t::parts::lister) return; @@ -3370,21 +3404,35 @@ namespace nana if (! scrl.make_page_scroll(!up)) return; essence_->lister.select_for_all(false); - if (up) - item_proxy {essence_, essence_->scroll_y_abs()}.select(true); - else - { - index_pair idx{essence_->scroll_y_dpl()}; + + index_pair idx{essence_->scroll_y_dpl()}; + if (!up) essence_->lister.forward(idx, scrl.range()-1, idx); - item_proxy::from_display(essence_,idx).select(true); - } + + if (idx.is_item()) + item_proxy::from_display(essence_, idx).select(true); + else + if(!essence_->lister.single_selection()) + essence_->lister.categ_selected(idx.cat, true); + + essence_->trace_last_selected_item (); + break; } case keyboard::os_home: + { essence_->lister.select_for_all(false); - item_proxy::from_display(essence_, {0,0}).select(true); + + index_pair frst{essence_->lister.first()}; + if (frst.is_item()) + item_proxy::from_display(essence_, frst).select(true); + else + if(!essence_->lister.single_selection()) + essence_->lister.categ_selected(frst.cat, true); + essence_->trace_last_selected_item (); break; + } case keyboard::os_end: essence_->lister.select_for_all(false); item_proxy::from_display(essence_, essence_->lister.last()).select(true); @@ -3436,7 +3484,7 @@ namespace nana { auto i = ess_->lister.cat_container().begin(); std::advance(i, pos.cat); - cat_ = &(*i); + cat_ = &(*i); // what is pos is a cat? } } @@ -3481,7 +3529,7 @@ namespace nana /// is ignored if no change (maybe set last_selected anyway??), but if change emit event, deselect others if need ans set/unset last_selected item_proxy & item_proxy::select(bool s) { - auto & m = cat_->items.at(pos_.item); // a ref to the real item + auto & m = cat_->items.at(pos_.item); // a ref to the real item // what is pos is a cat? if(m.flags.selected == s) return *this; // ignore if no change m.flags.selected = s; // actually change selection @@ -3715,6 +3763,24 @@ namespace nana } } + cat_proxy & cat_proxy::select(bool sel) + { + for (item_proxy &it : *this ) + it.select(sel); + + ess_->lister.last_selected_abs = + ess_->lister.last_selected_dpl = index_pair {this->pos_, npos}; + + return *this; + } + bool cat_proxy::selected() const + { + for (item_proxy &it : *this ) + if (!it.selected()) + return false; + return true; + } + auto cat_proxy::columns() const -> size_type { return ess_->header.cont().size(); @@ -3957,11 +4023,12 @@ namespace nana _m_ess().set_auto_draw(ad); } - void listbox::append_header(nana::string text, unsigned width) + listbox::size_type listbox::append_header(nana::string text, unsigned width) { auto & ess = _m_ess(); - ess.header.create(std::move(text), width); + listbox::size_type index = ess.header.create(std::move(text), width); ess.update(); + return index; } listbox& listbox::header_width(size_type pos, unsigned pixels) @@ -3971,6 +4038,13 @@ namespace nana ess.update(); return *this; } + unsigned listbox::auto_width(size_type pos, unsigned max) + { + auto & ess = _m_ess(); + unsigned max_w = ess.auto_width(pos, max); + ess.update(); + return max_w; + } unsigned listbox::header_width(size_type pos) const {