diff --git a/include/nana/gui/widgets/listbox.hpp b/include/nana/gui/widgets/listbox.hpp index 056349cd..1827a585 100644 --- a/include/nana/gui/widgets/listbox.hpp +++ b/include/nana/gui/widgets/listbox.hpp @@ -112,7 +112,8 @@ namespace nana std::size_t pos_{0}; }; - struct index_pair + /// usefull for both absolute and display (sorted) positions + struct index_pair { size_type cat; //The pos of category size_type item; //the pos of item in a category. @@ -169,7 +170,8 @@ namespace nana class drawer_header_impl; class drawer_lister_impl; - class trigger: public drawer_trigger + /// mostly works on display positions + class trigger: public drawer_trigger { public: trigger(); @@ -198,13 +200,22 @@ namespace nana drawer_lister_impl *drawer_lister_; };//end class trigger - class item_proxy + /// operate with absolute positions and contain only the position but montain pointers to parts of the real items + /// item_proxy self, it references and iterators are not invalidated by sort() + class item_proxy : public std::iterator { public: item_proxy(essence_t*); item_proxy(essence_t*, const index_pair&); + /// the main porpose of this it to make obvious that item_proxy operate with absolute positions, and dont get moved during sort() + static item_proxy from_display(essence_t *ess, const index_pair &relative) ; + item_proxy from_display(const index_pair &relative) const; + + /// posible use: last_selected_display = last_selected.to_display().item; use with caution, it get invalidated after a sort() + index_pair to_display() const; + bool empty() const; item_proxy & check(bool ck); @@ -318,8 +329,8 @@ namespace nana essence_t * _m_ess() const; private: std::vector & _m_cells() const; - nana::any * _m_value(bool alloc_if_empty); - const nana::any * _m_value() const; + nana::any * _m_value(bool alloc_if_empty); + const nana::any * _m_value() const; private: essence_t * ess_; category_t* cat_{nullptr}; @@ -334,7 +345,7 @@ namespace nana cat_proxy(essence_t*, size_type pos); cat_proxy(essence_t*, category_t*); - /// Append an item at end of the category, set_value determines whether assign T object to the value of item. + /// Append an item at abs end of the category, set_value determines whether assign T object to the value of item. template item_proxy append(T&& t, bool set_value = false) { @@ -367,13 +378,18 @@ namespace nana item_proxy cbegin() const; item_proxy cend() const; - item_proxy at(size_type pos) const; + item_proxy at(size_type pos_abs) const; item_proxy back() const; - /// Returns the index of a item by its display pos, the index of the item isn't changed after sorting. + /// Returns the absolute index of a item by its display pos, the index of the item isn't changed after sorting. + /// convert from display order to absolute (find the real item in that display pos) but without check from current active sorting, in fact using just the last sorting !!! size_type index_by_display_order(size_type disp_order) const; - size_type display_order(size_type pos) const; - size_type position() const; + + /// find display order for the real item but without check from current active sorting, in fact using just the last sorting !!! + size_type display_order(size_type pos) const; + + /// this cat position + size_type position() const; /// Returns the number of items size_type size() const; @@ -410,7 +426,7 @@ namespace nana private: essence_t* ess_{nullptr}; category_t* cat_{nullptr}; - size_type pos_{0}; + size_type pos_{0}; ///< Absolute position, not relative to display, and dont change during sort() }; } }//end namespace drawerbase @@ -446,12 +462,12 @@ namespace nana } }//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 items. +/*! \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, +By \a clicking on a header the list get \a reordered, first up, and then down alternatively. */ class listbox : public widget_object, @@ -474,6 +490,8 @@ By \a clicking on a header the list get \a reordered, first up, and then down al void auto_draw(bool); ///); /// strick_ordering); - void sort_col(size_type col, bool reverse = false); + /// sort() and ivalidate any existing reference from display position to absolute item, that is: after sort() display offset point to different items + void sort_col(size_type col, bool reverse = false); size_type sort_col() const; - void unsort(); + + /// potencially ivalidate any existing reference from display position to absolute item, that is: after sort() display offset point to different items + void unsort(); bool freeze_sort(bool freeze); - selection selected() const; /// - class scroll + class scroll // add a widget scheme? : public widget_object, drawerbase::scroll::scroll_events> { typedef widget_object > base_type; @@ -345,29 +345,29 @@ namespace nana /// \brief The construct that creates a widget. /// @param wd A handle to the parent window of the widget being created. - /// @param visible specifying the visible after creating. + /// @param visible specify the visibility after creation. scroll(window wd, bool visible) { - this->create(wd, rectangle(), visible); + this->create(wd, rectangle(), visible); // add a widget scheme? and take some colors from these wd? } /// \brief The construct that creates a widget. /// @param wd A handle to the parent window of the widget being created. /// @param r the size and position of the widget in its parent window coordinate. - /// @param visible specifying the visible after creating. + /// @param visible specify the visibility after creation. scroll(window wd, const rectangle& r, bool visible = true) { this->create(wd, r, visible); } /// \brief Determines whether it is scrollable. - /// @param for_less whether it can be scrolled for a less value. + /// @param for_less whether it can be scrolled for a less value (backward or "up" if true, forward or "down" if false). bool scrollable(bool for_less) const { auto & m = this->get_drawer_trigger().metrics(); return (for_less ? (0 != m.value) : (m.value < m.peak - m.range)); } - + /// the whole total (peak) size_type amount() const { return this->get_drawer_trigger().metrics().peak; @@ -378,7 +378,7 @@ namespace nana return this->get_drawer_trigger().peak(Max); } - /// Get the range of the widget. + /// Get the range of the widget (how many is shonw on a page, that is, How many to scroll after click on first or second) size_type range() const { return this->get_drawer_trigger().metrics().range; @@ -390,7 +390,7 @@ namespace nana return this->get_drawer_trigger().range(r); } - /// \brief Get the value. + /// \brief Get the value (current offset calculated from the very beginnig) /// @return the value. size_type value() const { @@ -419,12 +419,12 @@ namespace nana return this->get_drawer_trigger().step(s); } - /// \brief Increase/decrease values by a step. + /// \brief Increase/decrease values by a step (alternativelly by some number of steps). /// @param forward it determines whether increase or decrease. /// @return true if the value is changed. - bool make_step(bool forward) + bool make_step(bool forward, unsigned steps=1) { - if(this->get_drawer_trigger().make_step(forward, 1)) + if(this->get_drawer_trigger().make_step(forward, steps)) { API::refresh_window(this->handle()); return true; @@ -437,13 +437,27 @@ namespace nana /// @return true if the vlaue is changed. bool make_scroll(bool forward) { - if(this->get_drawer_trigger().make_step(forward, 3)) + if(this->get_drawer_trigger().make_step(forward, 3)) // set this 3 in the metrics of the widget scheme ? { API::refresh_window(this->handle()); return true; } return false; } + + /// \brief Increase/decrease values by a page as if it is scrolled page up. + /// @param forward it determines whether increase or decrease. + /// @return true if the vlaue is changed. + bool make_page_scroll(bool forward) + { + if(this->get_drawer_trigger().make_step(forward, range()-1)) + { + API::refresh_window(this->handle()); + return true; + } + return false; + } + };//end class scroll }//end namespace nana #endif diff --git a/source/gui/widgets/listbox.cpp b/source/gui/widgets/listbox.cpp index e81a4859..e47706d2 100644 --- a/source/gui/widgets/listbox.cpp +++ b/source/gui/widgets/listbox.cpp @@ -18,6 +18,7 @@ #include #include #include +#include namespace nana { @@ -365,18 +366,28 @@ namespace nana cont_.emplace_back(std::move(text), pixels, static_cast(cont_.size())); } - void item_width(size_type index, unsigned width) + void item_width(size_type pos, unsigned width) { - if (index >= cont_.size()) + if (pos >= cont_.size()) return; for(auto & m : cont_) { - if(m.index == index) + if(m.index == pos) m.pixels = width; } } + unsigned item_width(size_type pos) const + { + for (auto & m : cont_) + { + if (m.index == pos) + return m.pixels; + } + return 0; + } + unsigned pixels() const { unsigned pixels = 0; @@ -571,9 +582,9 @@ namespace nana struct category_t { - typedef std::deque container; + using container = std::deque; - nana::string text; + ::nana::string text; std::vector sorted; container items; bool expand{true}; @@ -583,14 +594,10 @@ namespace nana category_t() = default; - category_t(nana::string&& str) + category_t(nana::string str) :text(std::move(str)) {} - - category_t(const nana::string& str) - :text(str) - {} - + bool selected() const { for (auto & m : items) @@ -604,10 +611,10 @@ namespace nana class es_lister { public: - typedef std::list container; + using container = std::list; - std::function(std::size_t) > fetch_ordering_comparer; + std::function(std::size_t) > fetch_ordering_comparer; es_lister() { @@ -619,8 +626,6 @@ namespace nana { ess_ = ess; widget_ = dynamic_cast(&wd); - if(nullptr == widget_) - throw std::bad_cast(); } nana::listbox* wd_ptr() const @@ -647,11 +652,14 @@ namespace nana return nullptr; } - void sort() + /// each sort() ivalidate any existing reference from display position to absolute item, that is after sort() display offset point to different items + void sort() { if((sorted_index_ == npos) || (!resort_)) return; + + auto weak_ordering_comp = fetch_ordering_comparer(sorted_index_); if(weak_ordering_comp) { @@ -708,13 +716,17 @@ namespace nana }); } } + scroll_refresh(); } + void scroll_refresh(); + /// sort() and ivalidate any existing reference from display position to absolute item, that is after sort() display offset point to different items bool sort_index(size_type index) { if (npos == index) { sorted_index_ = npos; + scroll_refresh(); return false; } @@ -730,11 +742,13 @@ namespace nana return true; } + /// sort() and ivalidate any existing reference from display position to absolute item, that is: after sort() display offset point to different items bool set_sort_index(std::size_t index, bool reverse) { if (npos == index) { sorted_index_ = npos; + scroll_refresh(); return false; } @@ -770,7 +784,7 @@ namespace nana return &list_.back(); } - void create_cat(const std::initializer_list & args) + void create_cat(const std::initializer_list& args) { for (auto & arg : args) list_.emplace_back(arg); @@ -807,7 +821,7 @@ namespace nana return &(*list_.emplace(i, std::move(text))); } - /// Insert before item in "pos" a new item with "text" in column 0 + /// Insert before item in absolute "pos" a new item with "text" in column 0, and place it in last display position of this cat bool insert(const index_pair& pos, nana::string&& text) { auto & catobj = *_m_at(pos.cat); @@ -826,15 +840,17 @@ namespace nana return true; } - size_type index_by_display_order(size_type cat, size_type order_pos) const + /// convert from display order to absolute (find the real item in that display pos) but without check from current active sorting, in fact using just the last sorting !!! + size_type index_by_display_order(size_type cat, size_type display_order_pos) const { auto & catobj = *_m_at(cat); - if (order_pos >= catobj.sorted.size()) + if (display_order_pos >= catobj.sorted.size()) throw std::out_of_range("listbox: Invalid item position."); - return catobj.sorted[order_pos]; + return catobj.sorted[display_order_pos]; } + /// find display order for the real item but without check from current active sorting, in fact using just the last sorting !!! size_type display_order(size_type cat, size_type pos) const { auto & catobj = *_m_at(cat); @@ -848,6 +864,7 @@ namespace nana return npos ; } + /// return a ref to the real item object at display!!! position pos using current sorting only if it is active, and at absolute position if no sorting is currently active. category_t::container::value_type& at(const index_pair& pos) { auto index = pos.item; @@ -857,15 +874,9 @@ namespace nana return _m_at(pos.cat)->items.at(index); } - const category_t::container::value_type& at(const index_pair& pos) const { - auto index = pos.item; - - if (sorted_index_ != npos) - index = absolute(pos); - - return _m_at(pos.cat)->items.at(index); + return at(pos); } void clear(size_type cat) @@ -884,99 +895,96 @@ namespace nana } } - std::pair advance(size_type categ, size_type index, size_type n) + index_pair advance(index_pair from, size_type offset) // <------------- index { - std::pair dpos(npos, npos); - if(categ >= size_categ() || (index != npos && index >= size_item(categ))) return dpos; + index_pair dpos{npos, npos}; + if(from.cat >= size_categ() || (from.item != npos && from.item >= size_item(from.cat))) + return dpos; - dpos.first = categ; - dpos.second = index; + dpos = from; - while(n) + while(offset) { - if(dpos.second == npos) + if(dpos.item == npos) { - if(expand(dpos.first) == false) + if(expand(dpos.cat) == false) { - if(dpos.first + 1 == size_categ()) + if(dpos.cat + 1 == size_categ()) break; - ++dpos.first; + ++dpos.cat; } else - dpos.second = 0; - --n; + dpos.item = 0; + --offset; } else { - size_type rest = size_item(dpos.first) - dpos.second - 1; + size_type rest = size_item(dpos.cat) - dpos.item - 1; if(rest == 0) { - if(dpos.first + 1 == size_categ()) + if(dpos.cat + 1 == size_categ()) break; - ++dpos.first; - dpos.second = npos; - --n; + ++dpos.cat; + dpos.item = npos; + --offset; } - else if(rest < n) + else if(rest < offset) { - n -= rest; - if(dpos.first + 1 >= size_categ()) + offset -= rest; + if(dpos.cat + 1 >= size_categ()) { - dpos.second += rest; + dpos.item += rest; break; } - dpos.second = npos; - ++dpos.first; + dpos.item = npos; + ++dpos.cat; } else { - dpos.second += n; + dpos.item += offset; break; } } } return dpos; } - - size_type distance(size_type cat, size_type index, size_type to_cat, size_type to_index) const + /// change to index arg + size_type distance(index_pair from, index_pair to) const { - if(cat == to_cat && index == to_index) return 0; + if(from == to ) return 0; - if(to_cat == cat) + if(to.cat == from.cat) { - if(index > to_index && index != npos) - std::swap(index, to_index); + if(from.item > to.item && from.item != npos) + std::swap(from.item, to.item); - return (index == npos ? to_index + 1 : to_index - index); - } - else if(to_cat < cat) - { - std::swap(cat, to_cat); - std::swap(index, to_index); + return (from.item == npos ? to.item + 1 : to.item - from.item); } + else if(to.cat < from.cat) + std::swap(from, to); size_type n = 0; - auto i = _m_at(cat); - if(index == npos) + auto i = _m_at(from.cat); + if(from.item == npos) { if(i->expand) n = i->items.size(); } else - n = i->items.size() - (index + 1); + n = i->items.size() - (from.item + 1); - for(++i, ++cat; i != list_.end(); ++i, ++cat) + for(++i, ++from.cat; i != list_.end(); ++i, ++from.cat) { ++n; //this is a category - if(cat != to_cat) + if(from.cat != to.cat) { if(i->expand) n += i->items.size(); } else { - if(to_index != npos) - n += (to_index + 1); + if(to.item != npos) + n += (to.item + 1); break; } } @@ -1215,9 +1223,9 @@ namespace nana wd_ptr()->events().selected.emit(arg); if (m.flags.selected) - last_selected = i; - else if (last_selected == i) - last_selected.set_both(npos); //make empty + last_selected_abs = i; + else if (last_selected_abs == i) + last_selected_abs.set_both(npos); //make empty } ++i.item; } @@ -1226,22 +1234,41 @@ namespace nana return changed; } - void item_selected(selection& vec) const + /// return absolute positions, no relative to display + void item_selected(selection& vec) const // change to selection item_selected(); { index_pair id; for(auto & cat : list_) { id.item = 0; - for(auto & m : cat.items) + for(auto & m : cat.items) { if(m.flags.selected) - vec.push_back(id); + vec.push_back(id); // absolute positions, no relative to display ++id.item; } ++id.cat; } } + + index_pair find_first_selected() + { + index_pair id; + for(auto & cat : list_) + { + id.item = 0; + for(auto & m : cat.items) + { + if(m.flags.selected) + return id; // absolute positions, no relative to display + ++id.item; + } + ++id.cat; + } + return {npos,npos}; + } + /// return absolute positions, no relative to display bool item_selected_all_checked(selection& vec) const { index_pair id; @@ -1254,7 +1281,7 @@ namespace nana { if (m.flags.selected) { - vec.push_back(id); + vec.push_back(id); // absolute positions, no relative to display ck &= m.flags.checked; } ++id.item; @@ -1266,89 +1293,9 @@ namespace nana return ck; } - void move_select(bool upwards) - { - auto next_selected = last_selected; - if (next_selected.empty()) - { - bool good = false; - for(size_type i = 0, size = list_.size(); i < size; ++i) - { - if(size_item(i)) - { - //The first item which contains at least one item. - next_selected.cat = i; - next_selected.item = 0; - good = true; - break; - } - } - if(good == false) return; // items in listbox : nothing to select (and an empty but visible cat?) - } - - //start moving - while(true) - { - if(upwards == false) - { - if(good(next_selected.cat)) - { - if (size_item(next_selected.cat) > next_selected.item + 1) - { - ++next_selected.item; - } - else - { - next_selected.item = 0; - if (size_categ() > next_selected.cat + 1) - ++next_selected.cat; - else - next_selected.cat = 0; - } - } - else - next_selected.set_both(0); - } - else - { - if (0 == next_selected.item) - { - //there is an item at least definitely, because the start pos is an available item. - do - { - if (0 == next_selected.cat) - next_selected.cat = size_categ() - 1; - else - --next_selected.cat; - - }while (0 == size_item(next_selected.cat)); - - next_selected.item = size_item(next_selected.cat) - 1; - } - else - --next_selected.item; - } - - if (good(next_selected.cat)) - { - expand(next_selected.cat, true); - - if (good(next_selected)) - { - select_for_all(false); - at(next_selected).flags.selected = true; - - arg_listbox arg{ item_proxy(ess_, index_pair(next_selected.cat, absolute(next_selected))), true }; - wd_ptr()->events().selected.emit(arg); - - last_selected = next_selected; - } - break; - } - else break; - } - } - + ///items; @@ -1590,10 +1538,10 @@ namespace nana wd_ptr()->events().selected.emit(arg); changed = true; - if (sel) - last_selected = pos; - else if (last_selected == pos) - last_selected.set_both(npos); + 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; } @@ -1605,7 +1553,8 @@ namespace nana categ_selected(categ, ! categ_selected(categ)); } - index_pair last() const + /// can be used as the absolute position of the last absolute item, or as the display pos of the last displayed item + index_pair last() const { index_pair i{ list_.size() - 1, list_.back().items.size() }; @@ -1622,6 +1571,12 @@ namespace nana return i; } + /// absolute position of the last displayed item + index_pair last_displ() const + { + return absolute ( last_displ() ); + } + bool good(size_type cat) const { return (cat < list_.size()); @@ -1661,12 +1616,35 @@ namespace nana return true; } - //Translate relative position into absolute position - size_type absolute(const index_pair& pos) const + ///Translate relative position (position in display) into absolute position (original data order) + size_type absolute(const index_pair& display_pos) const { - return (sorted_index_ == npos ? pos.item : _m_at(pos.cat)->sorted[pos.item]); + return (sorted_index_ == npos ? display_pos.item : _m_at(display_pos.cat)->sorted[display_pos.item]); + } + index_pair absolute_pair(const index_pair& display_pos) const + { + return {display_pos.cat, absolute( display_pos )}; + } + + ///Translate absolute position (original data order) into relative position (position in display) + size_type relative(const index_pair& pos) const + { + if (sorted_index_ == npos) return pos.item ; + + auto & catobj = *_m_at(pos.cat); + + for (size_type i=0; iexpand) { - std::size_t item_size = icat->items.size() - from.item; - if(offs < item_size) + std::size_t item_left_in_this_cat = icat->items.size() -1- from.item; + if(offs <= item_left_in_this_cat ) { item = from; - item.item += offs; - return true; + item.item += offs; // use absolute to know the real item + return true; // allways return here when we have only one cat. } else - offs -= item_size; + { + offs -= item_left_in_this_cat ; + item = from; + item.item += item_left_in_this_cat ; + } } ++from.cat; ++icat; for(; icat != list_.end(); ++icat, ++from.cat) { + item.cat = from.cat; + item.item = npos; + if(offs-- == 0) { - item.cat = from.cat; - item.item = npos; return true; } @@ -1719,7 +1702,7 @@ namespace nana { if(offs < icat->items.size()) { - item.cat = from.cat; + //item.cat = from.cat; item.item = offs; return true; } @@ -1730,6 +1713,7 @@ namespace nana return false; } + /// all arg are relative to display order, or all are absolute, but not mixed bool backward(index_pair from, size_type offs, index_pair& item) const { if(offs == 0) @@ -1738,7 +1722,7 @@ namespace nana if(good(from.cat)) { auto i = _m_at(from.cat); - size_type n = (from.is_category() ? 1 : from.item + 2); + size_type n = (from.is_category() ? 1 : from.item + 2); // ?? if(n <= offs) { offs -= n; @@ -1772,7 +1756,8 @@ namespace nana return false; } private: - container::iterator _m_at(size_type index) + /// categories iterator + container::iterator _m_at(size_type index) { if(index >= list_.size()) throw std::out_of_range("Nana.GUI.Listbox: invalid category index"); @@ -1792,16 +1777,16 @@ namespace nana return i; } public: - index_pair last_selected; + index_pair last_selected_abs, last_selected_dpl; private: essence_t * ess_{nullptr}; nana::listbox * widget_{nullptr}; std::size_t sorted_index_{npos}; ///< The index of the column used to sort bool resort_{true}; bool sorted_reverse_{false}; - bool ordered_categories_{false}; //A switch indicates whether the categories are ordered. - //The ordered categories always creates a new category at a proper position(before the first one which is larger than it). - container list_; + bool ordered_categories_{false}; ///< A switch indicates whether the categories are ordered. + /// The ordered categories always creates a new category at a proper position(before the first one which is larger than it). + container list_; // rename to categories_ bool single_selection_{ false }; bool single_selection_category_limited_{ false }; @@ -1840,8 +1825,8 @@ namespace nana { static const unsigned scale = 16; int offset_x; - index_pair offset_y; //cat stands for category, item stands for item. "item == npos" means that is a category. - + index_pair offset_y_abs, offset_y_dpl; //cat stands for category, item stands for item. "item == npos" means that is a category. + // need to be abs??? to see the same item after sort() ?? nana::scroll v; nana::scroll h; }scroll; @@ -1853,25 +1838,50 @@ namespace nana lister.fetch_ordering_comparer = std::bind(&es_header::fetch_comp, &header, std::placeholders::_1); } - const index_pair& scroll_y() const + const index_pair& scroll_y_abs() const { - return scroll.offset_y; + return scroll.offset_y_abs; + } + const index_pair& scroll_y_dpl() const + { + return scroll.offset_y_dpl; + } + const index_pair& scroll_y_dpl_refresh() + { + return scroll.offset_y_dpl = lister.relative_pair(scroll.offset_y_abs); } - void scroll_y(const index_pair& pos) + void scroll_y_abs(const index_pair& pos_abs) { - if (!lister.good(pos.cat)) + if (!lister.good(pos_abs.cat)) return; - scroll.offset_y.cat = pos.cat; - size_type number = lister.size_item(pos.cat); - if(pos.item < number) - scroll.offset_y.item = pos.item; + scroll.offset_y_abs.cat = pos_abs.cat; + + size_type number = lister.size_item(pos_abs.cat); + if(pos_abs.item < number) + scroll.offset_y_abs.item = pos_abs.item; else if(number) - scroll.offset_y.item = number - 1; + scroll.offset_y_abs.item = number - 1; else - scroll.offset_y.item = (pos.cat > 0 ? npos : 0); + scroll.offset_y_abs.item = (pos_abs.cat > 0 ? npos : 0); } + void scroll_y_rel(const index_pair& pos_rel) + { + scroll_y_abs(lister.relative_pair(pos_rel) ); + } + void set_scroll_y_abs(const index_pair& pos_abs) + { + scroll.offset_y_abs=pos_abs; + scroll_y_dpl_refresh() ; + } + /// directly set a tested relative display pos + void set_scroll_y_dpl(const index_pair& pos_dpl) + { + scroll.offset_y_dpl=pos_dpl; + scroll.offset_y_abs = lister.absolute_pair(pos_dpl); + } + //number_of_lister_item //@brief: Returns the number of items that are contained in pixels @@ -1882,44 +1892,56 @@ namespace nana return (lister_s / item_size) + (with_rest && (lister_s % item_size) ? 1 : 0); } - //keep the first selected item in the display area - void trace_selected_item() - { - selection svec; - lister.item_selected(svec); - if(svec.empty()) return; //no selected, exit. - - auto & item = svec[0]; - //Same with current scroll offset item. - if(item.item == npos && item.cat == scroll.offset_y.cat && scroll.offset_y.item == npos) - return; - - if(item.cat < scroll.offset_y.cat || ((item.cat == scroll.offset_y.cat) && (scroll.offset_y.item != npos) && (item.item == npos || item.item < scroll.offset_y.item))) - { - scroll.offset_y = item; - if(lister.expand(item.cat) == false) + //keep the first selected item in the display area: the distances are in display positions! + void trace_item_dpl( index_pair dpl_pos ) + { + if( dpl_pos.cat < scroll.offset_y_dpl.cat // in prevoious cat ---------------- up ----> we need to move + || ((dpl_pos.cat == scroll.offset_y_dpl.cat) && ( scroll.offset_y_dpl.item != npos) // is our cat, where we are an item + && (dpl_pos.item == npos || dpl_pos.item < scroll.offset_y_dpl.item))) + // problem!!!!!! + { + if(lister.expand(dpl_pos.cat) == false) { - if(lister.categ_selected(item.cat)) - scroll.offset_y.item = static_cast(npos); + if(lister.categ_selected(dpl_pos.cat)) + dpl_pos.item = static_cast(npos); else - lister.expand(item.cat, true); + lister.expand(dpl_pos.cat, true); } + set_scroll_y_dpl(dpl_pos); // <------------------------- set scroll.offset_y_dpl & scroll.offset_y_abs } else { - size_type numbers = number_of_lister_items(false); - size_type off = lister.distance(scroll.offset_y.cat, scroll.offset_y.item, item.cat, item.item); + size_type numbers = number_of_lister_items(false); // revise ... ok + size_type off = lister.distance(scroll.offset_y_dpl, dpl_pos); if(numbers > off) return; - auto n_off = lister.advance(scroll.offset_y.cat, scroll.offset_y.item, (off - numbers) + 1); - if(n_off.first != npos) - { - scroll.offset_y.cat = static_cast(n_off.first); - scroll.offset_y.item = static_cast(n_off.second); - } + index_pair n_off = lister.advance(scroll.offset_y_dpl, (off - numbers) + 1); + + if(n_off.cat != npos) // <------------------------- set scroll.offset_y_dpl & scroll.offset_y_abs + set_scroll_y_dpl(n_off); } - adjust_scroll_life(); - adjust_scroll_value(); + adjust_scroll_life(); // call adjust_scroll_value(); //adjust_scroll_value(); // again? + } + + void trace_item_abs( index_pair abs_pos ) + { + if(abs_pos.item == npos && abs_pos.cat == scroll.offset_y_abs.cat + && scroll.offset_y_abs.item == npos ) // if item==off y and is a cat + return; + + trace_item_dpl( lister.relative_pair(abs_pos)) ; // ??? scroll_y_dpl_refresh() ; + } + + void trace_last_selected_item( ) + { + trace_item_abs(lister.last_selected_abs); + } + + void trace_first_selected_item() + { + auto fs=lister.find_first_selected(); + if( ! fs.empty() ) + trace_item_abs( fs ); } void update() @@ -1948,12 +1970,12 @@ namespace nana if(height >= graph->width()) return; scroll.v.amount(lister.the_number_of_expanded()); scroll.v.range(number_of_lister_items(false)); - size_type off = lister.distance(0, 0, scroll.offset_y.cat, scroll.offset_y.item); + size_type off = lister.distance({0,0}, scroll.offset_y_dpl ); scroll.v.value(off); } } - - void adjust_scroll_life() + + void adjust_scroll_life() // at end call adjust_scroll_value(); { internal_scope_guard lock; @@ -2002,16 +2024,13 @@ namespace nana if(scroll.v.empty()) { scroll.v.create(wd, r); - API::take_active(scroll.v.handle(), false, wd); - scroll.v.events().mouse_move.connect_unignorable([this](const ::nana::arg_mouse& arg) + API::take_active(scroll.v.handle(), false, wd); // install value_changed() not mouse_move ???? + + scroll.v.events().value_changed([this](const ::nana::arg_scroll& arg) { - _m_answer_scroll(arg); + _m_answer_scroll_value(arg); }); - scroll.v.events().mouse_up.connect_unignorable([this](const ::nana::arg_mouse& arg) - { - _m_answer_scroll(arg); - }); } else scroll.v.move(r); @@ -2020,7 +2039,7 @@ namespace nana else if(!scroll.v.empty()) { scroll.v.close(); - scroll.offset_y.set_both(0); + set_scroll_y_dpl({0,0}); // scroll.offset_y.set_both(0); nana::rectangle r; if(rect_header(r)) @@ -2173,14 +2192,14 @@ namespace nana index_pair target; if(upwards == false) - lister.forward(scroll.offset_y, 1, target); + lister.forward(scroll.offset_y_dpl, 1, target); else - lister.backward(scroll.offset_y, 1, target); + lister.backward(scroll.offset_y_dpl, 1, target); - if (target == scroll.offset_y) + if (target == scroll.offset_y_dpl) return false; - scroll.offset_y = target; + set_scroll_y_dpl ( target ); return true; } @@ -2208,9 +2227,9 @@ namespace nana index_pair item; if(lister.forward(item, scroll.v.value(), item)) { - if (item != scroll.offset_y) + if (item != scroll.offset_y_dpl) { - scroll.offset_y = item; + set_scroll_y_dpl ( item ); update = true; } } @@ -2227,7 +2246,152 @@ namespace nana if(update) API::refresh_window(lister.wd_ptr()->handle()); } - }; + void _m_answer_scroll_value(const ::nana::arg_scroll& arg) + { + index_pair item; + if( !lister.forward(item, scroll.v.value(), item)) return; + + if (item == scroll.offset_y_dpl) + return; + + set_scroll_y_dpl ( item ); + + API::refresh_window(lister.wd_ptr()->handle()); + } + +#if ( 0 ) + void update_selection_range(index_pair to, const arg_mouse& arg) + { + using item_state = essence_t::item_state; + using parts = essence_t::parts; + bool update = false; + index_pair item_pos; + bool sel = true; + if (!lister.single_selection()) + { + if (arg.shift) + lister.select_display_range(lister.last_selected, item_pos, sel); + else if (arg.ctrl) + sel = !item_proxy(essence_, index_pair (item_pos.cat, lister.absolute(item_pos))).selected(); + else + lister.select_for_all(false); + } + else + sel = !item_proxy(essence_, index_pair (item_pos.cat, lister.absolute(item_pos))).selected(); + + item_ptr->flags.selected = sel; + index_pair last_selected(item_pos.cat, lister.absolute(item_pos)); + + arg_listbox arg{item_proxy{essence_, last_selected}, sel}; + lister.wd_ptr()->events().selected.emit(arg); + + if (item_ptr->flags.selected) + { + lister.cancel_others_if_single_enabled(true, last_selected); + essence_->lister.last_selected = last_selected; + + } + else if (essence_->lister.last_selected == last_selected) + essence_->lister.last_selected.set_both(npos); + } + else if(!lister.single_selection()) + lister.categ_selected(item_pos.cat, true); + update = true; + + } +#endif + +}; + + void es_lister::scroll_refresh() + { + ess_->scroll_y_dpl_refresh(); + + } + void es_lister::move_select(bool upwards, bool unselect_previous, bool trace_selected) + { + auto next_selected_dpl = relative_pair ( last_selected_abs); // last_selected_dpl; // ?? + if (next_selected_dpl.empty()) // has no cat ? (cat == npos) => beging from first cat + { + bool good = false; + for(size_type i = 0, size = list_.size(); i < size; ++i) // run all cat + { + if(size_item(i)) + { + //The first category which contains at least one item. + next_selected_dpl.cat = i; + next_selected_dpl.item = 0; + good = true; + break; + } + } + if(! good ) return; // items in listbox : nothing to select (and an empty but visible cat?) + } + + //start moving + while(true) + { + if(upwards == false) + { + if(good(next_selected_dpl.cat)) + { + if (size_item(next_selected_dpl.cat) > next_selected_dpl.item + 1) + { + ++next_selected_dpl.item; + } + else + { + next_selected_dpl.item = 0; + if (size_categ() > next_selected_dpl.cat + 1) + ++next_selected_dpl.cat; + else + next_selected_dpl.cat = 0; + } + } + else + next_selected_dpl.set_both(0); + } + else + { + if (0 == next_selected_dpl.item) + { + //there is an item at least definitely, because the start pos is an available item. + do + { + if (0 == next_selected_dpl.cat) + next_selected_dpl.cat = size_categ() - 1; + else + --next_selected_dpl.cat; + + }while (0 == size_item(next_selected_dpl.cat)); + + next_selected_dpl.item = size_item(next_selected_dpl.cat) - 1; + } + else + --next_selected_dpl.item; + } + + if (good(next_selected_dpl.cat)) + { + expand(next_selected_dpl.cat, true); // revise expand + + if (good(next_selected_dpl)) + { + if (unselect_previous && !single_selection_ ) + select_for_all(false); + + /// 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::from_display(ess_, next_selected_dpl).select(true); + + if (trace_selected) + ess_->trace_item_dpl(next_selected_dpl); + } + break; + } + else break; + } + } + class drawer_header_impl { @@ -2464,7 +2628,9 @@ namespace nana void draw(const nana::rectangle& rect) const { - internal_scope_guard lock; + // essence_->scroll_y_dpl_refresh() ; // ???? + + internal_scope_guard lock; size_type n = essence_->number_of_lister_items(true); if(0 == n)return; @@ -2480,9 +2646,11 @@ namespace nana es_lister & lister = essence_->lister; //The Tracker indicates the item where mouse placed. index_pair tracker(npos, npos); - auto & ptr_where = essence_->pointer_where; + auto & ptr_where = essence_->pointer_where; + + //if where == lister || where == checker, 'second' indicates the offset to the relative display-order pos of the scroll offset_y which stands for the first item to be displayed in lister. if((ptr_where.first == parts::lister || ptr_where.first == parts::checker) && ptr_where.second != npos) - lister.forward(essence_->scroll.offset_y, ptr_where.second, tracker); + lister.forward(essence_->scroll.offset_y_dpl, ptr_where.second, tracker); std::vector subitems; essence_->header_seq(subitems, rect.width); @@ -2495,29 +2663,28 @@ namespace nana int txtoff = (essence_->item_size - essence_->text_height) / 2; auto i_categ = lister.cat_container().cbegin(); - std::advance(i_categ, essence_->scroll.offset_y.cat); + std::advance(i_categ, essence_->scroll.offset_y_dpl.cat); - auto idx = essence_->scroll.offset_y; + auto idx = essence_->scroll.offset_y_dpl; auto state = item_state::normal; - const bool sort_enabled = (essence_->lister.sort_index() != npos); //Here draws a root categ or a first drawing is not a categ. if(idx.cat == 0 || !idx.is_category()) { if (idx.cat == 0 && idx.is_category()) { - essence_->scroll.offset_y.item = 0; + essence_->scroll.offset_y_dpl.item = 0; idx.item = 0; } std::size_t size = i_categ->items.size(); - for(std::size_t offs = essence_->scroll.offset_y.item; offs < size; ++offs, ++idx.item) + for(std::size_t offs = essence_->scroll.offset_y_dpl.item; offs < size; ++offs, ++idx.item) { if(n-- == 0) break; state = (tracker == idx ? item_state::highlighted : item_state::normal); - _m_draw_item(i_categ->items[sort_enabled ? lister.absolute(index_pair(idx.cat, offs)) : offs], x, y, txtoff, header_w, rect, subitems, bgcolor,fgcolor, state); + _m_draw_item(i_categ->items[lister.absolute(index_pair(idx.cat, offs)) ], x, y, txtoff, header_w, rect, subitems, bgcolor,fgcolor, state); y += essence_->item_size; } @@ -2544,7 +2711,7 @@ namespace nana if(n-- == 0) break; state = (idx == tracker ? item_state::highlighted : item_state::normal); - _m_draw_item(i_categ->items[sort_enabled ? lister.absolute(index_pair(idx.cat, pos)) : pos], x, y, txtoff, header_w, rect, subitems, bgcolor, fgcolor, state); + _m_draw_item(i_categ->items[ lister.absolute(index_pair(idx.cat, pos))], x, y, txtoff, header_w, rect, subitems, bgcolor, fgcolor, state); y += essence_->item_size; ++idx.item; } @@ -2932,7 +3099,7 @@ namespace nana { auto & lister = essence_->lister; index_pair item_pos; - if (lister.forward(essence_->scroll.offset_y, ptr_where.second, item_pos)) + if (lister.forward(essence_->scroll.offset_y_dpl, ptr_where.second, item_pos)) { auto * item_ptr = (item_pos.is_item() ? &lister.at(item_pos) : nullptr); if(ptr_where.first == parts::lister) @@ -2941,7 +3108,7 @@ namespace nana if (!lister.single_selection()) { if (arg.shift) - lister.select_display_range(lister.last_selected, item_pos, sel); + lister.select_display_range(lister.last_selected_abs , item_pos, sel); else if (arg.ctrl) sel = !item_proxy(essence_, index_pair (item_pos.cat, lister.absolute(item_pos))).selected(); else @@ -2961,11 +3128,11 @@ namespace nana if (item_ptr->flags.selected) { lister.cancel_others_if_single_enabled(true, last_selected); - essence_->lister.last_selected = last_selected; + essence_->lister.last_selected_abs = last_selected; } - else if (essence_->lister.last_selected == last_selected) - essence_->lister.last_selected.set_both(npos); + else if (essence_->lister.last_selected_abs == last_selected) + essence_->lister.last_selected_abs.set_both(npos); } else if(!lister.single_selection()) lister.categ_selected(item_pos.cat, true); @@ -3007,6 +3174,7 @@ namespace nana } } + void trigger::mouse_up(graph_reference graph, const arg_mouse& arg) { using item_state = essence_t::item_state; @@ -3021,7 +3189,8 @@ namespace nana { if(essence_->lister.sort_index(essence_->pointer_where.second)) { - draw(); + essence_->trace_item_dpl({0,0}); + draw(); API::lazy_refresh(); } } @@ -3053,7 +3222,7 @@ namespace nana return; index_pair item_pos; - auto & offset_y = essence_->scroll.offset_y; + auto & offset_y = essence_->scroll.offset_y_dpl; auto & lister = essence_->lister; //Get the item which the mouse is placed. if (lister.forward(offset_y, essence_->pointer_where.second, item_pos)) @@ -3093,9 +3262,10 @@ namespace nana case keyboard::os_arrow_up: up = true; case keyboard::os_arrow_down: - essence_->lister.move_select(up); - essence_->trace_selected_item(); + // move_select(bool upwards=true, bool unselect_previous=true, bool trace_selected=false) + essence_->lister.move_select(up, !arg.shift, true); break; + case STR(' ') : { selection s; @@ -3104,6 +3274,28 @@ namespace nana item_proxy(essence_, i).check(ck); } break; + + case keyboard::copy: + { + nana::string str{STR("to_csv()")}; + //nana::system::dataexch().set(str); + return; + } + + case keyboard::os_pageup : + up = true; + case keyboard::os_pagedown: + { + auto& scrl = essence_->scroll.v; + if (! scrl.make_page_scroll(!up)) + return; + essence_->lister.select_for_all(false); + item_proxy {essence_, essence_->scroll_y_abs() } .select(true); + + break; + } + // case keyboard:: + default: return; } @@ -3129,7 +3321,23 @@ namespace nana } } - bool item_proxy::empty() const + /// the main porpose of this it to make obvious that item_proxy operate with absolute positions, and dont get moved during sort() + item_proxy item_proxy::from_display(essence_t *ess, const index_pair &relative) + { + return item_proxy{ess, ess->lister.absolute_pair(relative)}; + } + item_proxy item_proxy::from_display(const index_pair &relative) const + { + return item_proxy{ess_, ess_->lister.absolute_pair(relative)}; + } + + /// posible use: last_selected_display = last_selected.to_display().item; use with caution, it get invalidated after a sort() + index_pair item_proxy::to_display() const + { + return ess_->lister.relative_pair(pos_); + } + + bool item_proxy::empty() const { return !ess_; } @@ -3151,24 +3359,24 @@ namespace nana return cat_->items.at(pos_.item).flags.checked; } - item_proxy & item_proxy::select(bool s) + /// 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); - if(m.flags.selected != s) + auto & m = cat_->items.at(pos_.item); // a ref to the real item + if(m.flags.selected == s) return *this; // ignore if no change + m.flags.selected = s; // actually change selection + + arg_listbox arg{*this, s}; + ess_->lister.wd_ptr()->events().selected.emit(arg); + + if (m.flags.selected) { - m.flags.selected = s; - - arg_listbox arg{*this, s}; - ess_->lister.wd_ptr()->events().selected.emit(arg); - - if (m.flags.selected) - { - ess_->lister.cancel_others_if_single_enabled(true, pos_); //Cancel all selections except pos_ if single_selection is enabled. - ess_->lister.last_selected = pos_; - } - else if (ess_->lister.last_selected == pos_) - ess_->lister.last_selected.set_both(npos); + ess_->lister.cancel_others_if_single_enabled(true, pos_); //Cancel all selections except pos_ if single_selection is enabled. + ess_->lister.last_selected_abs = pos_; } + else if (ess_->lister.last_selected_abs == pos_) + ess_->lister.last_selected_abs.set_both(npos); + return *this; } @@ -3451,11 +3659,11 @@ namespace nana return end(); } - item_proxy cat_proxy::at(size_type pos) const + item_proxy cat_proxy::at(size_type pos_abs) const { - if(pos >= size()) + if(pos_abs >= size()) throw std::out_of_range("listbox.cat_proxy.at() invalid position"); - return item_proxy(ess_, index_pair(pos_, pos)); + return item_proxy(ess_, index_pair(pos_, pos_abs)); } item_proxy cat_proxy::back() const @@ -3466,11 +3674,13 @@ namespace nana return item_proxy(ess_, index_pair(pos_, cat_->items.size() - 1)); } - size_type cat_proxy::index_by_display_order(size_type order_pos) const + /// convert from display order to absolute (find the real item in that display pos) but without check from current active sorting, in fact using just the last sorting !!! + size_type cat_proxy::index_by_display_order(size_type display_order_pos) const { - return ess_->lister.index_by_display_order(pos_, order_pos); + return ess_->lister.index_by_display_order(pos_, display_order_pos); } + /// find display order for the real item but without check from current active sorting, in fact using just the last sorting !!! size_type cat_proxy::display_order(size_type pos) const { return ess_->lister.display_order(pos_, pos); @@ -3635,6 +3845,19 @@ namespace nana ess.update(); } + listbox& listbox::header_width(size_type pos, unsigned pixels) + { + auto & ess = _m_ess(); + ess.header.item_width(pos, pixels); + ess.update(); + return *this; + } + + unsigned listbox::header_width(size_type pos) const + { + return _m_ess().header.item_width(pos); + } + listbox::cat_proxy listbox::append(nana::string s) { internal_scope_guard lock; @@ -3681,9 +3904,10 @@ namespace nana return *this; } - listbox::item_proxy listbox::at(const index_pair& pos) const + /// from abs pos + listbox::item_proxy listbox::at(const index_pair& pos_abs) const { - return at(pos.cat).at(pos.item); + return at(pos_abs.cat).at(pos_abs.item); } void listbox::insert(const index_pair& pos, nana::string text) @@ -3721,11 +3945,11 @@ namespace nana { auto & ess = _m_ess(); ess.lister.clear(cat); - auto pos = ess.scroll_y(); + auto pos = ess.scroll_y_dpl(); if(pos.cat == cat) { pos.item = (pos.cat > 0 ? npos : 0); - ess.scroll_y(pos); + ess.set_scroll_y_dpl(pos); } ess.update(); } @@ -3734,9 +3958,9 @@ namespace nana { auto & ess = _m_ess(); ess.lister.clear(); - auto pos = ess.scroll_y(); + auto pos = ess.scroll_y_dpl(); pos.item = (pos.cat > 0 ? npos : 0); - ess.scroll_y(pos); + ess.set_scroll_y_dpl(pos); ess.update(); } @@ -3746,17 +3970,17 @@ namespace nana ess.lister.erase(cat); if(cat) { - auto pos = ess.scroll_y(); + auto pos = ess.scroll_y_dpl(); if(cat <= pos.cat) { if(pos.cat == ess.lister.size_categ()) --pos.cat; pos.item = npos; - ess.scroll_y(pos); + ess.set_scroll_y_dpl(pos); } } else - ess.scroll_y(index_pair()); + ess.set_scroll_y_dpl(index_pair()); ess.update(); } @@ -3764,7 +3988,7 @@ namespace nana { auto & ess = _m_ess(); ess.lister.erase(); - ess.scroll_y(index_pair()); + ess.scroll_y_abs(index_pair()); ess.update(); } @@ -3776,7 +4000,7 @@ namespace nana auto * ess = ip._m_ess(); auto _where = ip.pos(); ess->lister.erase(_where); - auto pos = ess->scroll_y(); + auto pos = ess->scroll_y_dpl(); if((pos.cat == _where.cat) && (_where.item <= pos.item)) { if(pos.item == 0) @@ -3786,7 +4010,7 @@ namespace nana } else --pos.item; - ess->scroll_y(pos); + ess->set_scroll_y_dpl(pos); } ess->update(); if(_where.item < ess->lister.size_item(_where.cat)) @@ -3799,7 +4023,8 @@ namespace nana _m_ess().header.column(col).weak_ordering = std::move(strick_ordering); } - void listbox::sort_col(size_type col, bool reverse) + /// sort() and ivalidate any existing reference from display position to absolute item, that is: after sort() display offset point to different items + void listbox::sort_col(size_type col, bool reverse) { _m_ess().lister.set_sort_index(col, reverse); } @@ -3809,6 +4034,7 @@ namespace nana return _m_ess().lister.sort_index(); } + /// potencially ivalidate any existing reference from display position to absolute item, that is: after sort() display offset point to different items void listbox::unsort() { _m_ess().lister.set_sort_index(npos, false); @@ -3819,10 +4045,10 @@ namespace nana return !_m_ess().lister.active_sort(!freeze); } - auto listbox::selected() const -> selection + auto listbox::selected() const -> selection // change to: selection selected(); { selection s; - _m_ess().lister.item_selected(s); + _m_ess().lister.item_selected(s); // absolute positions, no relative to display return std::move(s); } @@ -3838,7 +4064,7 @@ namespace nana return _m_ess().header.visible(); } - void listbox::move_select(bool upwards) + void listbox::move_select(bool upwards) ///