diff --git a/include/nana/gui/place.hpp b/include/nana/gui/place.hpp index dbc12111..176c7bd4 100644 --- a/include/nana/gui/place.hpp +++ b/include/nana/gui/place.hpp @@ -112,7 +112,8 @@ namespace nana void bind(window handle); window window_handle() const; - void div(const char* s); ///< Divides the attached widget into fields. + void div(const char* s); ///< Divides the attached widget into fields. + const std::string& div() const noexcept; ///< Returns div-text that depends on fields status. void modify(const char* field_name, const char* div_text); ///< Modifies a specified field. field_reference field(const char* name);///< Returns a field with the specified name. diff --git a/include/nana/gui/widgets/listbox.hpp b/include/nana/gui/widgets/listbox.hpp index 63ee49ca..19fe1093 100644 --- a/include/nana/gui/widgets/listbox.hpp +++ b/include/nana/gui/widgets/listbox.hpp @@ -8,12 +8,7 @@ * http://www.boost.org/LICENSE_1_0.txt) * * @file: nana/gui/widgets/listbox.hpp - * @contributors: - * Hiroshi Seki - * Ariel Vina-Rodriguez - * leobackes(pr#86,pr#97) - * Benjamin Navarro(pr#81) - * besh81(pr#130) + * */ #ifndef NANA_GUI_WIDGETS_LISTBOX_HPP @@ -800,12 +795,31 @@ namespace nana /// posible use: last_selected_display = last_selected.to_display().item; use with caution, it get invalidated after a sort() index_pair to_display() const; + /// Determines whether the item is displayed on the screen + bool displayed() const; + bool empty() const; - item_proxy & check(bool ck); + /// Checks/unchecks the item + /** + * @param chk Indicates whether to check or uncheck the item + * @param scroll_view Indicates whether to scroll the view to the item. It is ignored if the item is displayed. + * @return the reference of *this. + */ + item_proxy & check(bool chk, bool scroll_view = false); + + /// Determines whether the item is checked bool checked() const; - item_proxy & select(bool); + /// Selects/unselects the item + /** + * @param sel Indicates whether to select or unselect the item + * @param scroll_view Indicates whether to scroll the view to the item. It is ignored if the item is displayed. + * @return the reference of *this. + */ + item_proxy & select(bool sel, bool scroll_view = false); + + /// Determines whether he item is selected bool selected() const; item_proxy & bgcolor(const nana::color&); @@ -1130,9 +1144,11 @@ namespace nana : public widget_geometrics { color_proxy header_bgcolor{static_cast(0xf1f2f4)}; + color_proxy header_fgcolor{ colors::black }; color_proxy header_grabbed{ static_cast(0x8BD6F6)}; color_proxy header_floated{ static_cast(0xBABBBC)}; color_proxy item_selected{ static_cast(0xD5EFFC) }; + color_proxy item_highlighted{ static_cast(0xD5EFFC) }; /// The max column width which is generated by fit_content is allowed. It is ignored when it is 0, or a max value is passed to fit_content. unsigned max_fit_content{ 0 }; @@ -1435,7 +1451,7 @@ the nana::detail::basic_window member pointer scheme export_options& def_export_options(); private: drawerbase::listbox::essence & _m_ess() const; - nana::any* _m_anyobj(size_type cat, size_type index, bool allocate_if_empty) const; + nana::any* _m_anyobj(size_type cat, size_type index, bool allocate_if_empty) const override; drawerbase::listbox::category_t* _m_assoc(std::shared_ptr, bool create_if_not_exists); void _m_erase_key(nana::detail::key_interface*); }; diff --git a/include/nana/gui/widgets/menu.hpp b/include/nana/gui/widgets/menu.hpp index f364f15c..8e0e7b44 100644 --- a/include/nana/gui/widgets/menu.hpp +++ b/include/nana/gui/widgets/menu.hpp @@ -1,13 +1,14 @@ /** * A Menu implementation * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2009-2014 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2009-2016 Jinhao(cnjinhao@hotmail.com) * * 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/menu.hpp + * */ #ifndef NANA_GUI_WIDGETS_MENU_HPP @@ -122,12 +123,13 @@ namespace nana ~menu(); /// Appends an item to the menu. - item_proxy append(const std::string& text, const event_fn_t& callback= event_fn_t()); + item_proxy append(std::string text_utf8, const event_fn_t& callback= event_fn_t()); void append_splitter(); void clear(); ///< Erases all of the items. /// Closes the menu. It does not destroy the menu; just close the window for the menu. void close(); void image(std::size_t pos, const paint::image& icon); + void text(std::size_t pos, std::string text_utf8); void check_style(std::size_t pos, checks); void checked(std::size_t pos, bool); bool checked(std::size_t pos) const; diff --git a/include/nana/internationalization.hpp b/include/nana/internationalization.hpp index 250e3978..1b434169 100644 --- a/include/nana/internationalization.hpp +++ b/include/nana/internationalization.hpp @@ -16,6 +16,7 @@ #include #include #include +#include namespace nana { diff --git a/source/gui/notifier.cpp b/source/gui/notifier.cpp index e46541c9..ddecc68d 100644 --- a/source/gui/notifier.cpp +++ b/source/gui/notifier.cpp @@ -201,7 +201,7 @@ namespace nana #if defined(NANA_WINDOWS) void notifications_window_proc(HWND wd, WPARAM wparam, LPARAM lparam) { - arg_notifier arg; + arg_notifier arg = {}; switch (lparam) { case WM_LBUTTONDBLCLK: @@ -321,6 +321,7 @@ namespace nana void notifier::icon(const std::string& icon_file) { +#if defined(NANA_WINDOWS) paint::image image_ico{ icon_file }; auto icon_handle = paint::image_accessor::icon(image_ico); if (icon_handle) @@ -330,14 +331,17 @@ namespace nana impl_->set_icon(image_ico); impl_->icon = image_ico; } +#endif } void notifier::insert_icon(const std::string& icon_file) { +#if defined(NANA_WINDOWS) paint::image image_ico{ icon_file }; auto icon_handle = paint::image_accessor::icon(image_ico); if (icon_handle) impl_->icons.emplace_back(static_cast(image_ico)); +#endif } void notifier::period(unsigned ms) diff --git a/source/gui/place.cpp b/source/gui/place.cpp index 1af71f0c..b1d26889 100644 --- a/source/gui/place.cpp +++ b/source/gui/place.cpp @@ -8,7 +8,8 @@ * http://www.boost.org/LICENSE_1_0.txt) * * @file: nana/gui/place.cpp - * @contributors: Ariel Vina-Rodriguez + * @contributors: Ariel Vina-Rodriguez + * dankan1890(PR#156) */ #include @@ -56,7 +57,7 @@ namespace nana { div_start, div_end, splitter, identifier, dock, vert, grid, number, array, reparray, - weight, gap, margin, arrange, variable, repeated, min_px, max_px, left, right, top, bottom, + weight, gap, margin, arrange, variable, repeated, min_px, max_px, left, right, top, bottom, undisplayed, invisible, collapse, parameters, equal, eof, error @@ -275,7 +276,7 @@ namespace nana _m_throw_error("a parameter list is required after 'collapse'"); return token::collapse; } - else if ("left" == idstr_ || "right" == idstr_ || "top" == idstr_ || "bottom" == idstr_) + else if ("left" == idstr_ || "right" == idstr_ || "top" == idstr_ || "bottom" == idstr_ || "undisplayed" == idstr_ || "invisible" == idstr_) { switch (idstr_.front()) { @@ -283,8 +284,11 @@ namespace nana case 'r': return token::right; case 't': return token::top; case 'b': return token::bottom; + case 'u': return token::undisplayed; + case 'i': return token::invisible; } } + return token::identifier; } @@ -407,8 +411,18 @@ namespace nana sp = _m_eat_whitespace(sp); if ('%' == *sp) { - if (number_t::kind::integer == number_.kind_of()) + switch (number_.kind_of()) + { + case number_t::kind::integer: number_.assign_percent(number_.integer()); + break; + case number_t::kind::real: + number_.assign_percent(number_.real()); + break; + default: + break; + } + return sp - allstart + 1; } return sp - allstart; @@ -427,6 +441,87 @@ namespace nana }; //end class tokenizer } + + inline bool is_idchar(int ch) + { + return ('_' == ch || isalnum(ch)); + } + + std::size_t find_idstr(const std::string& text, const char* idstr, std::size_t off = 0) + { + const auto len = std::strlen(idstr); + + size_t pos; + while ((pos = text.find(idstr, off)) != text.npos) + { + if (!is_idchar(text[pos + len])) + { + if (pos == 0 || !is_idchar(text[pos - 1])) + return pos; + } + + off = pos + len; // occurrence not found, advancing the offset and try again + } + return text.npos; + } + + std::pair get_field_bound(const std::string& div, const char* idstr, int depth) + { + auto start_pos = find_idstr(div, idstr); + + if (depth < 0 || start_pos >= div.length()) + return{}; + + const char* p = div.c_str() + start_pos; + + while (depth >= 0) + { + auto pos = div.find_last_of("<>", start_pos); + if (div.npos == pos) + return{}; + + if (div[pos] == '>') + { + ++depth; + start_pos = pos - 1; + continue; + } + + if (0 == depth) + { + start_pos = pos; + break; + } + + --depth; + start_pos = pos - 1; + } + + auto off = start_pos + 1; + + while (true) + { + auto pos = div.find_first_of("<>", off); + + if (div.npos == pos) + return{}; + + if ('<' == div[pos]) + { + ++depth; + off = pos + 1; + continue; + } + + if (0 == depth) + return{ start_pos, pos + 1 }; + + --depth; + off = pos + 1; + } + } + + //struct implement struct place::implement { @@ -442,6 +537,8 @@ namespace nana window window_handle{nullptr}; event_handle event_size_handle{nullptr}; + + std::string div_text; std::unique_ptr root_division; std::map fields; std::map docks; @@ -1309,8 +1406,9 @@ namespace nana enum{splitter_px = 4}; public: - div_splitter(place_parts::number_t init_weight) - : division(kind::splitter, std::string()), + div_splitter(place_parts::number_t init_weight, implement* impl): + division(kind::splitter, std::string()), + impl_(impl), init_weight_(init_weight) { this->weight.assign(splitter_px); @@ -1335,7 +1433,7 @@ namespace nana auto grab_fn = [this](const arg_mouse& arg) { - if (false == arg.left_button) + if ((false == arg.left_button) && (mouse::left_button != arg.button)) return; if (event_code::mouse_down == arg.evt_code) @@ -1368,6 +1466,7 @@ namespace nana else if(event_code::mouse_up == arg.evt_code) { grabbed_ = false; + this->_m_update_div(impl_->div_text); } else if (event_code::mouse_move == arg.evt_code) { @@ -1466,6 +1565,231 @@ namespace nana splitter_.move(this->field_area); } private: + static int _m_search_name(const division* div, std::string& name) + { + if (div->name.size()) + { + name = div->name; + return 0; + } + + for (auto & child : div->children) + { + if (child->name.size()) + { + name = child->name; + return 1; + } + } + + for (auto & child : div->children) + { + auto depth = _m_search_name(child.get(), name); + if (depth >= 0) + return depth + 1; + } + + return -1; + } + + static std::pair _m_field_bound(const std::string& div, std::size_t start_pos) + { + int depth = 0; + + if ('<' == div[start_pos]) + { + auto off = start_pos + 1; + while (off < div.length()) + { + auto pos = div.find_first_of("<>", off); + if (div.npos == pos) + break; + + if ('<' == div[pos]) + { + ++depth; + off = pos + 1; + continue; + } + + if (0 == depth) + return{ start_pos, pos + 1}; + + --depth; + off = pos + 1; + } + } + else if (('>' == div[start_pos]) && (start_pos > 0)) + { + auto off = start_pos - 1; + while (true) + { + auto pos = div.find_last_of("<>", off); + if (div.npos == pos) + break; + + if ('>' == div[pos]) + { + ++depth; + if (0 == pos) + break; + + off = pos - 1; + } + + if (0 == depth) + return{ pos, start_pos + 1}; + + if (0 == pos) + break; + + off = pos - 1; + } + } + + return{}; + } + + static void _m_remove_attr(std::string& div, const char* attr) + { + auto attr_pos = div.find(attr); + if (div.npos == attr_pos) + return; + + std::size_t off = 1; + while (true) + { + auto pos = div.find('<', off); + if (div.npos == pos) + break; + + if (attr_pos < pos) + break; + + int depth = 0; + off = pos + 1; + std::size_t endpos = 0; + while (true) + { + endpos = div.find_first_of("<>", off); + if (div.npos == endpos) + return; + + if ('<' == div[endpos]) + { + ++depth; + off = endpos + 1; + continue; + } + + if (0 == depth) + break; + + --depth; + off = endpos + 1; + } + + if (attr_pos < endpos) + { + attr_pos = div.find(attr, endpos + 1); + if (div.npos == attr_pos) + return; + } + + off = endpos + 1; + } + + auto len = std::strlen(attr); + + auto endpos = div.find_first_not_of(" ", attr_pos + len); + if (div.npos != endpos && div[endpos] == '=') + { + endpos = div.find_first_not_of(" 0123456789.%", endpos + 1); + if (div.npos == endpos) + throw std::runtime_error("please report an issue if throws"); + } + else + endpos = attr_pos + len; + + div.erase(attr_pos, endpos - attr_pos); + } + + void _m_update_div(std::string& div) + { + std::string name; + bool left = true; + + //Search a name recursively from a specified leaf field. + //It returns the depth from the leaf div to the div which has a name. + auto depth = _m_search_name(_m_leaf_left(), name); + if (-1 == depth) + { + left = false; + depth = _m_search_name(_m_leaf_right(), name); + if (-1 == depth) + return; + } + + //Get the bound of field div-text through reverse recursion. + auto bound = get_field_bound(div, name.c_str(), depth); + if (bound.first == bound.second) + return; + + auto fieldstr = div.substr(bound.first, bound.second - bound.first); + _m_remove_attr(fieldstr, "weight"); + + decltype(bound) other_bound; + if (left) + { + //Get the bound of leaf right + auto pos = div.find('<', bound.second + 1); + if (div.npos == pos) + throw std::runtime_error("please report an issue if it throws"); + + other_bound = _m_field_bound(div, pos); + } + else + { + //Get the bound of leaf left + auto pos = div.rfind('>', bound.first - 1); + if (div.npos == pos) + throw std::runtime_error("place report an issue if it throws"); + + other_bound = _m_field_bound(div, pos); + } + + auto other_fieldstr = div.substr(other_bound.first, other_bound.second - other_bound.first); + _m_remove_attr(other_fieldstr, "weight"); + + const bool vert = (::nana::cursor::size_we != splitter_cursor_); + + rectangle_rotator r_left(vert, _m_leaf_left()->field_area); + rectangle_rotator r_right(vert, _m_leaf_right()->field_area); + rectangle_rotator r_owner(vert, this->div_owner->field_area); + + double percent = double((left ? r_left : r_right).w()) / double(r_owner.w()); + + std::string weight = "weight=" + std::to_string(percent * 100) + "% "; + + fieldstr.insert(1, weight); + + //Replaces the 'right' field before 'left' in order to make the bound consistent + if (left) + { + if (other_fieldstr.length() != (other_bound.second - other_bound.first)) + div.replace(other_bound.first, other_bound.second - other_bound.first, other_fieldstr); + + div.replace(bound.first, bound.second - bound.first, fieldstr); + } + else + { + div.replace(bound.first, bound.second - bound.first, fieldstr); + + if (other_fieldstr.length() != (other_bound.second - other_bound.first)) + div.replace(other_bound.first, other_bound.second - other_bound.first, other_fieldstr); + } + } + division * _m_leaf_left() const { return previous(); @@ -1526,6 +1850,7 @@ namespace nana return area; } private: + implement* const impl_; nana::cursor splitter_cursor_{nana::cursor::arrow}; place_parts::splitter splitter_; nana::point begin_point_; @@ -1885,7 +2210,7 @@ namespace nana unsigned vert_count = 0, horz_count = 0; bool is_first = true; - bool prev_attr; + bool prev_attr = false; for (auto & child : children) { @@ -1962,7 +2287,7 @@ namespace nana child_dv->splitter.reset(); ::nana::rectangle child_r; - double split_range_begin = -1, split_range_end; + double split_range_begin = -1, split_range_end = 0; switch (child->dir) { default: @@ -2147,6 +2472,9 @@ namespace nana std::vector> children; ::nana::direction div_dir = ::nana::direction::west; + bool undisplayed = false; + bool invisible = false; + for (token tk = tknizer.read(); tk != token::eof; tk = tknizer.read()) { bool exit_for = false; @@ -2162,7 +2490,7 @@ namespace nana //Ignore the splitter when there is not a division. if (!children.empty() && (division::kind::splitter != children.back()->kind_of_division)) { - auto splitter = new div_splitter(tknizer.number()); + auto splitter = new div_splitter(tknizer.number(), this); children.back()->div_next = splitter; children.emplace_back(std::unique_ptr{ splitter }); } @@ -2305,6 +2633,10 @@ namespace nana div_dir = ::nana::direction::north; break; case token::bottom: div_dir = ::nana::direction::south; break; + case token::undisplayed: + undisplayed = true; break; + case token::invisible: + invisible = true; break; default: break; } if (exit_for) @@ -2430,6 +2762,9 @@ namespace nana div->children.swap(children); div->margin = std::move(margin); div->dir = div_dir; + + div->display = !undisplayed; + div->visible = !(undisplayed || invisible); return div; } @@ -2593,6 +2928,7 @@ namespace nana impl_->connect(div.get()); impl_->root_division.reset(); //clear atachments div-fields impl_->root_division.swap(div); + impl_->div_text.assign(s); } catch (...) { @@ -2601,6 +2937,16 @@ namespace nana } } + const std::string& place::div() const noexcept + { + return impl_->div_text; + } + + //Contributed by dankan1890(PR#156) + enum class update_operation { erase = 0, insert, replace }; + + void update_div(std::string& div, const char* field, const char* attr, update_operation operation); + void place::modify(const char* name, const char* div_text) { if (nullptr == div_text) @@ -2655,6 +3001,7 @@ namespace nana impl_->check_unique(impl_->root_division.get()); impl_->connect(impl_->root_division.get()); impl_->tmp_replaced.reset(); + update_div(impl_->div_text, name, div_text, update_operation::replace); modified_ptr->div_owner = div_owner; modified_ptr->div_next = div_next; @@ -2698,6 +3045,71 @@ namespace nana return *p; } + void update_div(std::string& div, const char* field, const char* attr, update_operation operation) + { + const auto fieldname_pos = find_idstr(div, field); + if (div.npos == fieldname_pos) + return; + + auto bound = get_field_bound(div, field, 0); + + auto fieldstr = div.substr(bound.first + 1, bound.second - bound.first - 2); + //Search the attribute + std::size_t pos = 0; + int depth = 0; + for (; true; ++pos) + { + pos = find_idstr(fieldstr, attr, pos); + if (fieldstr.npos == pos) + break; + + //Check if the attr is belong to this field. + depth = 0; + std::size_t off = pos; + while (true) + { + off = fieldstr.find_last_of("<>", off); + if (fieldstr.npos == off) + break; + + if ('>' == fieldstr[off]) + ++depth; + else + --depth; + + if (0 == off) + break; + --off; + } + + if (0 == depth) + break; + } + + if (fieldstr.npos == pos) + { + //There is not an attribute + if (operation == update_operation::insert) + div.insert(fieldname_pos + std::strlen(field), " " + std::string(attr)); + else if (operation == update_operation::replace) + { + div.erase(bound.first + 1, fieldstr.length()); + div.insert(bound.first + 1, std::string(attr) + " " + std::string(field)); + } + } + else + { + //There is an attribute + if (operation == update_operation::erase) + { + div.erase(bound.first + pos + 1, std::strlen(attr)); + + if ((div[bound.first + pos] == div[bound.first + pos + 1]) && (' ' == div[bound.first + pos])) + div.erase(bound.first + pos, 1); + } + } + } + void place::field_visible(const char* name, bool vsb) { if (!name) name = ""; @@ -2707,7 +3119,10 @@ namespace nana auto div = impl_->search_div_name(impl_->root_division.get(), name); if (div) + { div->set_visible(vsb); + update_div(impl_->div_text, name, "invisible", !vsb ? update_operation::insert : update_operation::erase); + } } bool place::field_visible(const char* name) const @@ -2730,7 +3145,11 @@ namespace nana auto div = impl_->search_div_name(impl_->root_division.get(), name); if (div) + { + update_div(impl_->div_text, name, "invisible", update_operation::erase); + update_div(impl_->div_text, name, "undisplayed", !dsp ? update_operation::insert : update_operation::erase); div->set_display(dsp); + } } bool place::field_display(const char* name) const diff --git a/source/gui/widgets/listbox.cpp b/source/gui/widgets/listbox.cpp index 0dcccdd0..c2cdedf1 100644 --- a/source/gui/widgets/listbox.cpp +++ b/source/gui/widgets/listbox.cpp @@ -14,7 +14,8 @@ * leobackes(pr#86,pr#97) * Benjamin Navarro(pr#81) * besh81(pr#130) - * + * dankan1890(pr#158) + * */ #include @@ -3393,7 +3394,8 @@ namespace nana const auto border_color = essence_->scheme_ptr->header_bgcolor.get_color().blend(colors::black, 0.8); int text_top = (r.height - essence_->scheme_ptr->text_height) / 2 + r.y; - auto text_color = essence_->lister.wd_ptr()->fgcolor(); + auto text_color = essence_->scheme_ptr->header_fgcolor.get_color(); + auto state = item_state::normal; //check whether grabing an item, if item_spliter_ != npos, that indicates the grab item is a spliter. @@ -3784,7 +3786,7 @@ namespace nana if (item.flags.selected) bgcolor = bgcolor.blend(colors::black, 0.98); // or "selected" else - bgcolor = bgcolor.blend(essence_->scheme_ptr->item_selected, 0.7); /// \todo create a parametre for amount of blend + bgcolor = bgcolor.blend(essence_->scheme_ptr->item_highlighted, 0.7); /// \todo create a parametre for amount of blend } unsigned show_w = (std::min)(content_r.width, width - essence_->scroll.x_offset()); @@ -4506,12 +4508,28 @@ namespace nana return ess_->lister.relative_pair(pos_); } + bool item_proxy::displayed() const + { + if (!ess_->lister.get(pos_.cat)->expand) + return false; + + auto pos = to_display(); + if (ess_->scroll.offset_y_dpl > pos) + return false; + + auto size = ess_->number_of_lister_items(false); + + auto last = ess_->lister.advance(ess_->scroll.offset_y_dpl, size); + + return (last > pos || last == pos); + } + bool item_proxy::empty() const { return !ess_; } - item_proxy & item_proxy::check(bool ck) + item_proxy & item_proxy::check(bool ck, bool scroll_view) { internal_scope_guard lock; auto & m = cat_->items.at(pos_.item); @@ -4520,6 +4538,15 @@ namespace nana m.flags.checked = ck; ess_->lister.emit_checked(pos_); + if (scroll_view) + { + if (ess_->lister.get(pos_.cat)->expand) + ess_->lister.get(pos_.cat)->expand = false; + + if (!this->displayed()) + ess_->lister.scroll(pos_, !(ess_->scroll.offset_y_dpl > this->to_display())); + } + ess_->update(); } return *this; @@ -4531,26 +4558,37 @@ namespace nana } /// is ignored if no change (maybe set last_selected anyway??), but if change emit event, deselect others if need ans set/unset last_selected - item_proxy & item_proxy::select(bool s) + item_proxy & item_proxy::select(bool sel, bool scroll_view) { internal_scope_guard lock; //pos_ never represents a category if this item_proxy is available. 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 - - ess_->lister.emit_selected(this->pos_); - - if (m.flags.selected) + if (m.flags.selected != sel) { - 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_) + m.flags.selected = sel; // actually change selection + + ess_->lister.emit_selected(this->pos_); + + 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_abs = pos_; + } + else if (ess_->lister.last_selected_abs == pos_) ess_->lister.last_selected_abs.set_both(npos); - ess_->update(); + if (scroll_view) + { + if (ess_->lister.get(pos_.cat)->expand) + ess_->lister.get(pos_.cat)->expand = false; + + if (!this->displayed()) + ess_->lister.scroll(pos_, !(ess_->scroll.offset_y_dpl > this->to_display())); + } + + ess_->update(); + } return *this; } diff --git a/source/gui/widgets/menu.cpp b/source/gui/widgets/menu.cpp index ab812c77..1decffd5 100644 --- a/source/gui/widgets/menu.cpp +++ b/source/gui/widgets/menu.cpp @@ -10,6 +10,7 @@ * @file: nana/gui/widgets/menu.cpp * @contributors: * kmribti(pr#102) +* dankan1890(pr#158) */ #include @@ -1116,9 +1117,9 @@ namespace nana delete impl_; } - auto menu::append(const std::string& text, const event_fn_t& callback) -> item_proxy + auto menu::append(std::string text_utf8, const menu::event_fn_t& callback) -> item_proxy { - impl_->mbuilder.data().items.emplace_back(new item_type(text, callback)); + impl_->mbuilder.data().items.emplace_back(new item_type(std::move(text_utf8), callback)); return item_proxy(size() - 1, *impl_->mbuilder.data().items.back()); } @@ -1156,6 +1157,11 @@ namespace nana impl_->mbuilder.data().items.at(index)->image = img; } + void menu::text(std::size_t index, std::string text_utf8) + { + impl_->mbuilder.data().items.at(index)->text.swap(text_utf8); + } + bool menu::link(std::size_t index, menu& menu_obj) { if(impl_->mbuilder.set_sub_menu(index, menu_obj.impl_->mbuilder.data())) diff --git a/source/paint/detail/image_ico_ex.hpp b/source/paint/detail/image_ico_ex.hpp new file mode 100644 index 00000000..33f1d96c --- /dev/null +++ b/source/paint/detail/image_ico_ex.hpp @@ -0,0 +1,262 @@ +#ifndef NANA_PAINT_DETAIL_IMAGE_ICO_EX_HPP +#define NANA_PAINT_DETAIL_IMAGE_ICO_EX_HPP + +#include "image_pixbuf.hpp" + +namespace nana { +namespace paint { +namespace detail { +// These next two structs represent how the icon information is stored +// in an ICO file. +typedef struct +{ + uint8_t bWidth; // Width of the image + uint8_t bHeight; // Height of the image (times 2) + uint8_t bColorCount; // Number of colors in image (0 if >=8bpp) + uint8_t bReserved; // Reserved + uint16_t wPlanes; // Color Planes + uint16_t wBitCount; // Bits per pixel + uint32_t dwBytesInRes; // how many bytes in this resource? + uint32_t dwImageOffset; // where in the file is this image +} ICONDIRENTRY, *LPICONDIRENTRY; + +typedef struct +{ + uint16_t idReserved; // Reserved + uint16_t idType; // resource type (1 for icons) + uint16_t idCount; // how many images? + //ICONDIRENTRY idEntries[1]; // the entries for each image +} ICONDIR, *LPICONDIR; + +// size - 40 bytes +typedef struct +{ + uint32_t biSize; + uint32_t biWidth; + uint32_t biHeight; // Icon Height (added height of XOR-Bitmap and AND-Bitmap) + uint16_t biPlanes; + uint16_t biBitCount; + uint32_t biCompression; + int32_t biSizeImage; + uint32_t biXPelsPerMeter; + uint32_t biYPelsPerMeter; + uint32_t biClrUsed; + uint32_t biClrImportant; +} s_BITMAPINFOHEADER, *s_PBITMAPINFOHEADER; + +// 46 bytes +typedef struct +{ + s_BITMAPINFOHEADER icHeader; // DIB header + uint32_t icColors[1]; // Color table (short 4 bytes) //RGBQUAD + uint8_t icXOR[1]; // DIB bits for XOR mask + uint8_t icAND[1]; // DIB bits for AND mask +} ICONIMAGE, *LPICONIMAGE; + + +class image_ico_ex + : public basic_image_pixbuf +{ + bool _m_read_ico(const void* data, std::size_t size) + { + auto width = 0; + auto height = 0; + auto buffer = (unsigned char *)data; + auto icoDir = reinterpret_cast(buffer); + int iconsCount = icoDir->idCount; + if (icoDir->idReserved != 0 || icoDir->idType != 1 || iconsCount == 0 || iconsCount > 20) + return false; + + auto cursor = buffer; + cursor += 6; + auto dirEntry = reinterpret_cast(cursor); + auto maxSize = 0; + auto offset = 0; + auto maxBitCount = 0; + for (auto i = 0; i < iconsCount; i++, ++dirEntry) + { + int w = dirEntry->bWidth; + int h = dirEntry->bHeight; + int bitCount = dirEntry->wBitCount; + if (w * h > maxSize || bitCount > maxBitCount) // we choose icon with max resolution + { + width = w; + height = h; + offset = dirEntry->dwImageOffset; + maxSize = w * h; + maxBitCount = bitCount; + } + } + + if (offset == 0) return false; + + cursor = buffer; + cursor += offset; + auto icon = reinterpret_cast(cursor); + auto realBitsCount = static_cast(icon->icHeader.biBitCount); + auto hasAndMask = (realBitsCount < 32) && (height != icon->icHeader.biHeight); + cursor += 40; + pixbuf_.open(width, height); + + // rgba + vertical swap + if (realBitsCount >= 32) + { + for (auto x = 0; x < width; ++x) + for (auto y = 0; y < height; ++y) + { + pixbuf_.alpha_channel(true); + auto shift2 = 4 * (x + (height - y - 1) * width); + pixel_color_t image; + image.element.red = cursor[shift2 + 2]; + image.element.green = cursor[shift2 + 1]; + image.element.blue = cursor[shift2]; + image.element.alpha_channel = cursor[shift2 + 3]; + pixbuf_.pixel(x, y, image); + } + } + + else if (realBitsCount == 24) + { + for (auto x = 0; x < width; x++) + for (auto y = 0; y < height; y++) + { + pixbuf_.alpha_channel(true); + auto shift2 = 3 * (x + (height - y - 1) * width); + pixel_color_t image; + image.element.red = cursor[shift2 + 2]; + image.element.green = cursor[shift2 + 1]; + image.element.blue = cursor[shift2]; + image.element.alpha_channel = 255; + pixbuf_.pixel(x, y, image); + } + } + + else if (realBitsCount == 8) /// 256 colors + { + // 256 color table + auto colors = reinterpret_cast(cursor); + cursor += 256 * 4; + for (auto x = 0; x < width; x++) + for (auto y = 0; y < height; y++) + { + pixbuf_.alpha_channel(true); + + auto shift2 = (x + (height - y - 1) * width); + auto index = 4 * cursor[shift2]; + pixel_color_t image; + image.element.red = colors[index + 2]; + image.element.green = colors[index + 1]; + image.element.blue = colors[index]; + image.element.alpha_channel = 255; + pixbuf_.pixel(x, y, image); + } + } + + else if (realBitsCount == 4) /// 16 colors + { + // 16 color table + auto colors = reinterpret_cast(cursor); + cursor += 16 * 4; + for (auto x = 0; x < width; x++) + for (auto y = 0; y < height; y++) + { + auto shift2 = (x + (height - y - 1) * width); + auto index = cursor[shift2 / 2]; + if (shift2 % 2 == 0) + index = (index >> 4) & 0xF; + else + index = index & 0xF; + index *= 4; + pixbuf_.alpha_channel(true); + pixel_color_t image; + image.element.red = colors[index + 2]; + image.element.green = colors[index + 1]; + image.element.blue = colors[index]; + image.element.alpha_channel = 255; + pixbuf_.pixel(x, y, image); + } + } + + else if (realBitsCount == 1) /// 2 colors + { + // 2 color table + auto colors = reinterpret_cast(cursor); + cursor += 2 * 4; + auto boundary = width; //!!! 32 bit boundary (http://www.daubnet.com/en/file-format-ico) + while (boundary % 32 != 0) boundary++; + for (auto x = 0; x < width; x++) + for (auto y = 0; y < height; y++) + { + auto shift2 = (x + (height - y - 1) * boundary); + auto index = cursor[shift2 / 8]; + + // select 1 bit only + unsigned char bit = 7 - (x % 8); + index = (index >> bit) & 0x01; + index *= 4; + pixbuf_.alpha_channel(true); + pixel_color_t image; + image.element.red = colors[index + 2]; + image.element.green = colors[index + 1]; + image.element.blue = colors[index]; + image.element.alpha_channel = 255; + pixbuf_.pixel(x, y, image); + } + } + + // Read AND mask after base color data - 1 BIT MASK + if (hasAndMask) + { + auto boundary = width * realBitsCount; //!!! 32 bit boundary (http://www.daubnet.com/en/file-format-ico) + while (boundary % 32 != 0) boundary++; + cursor += boundary * height / 8; + boundary = width; + while (boundary % 32 != 0) boundary++; + for (auto y = 0; y < height; y++) + for (auto x = 0; x < width; x++) + { + unsigned char bit = 7 - (x % 8); + auto shift2 = (x + (height - y - 1) * boundary) / 8; + auto mask = (0x01 & (static_cast(cursor[shift2]) >> bit)); + auto pc = pixbuf_.pixel(x, y); + auto alpha = pc.element.alpha_channel; + alpha *= 1 - mask; + pc.element.alpha_channel = alpha; + pixbuf_.pixel(x, y, pc); + } + } + return true; + } +public: + + bool open(const std::experimental::filesystem::path& ico_file) override + { + std::ifstream file(ico_file.string(), std::ios::binary); + if (!file.is_open()) return false; + + // allocates a buffer for the image + file.seekg(0, std::ios::end); + const auto bytes = static_cast(file.tellg()); + file.seekg(0, std::ios::beg); + auto buffer = new char[bytes]; + + // read data from the file and set them in the buffer + file.read(buffer, bytes); + auto okret = _m_read_ico(buffer, bytes); + + // delete buffer and return + delete[] buffer; + return okret; + } + + bool open(const void* data, std::size_t bytes) override + { + return _m_read_ico(data, bytes); + } + +}; +}//end namespace detail +}//end namespace paint +}//end namespace nana + +#endif \ No newline at end of file diff --git a/source/paint/image.cpp b/source/paint/image.cpp index 498dd944..65ad6ba2 100644 --- a/source/paint/image.cpp +++ b/source/paint/image.cpp @@ -35,6 +35,7 @@ #include "detail/image_ico.hpp" #include "image_accessor.hpp" +#include "detail/image_ico_ex.hpp" namespace fs = std::experimental::filesystem; @@ -264,11 +265,7 @@ namespace paint { if (ext_ico == ext) { -#if defined(NANA_WINDOWS) - ptr = std::make_shared(true); -#else - return ptr; -#endif + ptr = std::make_shared(); break; } @@ -367,6 +364,8 @@ namespace paint ptr = std::make_shared(true); } #endif + else if (!ptr && bytes > 40 && (0x00010000 == *reinterpret_cast(data))) + ptr = std::make_shared(); } }