From e4b60148e76cc91cbf1c0b66bd715e9329f79490 Mon Sep 17 00:00:00 2001 From: qPCR4vir Date: Tue, 13 Nov 2018 19:01:20 +0100 Subject: [PATCH 01/16] listbox::move_column implemented by new es_header::move_to_view_pos () --- include/nana/gui/widgets/listbox.hpp | 6 ++- source/gui/widgets/listbox.cpp | 58 ++++++++++++++++++++-------- 2 files changed, 47 insertions(+), 17 deletions(-) diff --git a/include/nana/gui/widgets/listbox.hpp b/include/nana/gui/widgets/listbox.hpp index ecdb9be8..7622f331 100644 --- a/include/nana/gui/widgets/listbox.hpp +++ b/include/nana/gui/widgets/listbox.hpp @@ -1461,6 +1461,9 @@ the nana::detail::basic_window member pointer scheme /// Returns the number of columns size_type column_size() const; + /// Move column to view_position + void move_column(size_type abs_pos, size_type view_pos); + void column_resizable(bool resizable); bool column_resizable() const; void column_movable(bool); @@ -1517,7 +1520,8 @@ the nana::detail::basic_window member pointer scheme ///Sets a strict weak ordering comparer for a column void set_sort_compare( size_type col, - std::function strick_ordering); + std::function strick_ordering); /// 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); diff --git a/source/gui/widgets/listbox.cpp b/source/gui/widgets/listbox.cpp index deb6d881..fe0f96d0 100644 --- a/source/gui/widgets/listbox.cpp +++ b/source/gui/widgets/listbox.cpp @@ -17,6 +17,12 @@ * dankan1890(pr#158) * */ +#include +#include +#include +#include +#include +#include #include #include //for inline widget @@ -28,12 +34,6 @@ #include #include "skeletons/content_view.hpp" -#include -#include -#include -#include -#include - namespace nana { static void check_range(std::size_t pos, std::size_t size) @@ -130,17 +130,18 @@ namespace nana struct column : public column_interface { - native_string_type caption; - unsigned width_px; - std::pair range_width_px; + native_string_type caption; //< header title + unsigned width_px; //< column width in pixels + std::pair range_width_px; //< allowed witdh bool visible_state{ true }; - /// Absolute position of column when it was creating - size_type index; + + size_type index; //< Absolute position of column when it was created nana::align alignment{ nana::align::left }; - std::function weak_ordering; + std::function weak_ordering; std::shared_ptr font; ///< The exclusive column font @@ -186,18 +187,18 @@ namespace nana { } private: - //The definition is provided after essence + /// The definition is provided after essence void _m_refresh() noexcept; private: essence* const ess_; public: - //Implementation of column_interface + /// Implementation of column_interface unsigned width() const noexcept override { return width_px; } - // Sets the width and overrides the ranged width + /// Sets the width and overrides the ranged width void width(unsigned pixels) noexcept override { width_px = pixels; @@ -223,7 +224,7 @@ namespace nana } } - size_type position(bool disp_order) const noexcept override; //The definition is provided after essence + size_type position(bool disp_order) const noexcept override; //< The definition is provided after essence std::string text() const noexcept override { @@ -542,6 +543,24 @@ namespace nana return pos; } + + /// move col to view pos + void move_to_view_pos (size_type col, size_type view, bool front) noexcept + { + if (!front) view++; + if (view >= cont_.size() ) return; + + auto i = std::find_if( cont_.begin(), + cont_.end(), + [&](const column& c){return col==c.index;}); + + if (i==cont_.end()) return; + + auto col_from = *i; + cont_.erase(i); + cont_.insert(cont_.begin()+ view, col_from); + + } /// move the col originaly at "from" to the position currently in front (or after) the col originaly at index "to" invalidating some current index void move(size_type from, size_type to, bool front) noexcept { @@ -6049,5 +6068,12 @@ namespace nana internal_scope_guard lock; return _m_ess().content_view->scroll_operation(); } + + /// Move column to view_position + void listbox::move_column(size_type abs_pos, size_type view_pos) + { + internal_scope_guard lock; + return _m_ess().header.move_to_view_pos(abs_pos, view_pos, true); + } //end class listbox }//end namespace nana From c0de9ee4602e86757985b19375cc301de9fff01d Mon Sep 17 00:00:00 2001 From: qPCR4vir Date: Tue, 13 Nov 2018 19:05:19 +0100 Subject: [PATCH 02/16] listbox::reorder_columns implemented by move_column --- include/nana/gui/widgets/listbox.hpp | 11 ++++++- source/gui/widgets/listbox.cpp | 44 ++++++++++++++++++++++++---- 2 files changed, 48 insertions(+), 7 deletions(-) diff --git a/include/nana/gui/widgets/listbox.hpp b/include/nana/gui/widgets/listbox.hpp index 7622f331..cae6ec92 100644 --- a/include/nana/gui/widgets/listbox.hpp +++ b/include/nana/gui/widgets/listbox.hpp @@ -1464,7 +1464,16 @@ the nana::detail::basic_window member pointer scheme /// Move column to view_position void move_column(size_type abs_pos, size_type view_pos); - void column_resizable(bool resizable); + /// Sort columns in range first_col to last_col inclusive using the values from a row + void reorder_columns(size_type first_col, + size_type last_col, + index_pair row, bool reverse, + std::function comp); + + void column_resizable(bool resizable); bool column_resizable() const; void column_movable(bool); bool column_movable() const; diff --git a/source/gui/widgets/listbox.cpp b/source/gui/widgets/listbox.cpp index fe0f96d0..e6c7e97a 100644 --- a/source/gui/widgets/listbox.cpp +++ b/source/gui/widgets/listbox.cpp @@ -6069,11 +6069,43 @@ namespace nana return _m_ess().content_view->scroll_operation(); } - /// Move column to view_position - void listbox::move_column(size_type abs_pos, size_type view_pos) - { - internal_scope_guard lock; - return _m_ess().header.move_to_view_pos(abs_pos, view_pos, true); - } + /// Move column to view_position + void listbox::move_column(size_type abs_pos, size_type view_pos) + { + internal_scope_guard lock; + return _m_ess().header.move_to_view_pos(abs_pos, view_pos, true); + } + + /// Sort columns in range first_col to last_col inclusive using a row + void listbox::reorder_columns(size_type first_col, + size_type last_col, + index_pair row, bool reverse, + std::function comp) + { + if (first_col<0 || last_col<=first_col) + return; + if (last_col >= column_size()) + return; + std::vector new_idx; + for(size_type i=first_col; i<=last_col; ++i) new_idx.push_back(i); + const item_proxy & ip_row=this->at(row); + internal_scope_guard lock; + const nana::any *pnany=_m_ess().lister.anyobj(row,false); + std::sort(new_idx.begin(), new_idx.end(), [&](size_type col1, + size_type col2) + { + return comp(ip_row.text(col1), col1, + ip_row.text(col2), col2, + pnany, reverse); + }); + for(size_t i=0; i Date: Tue, 13 Nov 2018 19:42:07 +0100 Subject: [PATCH 03/16] update --- source/gui/widgets/listbox.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/source/gui/widgets/listbox.cpp b/source/gui/widgets/listbox.cpp index e6c7e97a..dd970a65 100644 --- a/source/gui/widgets/listbox.cpp +++ b/source/gui/widgets/listbox.cpp @@ -6074,6 +6074,7 @@ namespace nana { internal_scope_guard lock; return _m_ess().header.move_to_view_pos(abs_pos, view_pos, true); + _m_ess().update(); } /// Sort columns in range first_col to last_col inclusive using a row @@ -6105,6 +6106,7 @@ namespace nana { move_column(new_idx[i],i+first_col); } + _m_ess().update(); } //end class listbox From 1d2c7f85f484cee0a387794f08cd869ed5e449a6 Mon Sep 17 00:00:00 2001 From: James Bremner Date: Tue, 18 Dec 2018 17:05:45 -0500 Subject: [PATCH 04/16] Partial implementation of missing nana::typeface() --- include/nana/gui/widgets/group.hpp | 9 +- source/gui/widgets/group.cpp | 480 +++++++++++++++-------------- 2 files changed, 254 insertions(+), 235 deletions(-) diff --git a/include/nana/gui/widgets/group.hpp b/include/nana/gui/widgets/group.hpp index ba48928b..c453c168 100644 --- a/include/nana/gui/widgets/group.hpp +++ b/include/nana/gui/widgets/group.hpp @@ -3,8 +3,8 @@ * Nana C++ Library(http://www.nanaro.org) * Copyright(C) 2015-2018 Jinhao(cnjinhao@hotmail.com) * - * Distributed under the Boost Software License, Version 1.0. - * (See accompanying file LICENSE_1_0.txt or copy at + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at * http://www.boost.org/LICENSE_1_0.txt) * * @file: nana/gui/widgets/group.hpp @@ -77,6 +77,9 @@ namespace nana{ /// Determines whether a specified option is checked, it throws an out_of_range if !(pos < number of options) bool option_checked(std::size_t pos) const; + /// Change typeface of caption label ( does not effect child widgets ) + void typeface( const nana::paint::font& font ); + group& enable_format_caption(bool format); group& collocate() noexcept; @@ -86,7 +89,7 @@ namespace nana{ void field_display(const char* field_name, bool display); /// Widget* create_child(const char* field, Args && ... args) { diff --git a/source/gui/widgets/group.cpp b/source/gui/widgets/group.cpp index 6d697a62..1f3d24dd 100644 --- a/source/gui/widgets/group.cpp +++ b/source/gui/widgets/group.cpp @@ -3,8 +3,8 @@ * Nana C++ Library(http://www.nanaro.org) * Copyright(C) 2015-2018 Jinhao(cnjinhao@hotmail.com) * - * Distributed under the Boost Software License, Version 1.0. - * (See accompanying file LICENSE_1_0.txt or copy at + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at * http://www.boost.org/LICENSE_1_0.txt) * * @file: nana/gui/widgets/group.cpp @@ -14,7 +14,7 @@ * @brief group is a widget used to visually group and layout other widgets. * * @contributor: - * dankan1890(https://github.com/dankan1890) + * dankan1890(https://github.com/dankan1890) */ @@ -27,300 +27,316 @@ if(empty()) \ throw std::logic_error("the group is invalid"); -namespace nana{ +namespace nana +{ - static const char* field_title = "__nana_group_title__"; - static const char* field_options = "__nana_group_options__"; +static const char* field_title = "__nana_group_title__"; +static const char* field_options = "__nana_group_options__"; - struct group::implement - { - label caption; - align caption_align{ align::left }; - place place_content; - unsigned gap{2}; - std::string usr_div_str; +struct group::implement +{ + label caption; + align caption_align{ align::left }; + place place_content; + unsigned gap{2}; + std::string usr_div_str; - nana::size caption_dimension; + nana::size caption_dimension; - std::vector> options; - radio_group * radio_logic{nullptr}; + std::vector> options; + radio_group * radio_logic{nullptr}; - implement() = default; + implement() = default; - implement(window grp_panel, ::std::string titel, bool vsb, unsigned gap=2) - : caption (grp_panel, std::move(titel), vsb), - place_content{grp_panel}, - gap{gap} - { - } + implement(window grp_panel, ::std::string titel, bool vsb, unsigned gap=2) + : caption (grp_panel, std::move(titel), vsb), + place_content{grp_panel}, + gap{gap} + { + } - void create(window pnl) - { - caption.create(pnl); - caption.caption(""); - place_content.bind(pnl); + void create(window pnl) + { + caption.create(pnl); + caption.caption(""); + place_content.bind(pnl); - if (!radio_logic) - radio_logic = new radio_group; - } + if (!radio_logic) + radio_logic = new radio_group; + } - void update_div() - { - const std::size_t padding = 10; - caption_dimension = caption.measure(1000); - caption_dimension.width += 1; + void update_div() + { + const std::size_t padding = 10; + caption_dimension = caption.measure(1000); + caption_dimension.width += 1; - std::string div = "vert margin=[0," + std::to_string(gap) + "," + std::to_string(gap + 5) + "," + std::to_string(gap) + "]"; + std::string div = "vert margin=[0," + std::to_string(gap) + "," + std::to_string(gap + 5) + "," + std::to_string(gap) + "]"; - div += ""; - else - div += "<>"; //right or center + if (align::left == caption_align) + div += ""; + else + div += "<>"; //right or center - div += "<" + std::string{ field_title } + " weight=" + std::to_string(caption_dimension.width) + ">"; + div += "<" + std::string{ field_title } + " weight=" + std::to_string(caption_dimension.width) + ">"; - if (align::right == caption_align) - div += ""; - else if (align::center == caption_align) - div += "<>"; + if (align::right == caption_align) + div += ""; + else if (align::center == caption_align) + div += "<>"; - div += "><"; + div += "><"; - if (!usr_div_str.empty()) - div += "<" + usr_div_str + ">>"; - else - div += ">"; + if (!usr_div_str.empty()) + div += "<" + usr_div_str + ">>"; + else + div += ">"; - place_content.div(div.c_str()); + place_content.div(div.c_str()); - if (options.empty()) - place_content.field_display(field_options, false); + if (options.empty()) + place_content.field_display(field_options, false); - if (caption.caption().empty()) - place_content.field_display(field_title, false); - } - }; + if (caption.caption().empty()) + place_content.field_display(field_title, false); + } +}; - group::group() - : impl_(new implement) - { - } +group::group() + : impl_(new implement) +{ +} - group::group(window parent, const rectangle& r, bool vsb) - : group() - { - create(parent, r, vsb); - } +group::group(window parent, const rectangle& r, bool vsb) + : group() +{ + create(parent, r, vsb); +} - using groupbase_type = widget_object; +using groupbase_type = widget_object; - group::group(window parent, ::std::string titel, bool formatted, unsigned gap, const rectangle& r, bool vsb) - : group(parent, r, vsb) - { - this->bgcolor(API::bgcolor(parent)); +group::group(window parent, ::std::string titel, bool formatted, unsigned gap, const rectangle& r, bool vsb) + : group(parent, r, vsb) +{ + this->bgcolor(API::bgcolor(parent)); - impl_.reset(new implement(*this, std::move(titel), vsb, gap)); + impl_.reset(new implement(*this, std::move(titel), vsb, gap)); - impl_->caption.format(formatted); - _m_init(); - } + impl_->caption.format(formatted); + _m_init(); +} - group::~group() - { - delete impl_->radio_logic; - } +group::~group() +{ + delete impl_->radio_logic; +} - checkbox& group::add_option(std::string text) - { - _THROW_IF_EMPTY() +checkbox& group::add_option(std::string text) +{ + _THROW_IF_EMPTY() #ifdef _nana_std_has_emplace_return_type - auto & opt = impl_->options.emplace_back(new checkbox{ handle() }); + auto & opt = impl_->options.emplace_back(new checkbox { handle() }); #else - impl_->options.emplace_back(new checkbox(handle())); - auto & opt = impl_->options.back(); + impl_->options.emplace_back(new checkbox(handle())); + auto & opt = impl_->options.back(); #endif - opt->transparent(true); - opt->caption(std::move(text)); - impl_->place_content[field_options] << *opt; - impl_->place_content.field_display(field_options, true); - impl_->place_content.collocate(); + opt->transparent(true); + opt->caption(std::move(text)); + impl_->place_content[field_options] << *opt; + impl_->place_content.field_display(field_options, true); + impl_->place_content.collocate(); - if (impl_->radio_logic) - impl_->radio_logic->add(*opt); + if (impl_->radio_logic) + impl_->radio_logic->add(*opt); - return *impl_->options.back(); - } + return *impl_->options.back(); +} - void group::caption_align(align position) - { - if (position != impl_->caption_align) - { - impl_->caption_align = position; - impl_->update_div(); - impl_->place_content.collocate(); - API::refresh_window(*this); - } - } +void group::caption_align(align position) +{ + if (position != impl_->caption_align) + { + impl_->caption_align = position; + impl_->update_div(); + impl_->place_content.collocate(); + API::refresh_window(*this); + } +} - group& group::radio_mode(bool enable) - { - _THROW_IF_EMPTY() +group& group::radio_mode(bool enable) +{ + _THROW_IF_EMPTY() - if (enable) - { - //Create radio_group if it is null - if (!impl_->radio_logic) - impl_->radio_logic = new ::nana::radio_group; + if (enable) + { + //Create radio_group if it is null + if (!impl_->radio_logic) + impl_->radio_logic = new ::nana::radio_group; - //add all options into the radio_group - for (auto & opt : impl_->options) - impl_->radio_logic->add(*opt); - } - else - { - delete impl_->radio_logic; - impl_->radio_logic = nullptr; - } - return *this; - } + //add all options into the radio_group + for (auto & opt : impl_->options) + impl_->radio_logic->add(*opt); + } + else + { + delete impl_->radio_logic; + impl_->radio_logic = nullptr; + } + return *this; +} - std::size_t group::option() const - { - _THROW_IF_EMPTY(); +std::size_t group::option() const +{ + _THROW_IF_EMPTY(); - if (impl_->radio_logic) - return impl_->radio_logic->checked(); + if (impl_->radio_logic) + return impl_->radio_logic->checked(); - throw std::logic_error("the radio_mode of the group is disabled"); - } + throw std::logic_error("the radio_mode of the group is disabled"); +} - bool group::option_checked(std::size_t pos) const - { - _THROW_IF_EMPTY(); - return impl_->options.at(pos)->checked(); - } +bool group::option_checked(std::size_t pos) const +{ + _THROW_IF_EMPTY(); + return impl_->options.at(pos)->checked(); +} - group& group::enable_format_caption(bool format) - { - impl_->caption.format(format); - return *this; - } +void group::typeface( const nana::paint::font& font ) +{ + // change typeface of caption label + impl_->caption.typeface( font ); - group& group::collocate() noexcept - { - impl_->place_content.collocate(); - return *this; - } + /* change size of caption label - group& group::div(const char* div_str) noexcept - { - if (div_str) - impl_->usr_div_str = div_str; - else - impl_->usr_div_str.clear(); + The caption may be changed AFTER this call + so the neccessary label size is unknown + set it to 80% of the current widget width and 50 pixels + */ + impl_->caption.move( rectangle(0,0,size().width * 0.8,50)); +} - impl_->update_div(); - return *this; - } +group& group::enable_format_caption(bool format) +{ + impl_->caption.format(format); + return *this; +} - group::field_reference group::operator[](const char* field) - { - return impl_->place_content.field(field); - } +group& group::collocate() noexcept +{ + impl_->place_content.collocate(); + return *this; +} - void group::field_display(const char* field_name, bool display) - { - impl_->place_content.field_display(field_name, display); - } +group& group::div(const char* div_str) noexcept +{ + if (div_str) + impl_->usr_div_str = div_str; + else + impl_->usr_div_str.clear(); - bool group::field_display(const char* field_name) const - { - return impl_->place_content.field_display(field_name); - } + impl_->update_div(); + return *this; +} - void group::erase(window handle) - { - impl_->place_content.erase(handle); - } +group::field_reference group::operator[](const char* field) +{ + return impl_->place_content.field(field); +} - void group::_m_add_child(const char* field, widget* wdg) - { - impl_->place_content[field] << wdg->handle(); - } +void group::field_display(const char* field_name, bool display) +{ + impl_->place_content.field_display(field_name, display); +} - void group::_m_init() - { - this->div(nullptr); +bool group::field_display(const char* field_name) const +{ + return impl_->place_content.field_display(field_name); +} - auto & outter = impl_->place_content; +void group::erase(window handle) +{ + impl_->place_content.erase(handle); +} - outter[field_title] << impl_->caption; - outter.collocate(); +void group::_m_add_child(const char* field, widget* wdg) +{ + impl_->place_content[field] << wdg->handle(); +} - impl_->caption.transparent(true); - color pbg = API::bgcolor(this->parent()); - impl_->caption.bgcolor(pbg.blend(colors::black, 0.025)); +void group::_m_init() +{ + this->div(nullptr); - this->bgcolor(pbg.blend(colors::black, 0.05)); + auto & outter = impl_->place_content; - drawing dw(*this); + outter[field_title] << impl_->caption; + outter.collocate(); - //When the group is resized, the drawing is called before moving the caption, but - //the drawing of group requires the lastest position of caption for gradual rectangle. - //For the requirement, a move event handler is required for listning the change of caption's position. - impl_->caption.events().move([this](const arg_move&){ - if (align::left != impl_->caption_align) - API::refresh_window(*this); - }); + impl_->caption.transparent(true); + color pbg = API::bgcolor(this->parent()); + impl_->caption.bgcolor(pbg.blend(colors::black, 0.025)); - // This drawing function is owner by the onwer of dw (the outer panel of the group widget), not by dw !! - dw.draw([this](paint::graphics& graph) - { - auto gap_px = impl_->gap - 1; + this->bgcolor(pbg.blend(colors::black, 0.05)); - auto const top_round_line = static_cast(impl_->caption_dimension.height) / 2; + drawing dw(*this); - graph.rectangle(true, API::bgcolor(this->parent())); - graph.round_rectangle(rectangle(point(gap_px, top_round_line), - nana::size(graph.width() - 2 * gap_px, graph.height() - top_round_line - gap_px) - ), - 3, 3, this->scheme().border, true, this->bgcolor()); + //When the group is resized, the drawing is called before moving the caption, but + //the drawing of group requires the lastest position of caption for gradual rectangle. + //For the requirement, a move event handler is required for listning the change of caption's position. + impl_->caption.events().move([this](const arg_move&) + { + if (align::left != impl_->caption_align) + API::refresh_window(*this); + }); - auto opt_r = API::window_rectangle(impl_->caption); - if (opt_r) - { - rectangle grad_r{ opt_r->position(), nana::size{ opt_r->width + 4, static_cast(top_round_line - opt_r->y) } }; + // This drawing function is owner by the onwer of dw (the outer panel of the group widget), not by dw !! + dw.draw([this](paint::graphics& graph) + { + auto gap_px = impl_->gap - 1; - grad_r.y += top_round_line*2 / 3; - grad_r.x -= 2; + auto const top_round_line = static_cast(impl_->caption_dimension.height) / 2; - graph.gradual_rectangle(grad_r, - API::bgcolor(this->parent()), this->bgcolor(), true - ); - } - }); - } + graph.rectangle(true, API::bgcolor(this->parent())); + graph.round_rectangle(rectangle(point(gap_px, top_round_line), + nana::size(graph.width() - 2 * gap_px, graph.height() - top_round_line - gap_px) + ), + 3, 3, this->scheme().border, true, this->bgcolor()); - void group::_m_complete_creation() - { - widget::_m_complete_creation(); - impl_->create(handle()); - _m_init(); - } + auto opt_r = API::window_rectangle(impl_->caption); + if (opt_r) + { + rectangle grad_r{ opt_r->position(), nana::size{ opt_r->width + 4, static_cast(top_round_line - opt_r->y) } }; - auto group::_m_caption() const noexcept -> native_string_type - { - return impl_->caption.caption_native(); - } + grad_r.y += top_round_line*2 / 3; + grad_r.x -= 2; - void group::_m_caption(native_string_type&& str) - { - impl_->caption.caption(std::move(str)); - impl_->update_div(); - impl_->place_content.collocate(); - } + graph.gradual_rectangle(grad_r, + API::bgcolor(this->parent()), this->bgcolor(), true + ); + } + }); +} + +void group::_m_complete_creation() +{ + widget::_m_complete_creation(); + impl_->create(handle()); + _m_init(); +} + +auto group::_m_caption() const noexcept -> native_string_type +{ + return impl_->caption.caption_native(); +} + +void group::_m_caption(native_string_type&& str) +{ + impl_->caption.caption(std::move(str)); + impl_->update_div(); + impl_->place_content.collocate(); +} }//end namespace nana From e963c29646085f709782a89f5eab98b64e3517fe Mon Sep 17 00:00:00 2001 From: Jinhao Date: Fri, 4 Jan 2019 21:58:48 +0800 Subject: [PATCH 05/16] fix crash occurs when switch between radio button to checkbox(#375) --- include/nana/gui/widgets/checkbox.hpp | 1 + source/gui/widgets/checkbox.cpp | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/include/nana/gui/widgets/checkbox.hpp b/include/nana/gui/widgets/checkbox.hpp index 57239d20..1a75c91a 100644 --- a/include/nana/gui/widgets/checkbox.hpp +++ b/include/nana/gui/widgets/checkbox.hpp @@ -104,6 +104,7 @@ namespace drawerbase struct element_tag { checkbox * uiobj; + event_handle eh_clicked; event_handle eh_checked; event_handle eh_destroy; event_handle eh_keyboard; diff --git a/source/gui/widgets/checkbox.cpp b/source/gui/widgets/checkbox.cpp index a43cd908..3148ee88 100644 --- a/source/gui/widgets/checkbox.cpp +++ b/source/gui/widgets/checkbox.cpp @@ -216,6 +216,7 @@ namespace nana{ namespace drawerbase { e.uiobj->radio(false); e.uiobj->react(true); + API::umake_event(e.eh_clicked); API::umake_event(e.eh_checked); API::umake_event(e.eh_destroy); API::umake_event(e.eh_keyboard); @@ -232,7 +233,7 @@ namespace nana{ namespace drawerbase el.uiobj = &uiobj; - uiobj.events().checked.connect_unignorable([this](const arg_checkbox& arg) + el.eh_checked = uiobj.events().checked.connect_unignorable([this](const arg_checkbox& arg) { if (arg.widget->checked()) { @@ -244,7 +245,7 @@ namespace nana{ namespace drawerbase } }, true); - el.eh_checked = uiobj.events().click.connect_unignorable([this](const arg_click& arg) + el.eh_clicked = uiobj.events().click.connect_unignorable([this](const arg_click& arg) { for (auto & i : ui_container_) i.uiobj->check(arg.window_handle == i.uiobj->handle()); From 96f62a7b0835b323aa09ca8d20792418cef7d1cf Mon Sep 17 00:00:00 2001 From: Jinhao Date: Sun, 6 Jan 2019 08:29:43 +0800 Subject: [PATCH 06/16] fix bug that text_editor::text use \n\r as end of line --- source/gui/widgets/skeletons/text_editor.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/gui/widgets/skeletons/text_editor.cpp b/source/gui/widgets/skeletons/text_editor.cpp index 6a5251b4..f53a5837 100644 --- a/source/gui/widgets/skeletons/text_editor.cpp +++ b/source/gui/widgets/skeletons/text_editor.cpp @@ -1,7 +1,7 @@ /* * A text editor implementation * Nana C++ Library(http://www.nanapro.org) -* Copyright(C) 2003-2018 Jinhao(cnjinhao@hotmail.com) +* Copyright(C) 2003-2019 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -1728,7 +1728,7 @@ namespace nana { str = impl_->textbase.getline(0); for (std::size_t i = 1; i < lines; ++i) { - str += L"\n\r"; + str += L"\r\n"; str += impl_->textbase.getline(i); } } From 4c3455648f7ed44f7eb62d3578ee0f83801bcb2b Mon Sep 17 00:00:00 2001 From: besh81 Date: Tue, 8 Jan 2019 17:31:39 +0100 Subject: [PATCH 07/16] spinbox: if not editable keep prefix and suffix when gain focus --- source/gui/widgets/spinbox.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/gui/widgets/spinbox.cpp b/source/gui/widgets/spinbox.cpp index 6ed1f3ed..49ae0347 100644 --- a/source/gui/widgets/spinbox.cpp +++ b/source/gui/widgets/spinbox.cpp @@ -467,7 +467,7 @@ namespace nana std::wstring text; - if (API::is_focus_ready(editor_->window_handle())) + if (API::is_focus_ready(editor_->window_handle()) && editor_->attr().editable) text = to_wstring(range_->value()); else text = to_wstring(modifier_.prefix + range_->value() + modifier_.suffix); From 26c28ae4becac7b7029152993ce3eac940c3d998 Mon Sep 17 00:00:00 2001 From: James Bremner Date: Wed, 16 Jan 2019 16:43:05 -0500 Subject: [PATCH 08/16] Slider value handles -ve values by clipping to zero see http://nanapro.org/en-us/forum/index.php?u=/topic/1092/ggslider-value-range --- include/nana/gui/widgets/slider.hpp | 8 +++++++- source/gui/widgets/slider.cpp | 24 ++++++++++++++---------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/include/nana/gui/widgets/slider.hpp b/include/nana/gui/widgets/slider.hpp index 8ce9602b..2fc015ce 100644 --- a/include/nana/gui/widgets/slider.hpp +++ b/include/nana/gui/widgets/slider.hpp @@ -151,7 +151,13 @@ namespace nana bool vertical() const; void maximum(unsigned); unsigned maximum() const; - void value(unsigned); + + /** Set slider value + @param[in] v new value for slider. + v will be clipped to the range 0 to maximum + */ + void value(int ); + unsigned value() const; unsigned move_step(bool forward); ///< Increase or decrease the value of slider. unsigned adorn() const; diff --git a/source/gui/widgets/slider.cpp b/source/gui/widgets/slider.cpp index 67a67631..572eba99 100644 --- a/source/gui/widgets/slider.cpp +++ b/source/gui/widgets/slider.cpp @@ -245,7 +245,7 @@ namespace nana }; public: enum class parts{none, bar, slider}; - + using graph_reference = drawer_trigger::graph_reference; model() @@ -256,7 +256,7 @@ namespace nana proto_.renderer = pat::cloneable{interior_renderer{}}; attr_.seek_dir = seekdir::bilateral; - + attr_.is_draw_adorn = false; attr_.vcur = 0; attr_.vmax = 10; @@ -361,7 +361,7 @@ namespace nana parts seek_where(::nana::point pos) const { nana::rectangle r = _m_bar_area(); - + if (attr_.slider.vert) { std::swap(pos.x, pos.y); @@ -373,7 +373,7 @@ namespace nana return parts::slider; sdpos = static_cast(attr_.slider.weight) / 2; - + if (sdpos <= pos.x && pos.x < sdpos + static_cast(r.width)) { if(pos.y < r.bottom()) @@ -446,7 +446,7 @@ namespace nana bool move_slider(const ::nana::point& pos) { int adorn_pos = slider_state_.snap_pos + (attr_.slider.vert ? pos.y : pos.x) - slider_state_.refpos.x; - + if (adorn_pos > 0) { int range = static_cast(_m_range()); @@ -691,7 +691,7 @@ namespace nana window wd; nana::slider * widget; }other_; - + struct prototype_tag { pat::cloneable renderer; @@ -759,9 +759,9 @@ namespace nana void trigger::mouse_move(graph_reference graph, const arg_mouse& arg) { // check if slider is disabled - if(!API::get_widget(arg.window_handle)->enabled()) + if(!API::get_widget(arg.window_handle)->enabled()) return; // do nothing - + bool updated = false; if (model_ptr_->if_trace_slider()) { @@ -804,7 +804,7 @@ namespace nana //class slider slider::slider(){} - + slider::slider(window wd, bool visible) { create(wd, rectangle(), visible); @@ -844,10 +844,14 @@ namespace nana return get_drawer_trigger().get_model()->attribute().vmax; } - void slider::value(unsigned v) + void slider::value(int v) { if(handle()) { + // limit to positive values, vcur expects unsigned + if( v < 0 ) + v = 0; + if(get_drawer_trigger().get_model()->vcur(v)) API::refresh_window(handle()); } From 9a523366459d1dfc022ccf341c3e7858d79c48af Mon Sep 17 00:00:00 2001 From: Ariel Vina-Rodriguez Date: Fri, 18 Jan 2019 11:09:16 +0100 Subject: [PATCH 09/16] typos --- source/gui/widgets/listbox.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/source/gui/widgets/listbox.cpp b/source/gui/widgets/listbox.cpp index dd970a65..dcdd0eca 100644 --- a/source/gui/widgets/listbox.cpp +++ b/source/gui/widgets/listbox.cpp @@ -132,7 +132,7 @@ namespace nana { native_string_type caption; //< header title unsigned width_px; //< column width in pixels - std::pair range_width_px; //< allowed witdh + std::pair range_width_px; //< allowed width bool visible_state{ true }; @@ -441,7 +441,7 @@ namespace nana throw std::invalid_argument("listbox: invalid header index"); } - /// find and return a ref to the column that originaly was at position "pos" previous to any list reorganization. + /// find and return a ref to the column that originally was at position "pos" previous to any list reorganization. column& at(size_type pos, bool disp_order = false) { check_range(pos, cont_.size()); @@ -508,7 +508,7 @@ namespace nana return{ left, 0 }; } - /// return the original index of the visible col currently before(in front of) or after the col originaly at index "index" + /// return the original index of the visible col currently before(in front of) or after the col originally at index "index" size_type next(size_type index) const noexcept { bool found_me = false; @@ -561,7 +561,7 @@ namespace nana cont_.insert(cont_.begin()+ view, col_from); } - /// move the col originaly at "from" to the position currently in front (or after) the col originaly at index "to" invalidating some current index + /// move the col originally at "from" to the position currently in front (or after) the col originally at index "to" invalidating some current index void move(size_type from, size_type to, bool front) noexcept { if ((from == to) || (from >= cont_.size()) || (to >= cont_.size())) From 5935ce20360193fae16a2729472762d1f4fa6c9b Mon Sep 17 00:00:00 2001 From: Jinhao Date: Sat, 19 Jan 2019 03:09:52 +0800 Subject: [PATCH 10/16] fix bug that listbox item automatically deselected item automatically are deselected when release mouse after mouse selection(develop-1.7) --- source/gui/widgets/listbox.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/source/gui/widgets/listbox.cpp b/source/gui/widgets/listbox.cpp index 4f93a660..0c797c57 100644 --- a/source/gui/widgets/listbox.cpp +++ b/source/gui/widgets/listbox.cpp @@ -1,7 +1,7 @@ /* * A List Box Implementation * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2018 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2019 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -4110,11 +4110,13 @@ namespace nana essence_->draw_peripheral(); } + // In mouse move event, it cancels the msup_deselect if the listbox is draggable or it started the mouse selection. void trigger::mouse_move(graph_reference graph, const arg_mouse& arg) { using item_state = essence::item_state; using parts = essence::parts; + //Don't deselect the items if the listbox is draggable if ((operation_states::msup_deselect == essence_->operation.state) && API::dev::window_draggable(arg.window_handle)) essence_->operation.state = operation_states::none; @@ -4168,6 +4170,9 @@ namespace nana if (essence_->mouse_selection.started) { essence_->update_mouse_selection(arg.pos); + + //Don't deselect items if the mouse selection is started + essence_->operation.state = operation_states::none; need_refresh = true; } @@ -4411,7 +4416,7 @@ namespace nana essence_->stop_mouse_selection(); need_refresh = true; } - + if (operation_states::msup_deselect == essence_->operation.state) { essence_->operation.state = operation_states::none; From 8ecae7402ccfea7b8f951213bb680a9e9d7fbfd7 Mon Sep 17 00:00:00 2001 From: Jinhao Date: Mon, 21 Jan 2019 22:35:44 +0800 Subject: [PATCH 11/16] fix bug that window has wrong position if it has an ancestor --- source/gui/detail/native_window_interface.cpp | 30 ++++++++----------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/source/gui/detail/native_window_interface.cpp b/source/gui/detail/native_window_interface.cpp index fcf7f695..cf01e33d 100644 --- a/source/gui/detail/native_window_interface.cpp +++ b/source/gui/detail/native_window_interface.cpp @@ -1,7 +1,7 @@ /* * Platform Implementation * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2018 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2019 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -29,6 +29,7 @@ #include "../../paint/image_accessor.hpp" + namespace nana{ namespace detail{ @@ -199,7 +200,7 @@ namespace nana{ namespace x11_wait { static Bool configure(Display *disp, XEvent *evt, char *arg) - { + { return disp && evt && arg && (evt->type == ConfigureNotify) && (evt->xconfigure.window == *reinterpret_cast(arg)); } @@ -1019,15 +1020,11 @@ namespace nana{ auto const owner = restrict::spec.get_owner(wd); if(owner && (owner != reinterpret_cast(restrict::spec.root_window()))) { - auto origin = window_position(owner); -#if 0 - x += origin.x; - y += origin.y; -#else - auto owner_extents = window_frame_extents(owner); - x += origin.x + owner_extents.left; - y += origin.y + owner_extents.top; -#endif + int origin_x, origin_y; + Window child_useless_for_API; + ::XTranslateCoordinates(disp, reinterpret_cast(owner), restrict::spec.root_window(), 0, 0, &origin_x, &origin_y, &child_useless_for_API); + x += origin_x; + y += origin_y; } ::XMoveWindow(disp, reinterpret_cast(wd), x, y); @@ -1074,7 +1071,6 @@ namespace nana{ XSizeHints hints; nana::detail::platform_scope_guard psg; - //Returns if the requested rectangle is same with the current rectangle. //In some X-Server versions/implementations, XMapWindow() doesn't generate //a ConfigureNotify if the requested rectangle is same with the current rectangle. @@ -1114,11 +1110,11 @@ namespace nana{ auto const owner = restrict::spec.get_owner(wd); if(owner && (owner != reinterpret_cast(restrict::spec.root_window()))) { - auto origin = window_position(owner); - - auto owner_extents = window_frame_extents(owner); - x += origin.x + owner_extents.left; - y += origin.y + owner_extents.top; + int origin_x, origin_y; + Window child_useless_for_API; + ::XTranslateCoordinates(disp, reinterpret_cast(owner), restrict::spec.root_window(), 0, 0, &origin_x, &origin_y, &child_useless_for_API); + x += origin_x; + y += origin_y; } ::XMoveResizeWindow(disp, reinterpret_cast(wd), x, y, r.width, r.height); From e057724f987c974fae4ebacf36385aaaf87fec77 Mon Sep 17 00:00:00 2001 From: Jinhao Date: Mon, 21 Jan 2019 23:51:45 +0800 Subject: [PATCH 12/16] new method is_single_enabled for listbox --- include/nana/gui/widgets/listbox.hpp | 3 ++- source/gui/widgets/listbox.cpp | 12 ++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/include/nana/gui/widgets/listbox.hpp b/include/nana/gui/widgets/listbox.hpp index d5bb0607..86184718 100644 --- a/include/nana/gui/widgets/listbox.hpp +++ b/include/nana/gui/widgets/listbox.hpp @@ -1,7 +1,7 @@ /** * A List Box Implementation * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2018 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2019 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -1538,6 +1538,7 @@ the nana::detail::basic_window member pointer scheme void enable_single(bool for_selection, bool category_limited); void disable_single(bool for_selection); + bool is_single_enabled(bool for_selection) const noexcept; ///< Determines whether the single selection/check is enabled. export_options& def_export_options(); diff --git a/source/gui/widgets/listbox.cpp b/source/gui/widgets/listbox.cpp index 0c797c57..d7789748 100644 --- a/source/gui/widgets/listbox.cpp +++ b/source/gui/widgets/listbox.cpp @@ -1850,6 +1850,11 @@ namespace nana (for_selection ? single_selection_ : single_check_) = false; } + bool is_single_enabled(bool for_selection) const noexcept + { + return (for_selection ? single_selection_ : single_check_); + } + size_type size_item(size_type cat) const { return get(cat)->items.size(); @@ -5959,9 +5964,16 @@ namespace nana void listbox::disable_single(bool for_selection) { + internal_scope_guard lock; _m_ess().lister.disable_single(for_selection); } + bool listbox::is_single_enabled(bool for_selection) const noexcept + { + internal_scope_guard lock; + return _m_ess().lister.is_single_enabled(for_selection); + } + listbox::export_options& listbox::def_export_options() { return _m_ess().def_exp_options; From 3e9b08c0cc120ef7e0e2c5533cd8c3c5db290fe3 Mon Sep 17 00:00:00 2001 From: Jinhao Date: Sun, 27 Jan 2019 06:14:21 +0800 Subject: [PATCH 13/16] fix issue that reverse box selection works incorrectly new bug from develop-1.7 --- source/gui/widgets/listbox.cpp | 90 ++++++++++++++++++++++++---------- 1 file changed, 64 insertions(+), 26 deletions(-) diff --git a/source/gui/widgets/listbox.cpp b/source/gui/widgets/listbox.cpp index d7789748..f9e5bd0c 100644 --- a/source/gui/widgets/listbox.cpp +++ b/source/gui/widgets/listbox.cpp @@ -2011,6 +2011,8 @@ namespace nana { bool started{ false }; bool reverse_selection{ false }; + bool scroll_direction; + bool deselect_when_start_to_move; point screen_pos; point begin_position; ///< Logical position to the @@ -2018,9 +2020,18 @@ namespace nana index_pairs already_selected; index_pairs selections; - bool scroll_direction; unsigned scroll_step{ 1 }; unsigned mouse_move_timestamp{ 0 }; + + bool is_already_selected(const index_pair& abs_pos) const noexcept + { + return (already_selected.cend() != std::find(already_selected.cbegin(), already_selected.cend(), abs_pos)); + } + + bool is_selected(const index_pair& abs_pos) const noexcept + { + return (selections.cend() != std::find(selections.cbegin(), selections.cend(), abs_pos)); + } }mouse_selection; @@ -2118,6 +2129,7 @@ namespace nana mouse_selection.started = true; mouse_selection.begin_position = logic_pos; mouse_selection.end_position = logic_pos; + mouse_selection.deselect_when_start_to_move = true; if (arg.ctrl || arg.shift) { @@ -2133,6 +2145,17 @@ namespace nana if (!mouse_selection.started) return; + // When the button is pressed and start to move the mouse, the listbox should deselect all items. + // But when ctrl is clicked + if (mouse_selection.deselect_when_start_to_move) + { + mouse_selection.deselect_when_start_to_move = false; + if (mouse_selection.already_selected.empty()) + lister.select_for_all(false); + + mouse_selection.selections.clear(); + } + mouse_selection.screen_pos = screen_pos; auto logic_pos = coordinate_cast(screen_pos, true); @@ -2148,7 +2171,7 @@ namespace nana mouse_selection.end_position = logic_pos; - bool cancel_selections = true; + std::vector> selections; 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 && @@ -2159,18 +2182,18 @@ namespace nana auto begin = lister.advance(lister.first(), begin_off); if (!begin.empty()) { - 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); + //Tries to select the items in the box, then returns the items with their previous selected states selections = lister.select_display_range_if(begin, last, false, [this](const index_pair& abs_pos) { - if (this->mouse_selection.reverse_selection) + if (mouse_selection.reverse_selection) { - if (mouse_selection.already_selected.cend() != std::find(mouse_selection.already_selected.cbegin(), mouse_selection.already_selected.cend(), abs_pos)) + //Deselects the items in the box which has been already selected + if(mouse_selection.is_already_selected(abs_pos)) { item_proxy{ this, abs_pos }.select(false); return false; @@ -2181,21 +2204,21 @@ namespace nana for (auto & pair : selections) { + //Continue if the previous state is selected. It indicates the item now is not selected. if (pair.second) continue; - if (mouse_selection.selections.cend() == - std::find(mouse_selection.selections.cbegin(), mouse_selection.selections.cend(), pair.first)) - { + //Add the item to selections container. + if(!mouse_selection.is_selected(pair.first)) mouse_selection.selections.push_back(pair.first); - } } + //Deselects the items which are in mouse_selection.selections but not in selections. + //Eq to mouse_selection.selections = selections #ifdef _MSC_VER 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();) - #endif { auto & selpos = *i; @@ -2209,30 +2232,45 @@ namespace nana else ++i; } - - cancel_selections = false; } } } - if (cancel_selections) + //Restores an already selected item if it is not in selections. + for (auto& abs_pos : mouse_selection.already_selected) { - if (!mouse_selection.already_selected.empty()) + if (selections.cend() == std::find_if(selections.cbegin(), selections.cend(), [abs_pos](const std::pair& rhs) { + return (abs_pos == rhs.first); + })) { - 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) + item_proxy m{ this, abs_pos }; + if (!m.selected()) { - for (auto & abs_pos : mouse_selection.already_selected) - item_proxy(this, abs_pos).select(true); + m.select(true); + //Add the item to selections container. + if(!mouse_selection.is_selected(abs_pos)) + mouse_selection.selections.push_back(abs_pos); } } - else - lister.select_for_all(false); + } - mouse_selection.selections.clear(); + //Deselects the item which is not in already_selected and selections but in mouse_selection.selections + for(auto i = mouse_selection.selections.cbegin(); i != mouse_selection.selections.cend();) + { + auto abs_pos = *i; + + bool is_box_selected = (selections.cend() != std::find_if(selections.cbegin(), selections.cend(), [abs_pos](const std::pair& rhs) { + return (abs_pos == rhs.first); + })); + + if (is_box_selected || mouse_selection.is_already_selected(abs_pos)) + { + ++i; + continue; + } + + item_proxy{ this, abs_pos }.select(false); + i = mouse_selection.selections.erase(i); } } @@ -4374,7 +4412,7 @@ namespace nana if (arg.is_left_button() && (!lister.single_status(true))) essence_->start_mouse_selection(arg); - //Deselection of all items is deferred to the mouse up event when ctrl or shift is not pressed + //Deselecting 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. if (!(arg.ctrl || arg.shift)) { From b0201a5c238d46c1b654e422a0dffd550e3b1e14 Mon Sep 17 00:00:00 2001 From: Jinhao Date: Sun, 27 Jan 2019 06:19:18 +0800 Subject: [PATCH 14/16] Multiple file selection with filebox on Linux based on beru's PR(#349) --- include/nana/gui/filebox.hpp | 7 +- source/gui/filebox.cpp | 563 ++++++++++++++++++++++++++++++----- 2 files changed, 498 insertions(+), 72 deletions(-) diff --git a/include/nana/gui/filebox.hpp b/include/nana/gui/filebox.hpp index bce6af78..61ebdfa2 100644 --- a/include/nana/gui/filebox.hpp +++ b/include/nana/gui/filebox.hpp @@ -1,7 +1,7 @@ /** * Filebox * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2018 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2019 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -69,11 +69,10 @@ namespace nana const ::std::string& path() const; - const ::std::string& file() const; -#if defined(NANA_WINDOWS) + ::std::string file() const; + const ::std::vector<::std::string>& files() const; void allow_multi_select(bool allow); -#endif /// Display the filebox dialog bool show() const; diff --git a/source/gui/filebox.cpp b/source/gui/filebox.cpp index 43bbf2dc..a8848e1e 100644 --- a/source/gui/filebox.cpp +++ b/source/gui/filebox.cpp @@ -1,7 +1,7 @@ /* * Filebox * Nana C++ Library(http://www.nanapro.org) -* Copyright(C) 2003-2018 Jinhao(cnjinhao@hotmail.com) +* Copyright(C) 2003-2019 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -38,6 +38,8 @@ # include "../detail/posix/theme.hpp" #endif +#include //debug + namespace fs = std::filesystem; namespace fs_ext = nana::filesystem_ext; @@ -153,7 +155,7 @@ namespace nana typedef treebox::item_proxy item_proxy; public: - filebox_implement(window owner, mode dialog_mode, const std::string& title, bool pick_directory = false): + filebox_implement(window owner, mode dialog_mode, const std::string& title, bool pick_directory, bool allow_multi_select): form(owner, API::make_center(owner, 630, 440)), pick_directory_(pick_directory), mode_(dialog_mode) @@ -239,11 +241,42 @@ namespace nana ls_file_.append_header(i18n("NANA_FILEBOX_HEADER_TYPE"), 80); ls_file_.append_header(i18n("NANA_FILEBOX_HEADER_SIZE"), 70); - auto fn_sel_file = [this](const arg_mouse& arg){ - _m_select_file(arg); + + auto fn_list_handler = [this](const arg_mouse& arg){ + if(event_code::mouse_down == arg.evt_code) + { + selection_.is_deselect_delayed = true; + } + else if(event_code::mouse_up == arg.evt_code) + { + selection_.is_deselect_delayed = false; +#if 0 + this->_m_delay_deselect(true); +#endif + if(_m_sync_with_selection()) + _m_display_target_filenames(); + } + else if(event_code::mouse_move == arg.evt_code) + { + if(arg.left_button) + { + selection_.is_deselect_delayed = false; + std::cout<<"MouseMove set is_deselect_delayed = true"< bool { int dira = any_cast(fs_a)->directory ? 1 : 0; @@ -327,7 +360,7 @@ namespace nana tb_file_.events().key_char.connect_unignorable([this](const arg_keyboard& arg) { if(arg.key == nana::keyboard::enter) - _m_ok(); + _m_try_select(tb_file_.caption()); }); //Don't create the combox for choose a file extension if the dialog is used for picking a directory. @@ -341,11 +374,11 @@ namespace nana btn_ok_.create(*this); btn_ok_.i18n(i18n_eval("NANA_BUTTON_OK_SHORTKEY")); - btn_ok_.events().click.connect_unignorable([this](const arg_click&) { - _m_ok(); + _m_try_select(tb_file_.caption()); }); + btn_cancel_.create(*this); btn_cancel_.i18n(i18n_eval("NANA_BUTTON_CANCEL_SHORTKEY")); @@ -377,6 +410,10 @@ namespace nana } else caption(title); + + + if(!allow_multi_select) + ls_file_.enable_single(true, true); } void def_extension(const std::string& ext) @@ -392,7 +429,7 @@ namespace nana std::string dir; auto pos = init_file.find_last_of("\\/"); - auto file_with_path_removed = (pos != init_file.npos ? init_file.substr(pos + 1) : init_file); + auto filename = (pos != init_file.npos ? init_file.substr(pos + 1) : init_file); if(saved_init_path != init_path) { @@ -400,7 +437,7 @@ namespace nana saved_init_path = init_path; //Phase 2: Check whether init_file contains a path - if(file_with_path_removed == init_file) + if(filename == init_file) { //Phase 3: Check whether init_path is empty if(init_path.size()) @@ -414,7 +451,7 @@ namespace nana _m_load_cat_path(dir.size() ? dir : fs_ext::path_user().native()); - tb_file_.caption(file_with_path_removed); + tb_file_.caption(filename); } void add_filter(const std::string& desc, const std::string& type) @@ -450,6 +487,7 @@ namespace nana cb_types_.anyobj(i, v); } +#if 0 bool file(std::string& fs) const { if(selection_.type == kind::none) @@ -464,6 +502,20 @@ namespace nana fs = selection_.target; return true; } +#endif + std::vector files() const + { + if(kind::none == selection_.type) + return {}; + + auto pos = selection_.targets.front().find_last_of("\\/"); + if(pos != selection_.targets.front().npos) + saved_selected_path = selection_.targets.front().substr(0, pos); + else + saved_selected_path.clear(); + + return selection_.targets; + } private: void _m_layout() { @@ -704,7 +756,7 @@ namespace nana if(pick_directory_) return is_dir; - if((is_dir || 0 == extension) || (0 == extension->size())) return true; + if(is_dir || (nullptr == extension) || extension->empty()) return true; for(auto & extstr : *extension) { @@ -799,9 +851,8 @@ namespace nana ls_file_.auto_draw(true); } - void _m_finish(kind::t type, const std::string& tar) + void _m_finish(kind::t type) { - selection_.target = tar; selection_.type = type; close(); } @@ -881,40 +932,346 @@ namespace nana return true; } private: - void _m_select_file(const arg_mouse& arg) + void _m_insert_filename(const std::string& name) { - auto sel = ls_file_.selected(); - if(sel.empty()) + if(selection_.targets.cend() == std::find(selection_.targets.cbegin(), selection_.targets.cend(), name)) + selection_.targets.push_back(name); + } + + bool _m_hovered_good() const + { + auto pos = ls_file_.hovered(false); + if(!pos.empty()) + { + item_fs mfs; + ls_file_.at(pos).resolve_to(mfs); + return !mfs.directory; + } + return false; + } + + bool _m_has_good_select() const + { + auto selected_items = ls_file_.selected(); + if(selected_items.size()) + { + for(auto & pos : selected_items) + { + item_fs mfs; + ls_file_.at(pos).resolve_to(mfs); + + if((mode::open_directory == mode_) || (false == mfs.directory)) + return true; + } + } + return false; + } + + bool _m_sync_with_selection() + { + if(_m_has_good_select()) + { + auto selected_items = ls_file_.selected(); + if(selected_items.size() && (selected_items.size() < selection_.targets.size())) + { + selection_.targets.clear(); + for(auto & pos : selected_items) + { + item_fs mfs; + ls_file_.at(pos).resolve_to(mfs); + + if((mode::open_directory == mode_) || (false == mfs.directory)) + selection_.targets.push_back(mfs.name); + } + return true; + } + } + return false; + } + + void _m_click_select_file(const arg_mouse& arg) + { + auto selected_item = ls_file_.hovered(false); + if(selected_item.empty()) return; - auto index = sel[0]; item_fs m; - ls_file_.at(index).resolve_to(m); + ls_file_.at(selected_item).resolve_to(m); if(event_code::dbl_click == arg.evt_code) { - if(m.directory) - _m_load_cat_path(addr_.filesystem + m.name + "/"); + if(!m.directory) + _m_try_select(m.name); else - _m_finish(kind::filesystem, addr_.filesystem + m.name); + _m_load_cat_path(addr_.filesystem + m.name + "/"); } +#if 0 else { if((mode::open_directory == mode_) || (false == m.directory)) { - selection_.target = addr_.filesystem + m.name; - tb_file_.caption(m.name); + if(ls_file_.is_single_enabled(true) || !arg.ctrl) + { + std::cout<<"assign by click select "< _m_strip_files(const std::string& text) + { + std::vector files; + std::size_t start_pos = 0; + while(true) + { + while(true) + { + auto pos = text.find_first_of(" \"", start_pos); + if(text.npos == pos) + { + if(text.length() == start_pos) + return files; + + return {}; + } + + start_pos = pos + 1; + + if(text[pos] == '"') + break; + } + + auto pos = text.find('"', start_pos); + if(text.npos == pos) + return {}; + + if(pos - start_pos > 0) + files.push_back(text.substr(start_pos, pos - start_pos)); + + start_pos = pos + 1; + } + + return files; + + } + + void _m_try_select(const std::string& files_text) + { + selection_.targets.clear(); + //auto file = tb_file_.caption(); + auto targets = _m_strip_files(files_text); +#if 1 //debug + std::cout<<"filebox OK: file="<(fattr.type()); + + //Check if the selected name is a directory + auto is_dir = fs::is_directory(fattr); + + if(!is_dir && _m_append_def_extension(tar)) + { + //Add the extension, then check if it is a directory again. + fattr = fs::status(tar); + ftype = static_cast(fattr.type()); + is_dir = fs::is_directory(fattr); + } + + if(is_dir) + { + //Enter the directory if this is the first tar. + if(0 == tar_idx) + { + _m_load_cat_path(tar); + tb_file_.caption(std::string{}); + return; + } + + //Other folders are ignored + continue; + } + + if(mode::write_file != mode_) + { + if(fs::file_type::not_found == ftype) + { + msgbox mb(*this, caption()); + mb.icon(msgbox::icon_information); + if(mode::open_file == mode_) + mb << i18n("NANA_FILEBOX_ERROR_NOT_EXISTING_AND_RETRY", tar); + else + mb << i18n("NANA_FILEBOX_ERROR_DIRECTORY_NOT_EXISTING_AND_RETRY", tar); + mb(); + + return; + } + } + else + { + if(fs::file_type::not_found != ftype) + { + msgbox mb(*this, caption(), msgbox::yes_no); + mb.icon(msgbox::icon_question); + mb<(fattr.type()); + + //Check if the selected name is a directory + auto is_dir = fs::is_directory(fattr); + + if(!is_dir && _m_append_def_extension(tar)) + { + //Add the extension, then check if it is a directory again. + fattr = fs::status(tar); + ftype = static_cast(fattr.type()); + is_dir = fs::is_directory(fattr); + } + + if(is_dir) + { + //Enter the directory if this is the first tar. + if(0 == tar_idx) + { + _m_load_cat_path(tar); + tb_file_.caption(std::string{}); + return; + } + + //Other folders are ignored + continue; + } + + if(mode::write_file != mode_) + { + if(fs::file_type::not_found == ftype) + { + msgbox mb(*this, caption()); + mb.icon(msgbox::icon_information); + if(mode::open_file == mode_) + mb << i18n("NANA_FILEBOX_ERROR_NOT_EXISTING_AND_RETRY", tar); + else + mb << i18n("NANA_FILEBOX_ERROR_DIRECTORY_NOT_EXISTING_AND_RETRY", tar); + mb(); + + return; + } + } + else + { + if(fs::file_type::not_found != ftype) + { + msgbox mb(*this, caption(), msgbox::yes_no); + mb.icon(msgbox::icon_question); + mb< targets; +#if 0 + std::vector delay_deselect; +#endif + bool is_deselect_delayed{ false }; }selection_; static std::string saved_init_path; @@ -1099,12 +1543,9 @@ namespace nana window owner; bool open_or_save; -#if defined(NANA_WINDOWS) bool allow_multi_select; std::vector files; -#else - std::string file; -#endif + std::string title; std::string path; std::vector filters; @@ -1120,8 +1561,8 @@ namespace nana { impl_->owner = owner; impl_->open_or_save = open; -#if defined(NANA_WINDOWS) impl_->allow_multi_select = false; +#if defined(NANA_WINDOWS) auto len = ::GetCurrentDirectory(0, nullptr); if(len) { @@ -1178,11 +1619,7 @@ namespace nana filebox& filebox::init_file(const std::string& ifstr) { -#if defined(NANA_WINDOWS) impl_->files = {ifstr}; -#else - impl_->file = ifstr; -#endif return *this; } @@ -1198,30 +1635,18 @@ namespace nana return impl_->path; } -#if defined(NANA_WINDOWS) - const std::string& filebox::file() const + std::string filebox::file() const { if(impl_->files.empty()) - { - static const std::string empty = ""; - return empty; - } - else - { - return impl_->files.front(); - } + return {}; + + return impl_->files.front(); } const std::vector& filebox::files() const { return impl_->files; } -#else - const std::string& filebox::file() const - { - return impl_->file; - } -#endif bool filebox::show() const { @@ -1332,7 +1757,7 @@ namespace nana #elif defined(NANA_POSIX) using mode = filebox_implement::mode; - filebox_implement fb(impl_->owner, (impl_->open_or_save ? mode::open_file : mode::write_file), impl_->title); + filebox_implement fb(impl_->owner, (impl_->open_or_save ? mode::open_file : mode::write_file), impl_->title, false, impl_->allow_multi_select); if(impl_->filters.size()) { @@ -1353,27 +1778,28 @@ namespace nana else fb.add_filter("All Files", "*.*"); - fb.load_fs(impl_->path, impl_->file); + fb.load_fs(impl_->path, this->file()); API::modal_window(fb); - if(false == fb.file(impl_->file)) + + fb.files().swap(impl_->files); + if(impl_->files.empty()) return false; - auto tpos = impl_->file.find_last_of("\\/"); - if(tpos != impl_->file.npos) - impl_->path = impl_->file.substr(0, tpos); + + auto tpos = impl_->files.front().find_last_of("\\/"); + if(tpos != impl_->files.front().npos) + impl_->path = impl_->files.front().substr(0, tpos); else impl_->path.clear(); #endif - return true; } -#if defined(NANA_WINDOWS) + void filebox::allow_multi_select(bool allow) { impl_->allow_multi_select = allow; } -#endif //end class filebox //class directory picker @@ -1484,17 +1910,18 @@ namespace nana #elif defined(NANA_POSIX) using mode = filebox_implement::mode; - filebox_implement fb(impl_->owner, mode::open_directory, {}, true); + filebox_implement fb(impl_->owner, mode::open_directory, {}, true, false/*single select*/); fb.load_fs(impl_->init_path, ""); API::modal_window(fb); std::string path_directory; - if(false == fb.file(path_directory)) + auto path_dir = fb.files(); + if(path_dir.empty()) return {}; - return path_type{path_directory}; + return path_type{path_dir.front()}; #endif } }//end namespace nana From fb8ade6557f432c6172845783e4f500cfeefdc51 Mon Sep 17 00:00:00 2001 From: Jinhao Date: Thu, 31 Jan 2019 07:08:45 +0800 Subject: [PATCH 15/16] some modifications to #357 and #383 --- include/nana/gui/widgets/detail/compset.hpp | 8 +++-- include/nana/gui/widgets/treebox.hpp | 2 +- source/gui/widgets/listbox.cpp | 5 ++- source/gui/widgets/treebox.cpp | 37 ++++++++++++++------- 4 files changed, 35 insertions(+), 17 deletions(-) diff --git a/include/nana/gui/widgets/detail/compset.hpp b/include/nana/gui/widgets/detail/compset.hpp index 3991fe4e..8518b85c 100644 --- a/include/nana/gui/widgets/detail/compset.hpp +++ b/include/nana/gui/widgets/detail/compset.hpp @@ -1,6 +1,6 @@ /* * Concept of Component Set - * Copyright(C) 2003-2013 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2019 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -60,17 +60,19 @@ namespace nana{ namespace widgets{ namespace detail /// Widget scheme. typedef WidgetScheme widget_scheme_t; - widget_scheme_t * wdg_scheme_ptr_{ nullptr }; + //widget_scheme_t * wdg_scheme_ptr_{ nullptr }; //deprecated public: /// The destructor. virtual ~compset_placer(){} + /* /// Init the scheme pointer - void init_scheme(widget_scheme_t* wdg_scheme_ptr) + void init_scheme(widget_scheme_t* wdg_scheme_ptr) //deprecated { wdg_scheme_ptr_ = wdg_scheme_ptr; } + */ /// Enable/Disable the specified component. virtual void enable(component_t, bool) = 0; diff --git a/include/nana/gui/widgets/treebox.hpp b/include/nana/gui/widgets/treebox.hpp index 4ff78e99..0d78bbde 100644 --- a/include/nana/gui/widgets/treebox.hpp +++ b/include/nana/gui/widgets/treebox.hpp @@ -1,7 +1,7 @@ /** * A Tree Box Implementation * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2018 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2019 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE or copy at diff --git a/source/gui/widgets/listbox.cpp b/source/gui/widgets/listbox.cpp index 4b902c43..895b2829 100644 --- a/source/gui/widgets/listbox.cpp +++ b/source/gui/widgets/listbox.cpp @@ -6195,8 +6195,9 @@ namespace nana return; std::vector new_idx; for(size_type i=first_col; i<=last_col; ++i) new_idx.push_back(i); - const item_proxy & ip_row=this->at(row); + internal_scope_guard lock; + const item_proxy ip_row = this->at(row); const nana::any *pnany=_m_ess().lister.anyobj(row,false); std::sort(new_idx.begin(), new_idx.end(), [&](size_type col1, size_type col2) @@ -6205,6 +6206,8 @@ namespace nana ip_row.text(col2), col2, pnany, reverse); }); + + //Only change the view position of columns for(size_t i=0; icrook_size : 0), (enable_icon_ ? wdg_scheme_ptr_->icon_size : 0)); + auto m = std::max((enable_crook_ ? scheme_.crook_size : 0), (enable_icon_ ? scheme_.icon_size : 0)); + +#if 1 + unsigned as = 0, ds = 0, il; + graph.text_metrics(as, ds, il); + return std::max(as + ds + 8, m); +#else #ifdef _nana_std_has_string_view return std::max(m, graph.text_extent_size(std::wstring_view{ L"jH{", 3 }).height + 8); #else return std::max(m, graph.text_extent_size(L"jH{", 3).height + 8); +#endif #endif } virtual unsigned item_width(graph_reference graph, const item_attribute_t& attr) const override { - return graph.text_extent_size(attr.text).width + (enable_crook_ ? wdg_scheme_ptr_->crook_size : 0) + (enable_icon_ ? wdg_scheme_ptr_ ->icon_size: 0) + (wdg_scheme_ptr_->text_offset << 1) + wdg_scheme_ptr_->item_offset; + return graph.text_extent_size(attr.text).width + (enable_crook_ ? scheme_.crook_size : 0) + (enable_icon_ ? scheme_.icon_size : 0) + (scheme_.text_offset << 1) + scheme_.item_offset; } // Locate a component through the specified coordinate. @@ -1389,7 +1400,7 @@ namespace nana case component_t::expander: if(attr.has_children) { - r->width = wdg_scheme_ptr_->item_offset; + r->width = scheme_.item_offset; return true; } return false; @@ -1398,26 +1409,26 @@ namespace nana case component_t::crook: if(enable_crook_) { - r->x += wdg_scheme_ptr_->item_offset; - r->width = wdg_scheme_ptr_->crook_size; + r->x += scheme_.item_offset; + r->width = scheme_.crook_size; return true; } return false; case component_t::icon: if(enable_icon_) { - r->x += wdg_scheme_ptr_->item_offset + (enable_crook_ ? wdg_scheme_ptr_->crook_size : 0); + r->x += scheme_.item_offset + (enable_crook_ ? scheme_.crook_size : 0); r->y = 2; - r->width = wdg_scheme_ptr_->icon_size; + r->width = scheme_.icon_size; r->height -= 2; return true; } return false; case component_t::text: { - auto text_pos = wdg_scheme_ptr_->item_offset + (enable_crook_ ? wdg_scheme_ptr_->crook_size : 0) + (enable_icon_ ? wdg_scheme_ptr_->icon_size : 0) + wdg_scheme_ptr_->text_offset; + auto text_pos = scheme_.item_offset + (enable_crook_ ? scheme_.crook_size : 0) + (enable_icon_ ? scheme_.icon_size : 0) + scheme_.text_offset; r->x += text_pos; - r->width -= (text_pos + wdg_scheme_ptr_->text_offset); + r->width -= (text_pos + scheme_.text_offset); }; return true; default: @@ -1426,6 +1437,7 @@ namespace nana return false; } private: + const scheme& scheme_; bool enable_crook_{ false }; bool enable_icon_{ false }; }; @@ -1680,7 +1692,7 @@ namespace nana { impl_->data.trigger_ptr = this; impl_->data.renderer = nana::pat::cloneable(internal_renderer()); - impl_->data.comp_placer = nana::pat::cloneable(internal_placer()); + //impl_->data.comp_placer = nana::pat::cloneable(internal_placer()); //deprecated impl_->adjust.timer.elapse([this] { @@ -1946,7 +1958,8 @@ namespace nana widget.bgcolor(colors::white); impl_->data.widget_ptr = static_cast<::nana::treebox*>(&widget); impl_->data.scheme_ptr = static_cast<::nana::treebox::scheme_type*>(API::dev::get_scheme(widget)); - impl_->data.comp_placer->init_scheme(impl_->data.scheme_ptr); + //impl_->data.comp_placer->init_scheme(impl_->data.scheme_ptr); //deprecated + impl_->data.comp_placer = nana::pat::cloneable(internal_placer{ *impl_->data.scheme_ptr }); widget.caption("nana treebox"); } From 85c2bdbd75586dac58b1626c9fbc39e2bfaaa27e Mon Sep 17 00:00:00 2001 From: Jinhao Date: Sat, 2 Feb 2019 00:31:56 +0800 Subject: [PATCH 16/16] small changes --- source/gui/widgets/checkbox.cpp | 2 +- source/gui/widgets/listbox.cpp | 4 ++-- source/gui/widgets/spinbox.cpp | 2 +- source/gui/widgets/treebox.cpp | 2 ++ 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/source/gui/widgets/checkbox.cpp b/source/gui/widgets/checkbox.cpp index 3148ee88..fc08fcac 100644 --- a/source/gui/widgets/checkbox.cpp +++ b/source/gui/widgets/checkbox.cpp @@ -1,7 +1,7 @@ /* * A CheckBox Implementation * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2018 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2019 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at diff --git a/source/gui/widgets/listbox.cpp b/source/gui/widgets/listbox.cpp index 895b2829..13baf15a 100644 --- a/source/gui/widgets/listbox.cpp +++ b/source/gui/widgets/listbox.cpp @@ -6197,8 +6197,8 @@ namespace nana for(size_type i=first_col; i<=last_col; ++i) new_idx.push_back(i); internal_scope_guard lock; - const item_proxy ip_row = this->at(row); - const nana::any *pnany=_m_ess().lister.anyobj(row,false); + auto ip_row = this->at(row); + auto pnany=_m_ess().lister.anyobj(row,false); std::sort(new_idx.begin(), new_idx.end(), [&](size_type col1, size_type col2) { diff --git a/source/gui/widgets/spinbox.cpp b/source/gui/widgets/spinbox.cpp index 49ae0347..4a631c2c 100644 --- a/source/gui/widgets/spinbox.cpp +++ b/source/gui/widgets/spinbox.cpp @@ -1,7 +1,7 @@ /* * A Spin box widget * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2017 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2019 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at diff --git a/source/gui/widgets/treebox.cpp b/source/gui/widgets/treebox.cpp index 4181177e..e5be4dcd 100644 --- a/source/gui/widgets/treebox.cpp +++ b/source/gui/widgets/treebox.cpp @@ -1966,6 +1966,8 @@ namespace nana void trigger::detached() { + //Reset the comp_placer, because after deteching, the scheme refered by comp_placer will be released + impl_->data.comp_placer.reset(); impl_->data.graph = nullptr; }