From 33321d73dec5555f21ea6690a687077645f73c64 Mon Sep 17 00:00:00 2001 From: Jinhao Date: Sat, 17 Jun 2017 10:48:24 +0800 Subject: [PATCH] fix logic and crash errors of listbox box selection --- source/gui/widgets/listbox.cpp | 224 +++++++++++++++------------------ 1 file changed, 100 insertions(+), 124 deletions(-) diff --git a/source/gui/widgets/listbox.cpp b/source/gui/widgets/listbox.cpp index c5d7aa46..9ac67c5c 100644 --- a/source/gui/widgets/listbox.cpp +++ b/source/gui/widgets/listbox.cpp @@ -1507,36 +1507,22 @@ namespace nana } template - std::vector> select_display_range_if(const index_pair& fr_abs, index_pair to_dpl, bool unselect_others, Pred pred) + std::vector> select_display_range_if(const index_pair& fr_abs, index_pair to_dpl, bool deselect_others, Pred pred) { - const auto already_selected = this->pick_items(true); + const index_pairs already_selected = (deselect_others ? this->pick_items(true) : index_pairs{}); + + if (to_dpl.empty()) + { + if (fr_abs.empty()) + return{}; + + to_dpl = this->last(); + } auto fr_dpl = (fr_abs.is_category() ? fr_abs : this->index_cast(fr_abs, false)); //Converts an absolute position to display position if (fr_dpl > to_dpl) std::swap(fr_dpl, to_dpl); - if (to_dpl.is_category()) - { - //Search backward until a last item of a category is found. - while (true) - { - auto msize = this->size_item(to_dpl.cat); - if (0 != msize) - { - to_dpl.item = msize - 1; - break; - } - - if (fr_dpl.cat == to_dpl.cat) - break; - - --(to_dpl.cat); - } - } - - if (!this->good(to_dpl)) - to_dpl = this->last(); - const auto begin = fr_dpl; const auto last = to_dpl; @@ -1576,9 +1562,9 @@ namespace nana } } - if (unselect_others) + if (deselect_others) { - //Unselects the already selected which is out of range [begin, last] + //Deselects the already selected which is out of range [begin, last] for (auto index : already_selected) { auto disp_order = this->index_cast(index, false); //converts an absolute position to a display position @@ -1935,6 +1921,7 @@ namespace nana bool auto_draw{true}; bool checkable{false}; bool if_image{false}; + bool deselect_deferred{ false }; //deselects items when mouse button is released. unsigned text_height; ::nana::listbox::export_options def_exp_options; @@ -2059,16 +2046,19 @@ namespace nana return{ r.x - origin.x, r.x - origin.x + static_cast(header.pixels()) }; } - void start_mouse_selection(const point& screen_pos) + void start_mouse_selection(const arg_mouse& arg) { - auto logic_pos = coordinate_cast(screen_pos, true); + auto logic_pos = coordinate_cast(arg.pos, true); mouse_selection.started = true; mouse_selection.begin_position = logic_pos; mouse_selection.end_position = logic_pos; - mouse_selection.already_selected = lister.pick_items(true); - + if (arg.ctrl || arg.shift) + { + mouse_selection.already_selected = lister.pick_items(true); + mouse_selection.reverse_selection = arg.ctrl; + } API::set_capture(*listbox_ptr, true); } @@ -2089,90 +2079,89 @@ namespace nana mouse_selection.end_position = logic_pos; + bool cancel_selections = true; + auto content_x = coordinate_cast({ columns_range().first, 0 }, true).x; - if ((std::max)(mouse_selection.end_position.x, mouse_selection.begin_position.x) <= content_x || - (std::min)(mouse_selection.end_position.x, mouse_selection.begin_position.x) >= content_x + static_cast(header.pixels()) - ) + if ((std::max)(mouse_selection.end_position.x, mouse_selection.begin_position.x) >= content_x && + (std::min)(mouse_selection.end_position.x, mouse_selection.begin_position.x) < content_x + static_cast(header.pixels())) { - lister.select_for_all(false); - return; - } + auto const begin_off = (std::max)((std::min)(mouse_selection.begin_position.y, mouse_selection.end_position.y), 0) / item_height(); - auto const begin_off = (std::max)((std::min)(mouse_selection.begin_position.y, mouse_selection.end_position.y), 0) / item_height(); - - auto begin = lister.advance(lister.first(), begin_off); - if (begin.empty()) - return; - - std::vector> selections; - - if ((mouse_selection.end_position.y < 0) || (lister.distance(lister.first(), begin) == begin_off)) - { - //The range [begin_off, last_off] is a range of box selection - auto last_off = (std::max)(mouse_selection.begin_position.y, mouse_selection.end_position.y) / item_height(); - auto last = lister.advance(lister.first(), last_off); - - selections = lister.select_display_range_if(begin, last, false, [this](const index_pair& abs_pos) { - if (this->mouse_selection.reverse_selection) - { - if(mouse_selection.already_selected.cend() != std::find(mouse_selection.already_selected.cbegin(), mouse_selection.already_selected.cend(), abs_pos)) - { - item_proxy{ this, abs_pos }.select(false); - return false; - } - } - return true; - }); - - for (auto & pair : selections) + auto begin = lister.advance(lister.first(), begin_off); + if (!begin.empty()) { - if (pair.second) - continue; + std::vector> selections; - if (mouse_selection.selections.cend() == - std::find(mouse_selection.selections.cbegin(), mouse_selection.selections.cend(), pair.first)) + if ((mouse_selection.end_position.y < 0) || (lister.distance(lister.first(), begin) == begin_off)) { - mouse_selection.selections.push_back(pair.first); - } - } + //The range [begin_off, last_off] is a range of box selection + auto last_off = (std::max)(mouse_selection.begin_position.y, mouse_selection.end_position.y) / item_height(); + auto last = lister.advance(lister.first(), last_off); + + selections = lister.select_display_range_if(begin, last, false, [this](const index_pair& abs_pos) { + if (this->mouse_selection.reverse_selection) + { + if (mouse_selection.already_selected.cend() != std::find(mouse_selection.already_selected.cbegin(), mouse_selection.already_selected.cend(), abs_pos)) + { + item_proxy{ this, abs_pos }.select(false); + return false; + } + } + return true; + }); + + for (auto & pair : selections) + { + if (pair.second) + continue; + + if (mouse_selection.selections.cend() == + std::find(mouse_selection.selections.cbegin(), mouse_selection.selections.cend(), pair.first)) + { + mouse_selection.selections.push_back(pair.first); + } + } #ifdef _MSC_VER - for(auto i = mouse_selection.selections.cbegin(); i != mouse_selection.selections.cend();) + for (auto i = mouse_selection.selections.cbegin(); i != mouse_selection.selections.cend();) #else - for(auto i = mouse_selection.selections.begin(); i != mouse_selection.selections.end();) + for(auto i = mouse_selection.selections.begin(); i != mouse_selection.selections.end();) #endif - { - if (selections.cend() == std::find_if(selections.cbegin(), selections.cend(), pred_mouse_selection{*i})) - { - item_proxy{ this, *i }.select(false); - i = mouse_selection.selections.erase(i); - } - else - ++i; - } + { + if (selections.cend() == std::find_if(selections.cbegin(), selections.cend(), pred_mouse_selection{ *i })) + { + item_proxy{ this, *i }.select(false); + i = mouse_selection.selections.erase(i); + } + else + ++i; + } - } - else - { - for (auto & pos : mouse_selection.selections) - { - item_proxy{ this, pos }.select(false); + cancel_selections = false; + } } + } + + if (cancel_selections) + { + if (!mouse_selection.already_selected.empty()) + { + for (auto & pos : mouse_selection.selections) + item_proxy(this, pos).select(false); + + //Don't restore the already selections if it is reverse selection(pressing shift). Behaves like Windows Explorer. + if (!mouse_selection.reverse_selection) + { + for (auto & abs_pos : mouse_selection.already_selected) + item_proxy(this, abs_pos).select(true); + } + } + else + lister.select_for_all(false); mouse_selection.selections.clear(); } - - if (mouse_selection.reverse_selection) - { - for (auto & abs_pos : mouse_selection.already_selected) - { - if (selections.empty() || (selections.cend() == std::find_if(selections.cbegin(), selections.cend(), pred_mouse_selection{ abs_pos }))) - { - item_proxy{ this, abs_pos }.select(true); - } - } - } } void stop_mouse_selection() noexcept @@ -3934,6 +3923,9 @@ namespace nana using item_state = essence::item_state; using parts = essence::parts; + //Cancel deferred deselection operation when mouse moves. + essence_->deselect_deferred = false; + bool need_refresh = false; point pos_in_header = arg.pos; @@ -4153,23 +4145,6 @@ namespace nana } update = true; } - else - { - //Blank area is clicked - - bool unselect_all = true; - if (!lister.single_status(true)) //not single selected - { - if (arg.ctrl || arg.shift) - { - essence_->mouse_selection.reverse_selection = arg.ctrl; - unselect_all = false; - } - } - - if(unselect_all) - update = lister.select_for_all(false); //unselect all items due to the blank area being clicked - } if(update) { @@ -4187,16 +4162,11 @@ namespace nana { //Start box selection if mulit-selection is enabled if (arg.is_left_button() && (!lister.single_status(true))) - essence_->start_mouse_selection(arg.pos); + essence_->start_mouse_selection(arg); - lister.select_for_all(false); - update = true; - if (good_list_r) - { - drawer_lister_->draw(list_r); - if (good_head_r) - drawer_header_->draw(graph, head_r); - } + //Deselection of all items is deferred to the mouse up event when ctrl or shift is not pressed + //Pressing ctrl or shift is to selects other items without deselecting current selections. + essence_->deselect_deferred = !(arg.ctrl || arg.shift); } if(update) @@ -4236,6 +4206,12 @@ namespace nana need_refresh = true; } + if (essence_->deselect_deferred) + { + essence_->deselect_deferred = false; + need_refresh |= (essence_->lister.select_for_all(false)); + } + if (need_refresh) { refresh(graph);