From 7ba93f21646c2763f06eb4902c9c60f2f1af3e0a Mon Sep 17 00:00:00 2001 From: Jinhao Date: Thu, 14 Mar 2019 06:31:52 +0800 Subject: [PATCH 01/16] add contribution description --- source/gui/widgets/treebox.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/source/gui/widgets/treebox.cpp b/source/gui/widgets/treebox.cpp index 5fb97c7b..eb859353 100644 --- a/source/gui/widgets/treebox.cpp +++ b/source/gui/widgets/treebox.cpp @@ -963,6 +963,9 @@ namespace nana if(text_r.right() > visible_w_pixels()) { node_state.tooltip = new tooltip_window(data.widget_ptr->handle(), text_r); + + //PR#406 Error Flynn's contribution + //fix: tooltip window doesn't have tree scheme & typeface API::dev::set_scheme(node_state.tooltip->handle(), API::dev::get_scheme(data.widget_ptr->handle())); node_state.tooltip->typeface(data.widget_ptr->typeface()); From 2b6c8e01804c67fdceaf30e56af249d98ce505e5 Mon Sep 17 00:00:00 2001 From: Vina Rodriguez Date: Tue, 19 Mar 2019 20:00:55 +0100 Subject: [PATCH 02/16] fix crash by using empty path to initialize folderbox with fs canonical() which don't exist --- source/gui/filebox.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/gui/filebox.cpp b/source/gui/filebox.cpp index 5b2db9e2..aff5b2a7 100644 --- a/source/gui/filebox.cpp +++ b/source/gui/filebox.cpp @@ -1359,7 +1359,7 @@ namespace nana path.resize(len); impl_->path = to_utf8(path); - } + } #endif } @@ -1589,7 +1589,7 @@ namespace nana }; folderbox::folderbox(window owner, const path_type& init_path, std::string title) - : impl_(new implement{ owner, fs::canonical(init_path).make_preferred(), title, false}) + : impl_(new implement{ owner, fs::weakly_canonical(init_path).make_preferred(), title, false}) {} From ea3082239bc8b429bfcd65a2bc7bcec5210177e7 Mon Sep 17 00:00:00 2001 From: Vina Rodriguez Date: Tue, 19 Mar 2019 20:04:31 +0100 Subject: [PATCH 03/16] implement filesystem path stem() in nana --- include/nana/filesystem/filesystem.hpp | 2 +- source/filesystem/filesystem.cpp | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/include/nana/filesystem/filesystem.hpp b/include/nana/filesystem/filesystem.hpp index f14c043f..beaa1545 100644 --- a/include/nana/filesystem/filesystem.hpp +++ b/include/nana/filesystem/filesystem.hpp @@ -254,7 +254,7 @@ namespace nana { namespace experimental { namespace filesystem path relative_path() const; path parent_path() const; path filename() const; - //path stem() const; + path stem() const; path extension() const; // query diff --git a/source/filesystem/filesystem.cpp b/source/filesystem/filesystem.cpp index b13e8465..061a12f7 100644 --- a/source/filesystem/filesystem.cpp +++ b/source/filesystem/filesystem.cpp @@ -225,8 +225,10 @@ namespace nana { namespace experimental { namespace filesystem //Because of No wide character version of POSIX #if defined(NANA_POSIX) const char* separators = "/"; + const char* punt = "."; #else const wchar_t* separators = L"/\\"; + const wchar_t* punt = L"."; #endif //class file_status @@ -453,6 +455,22 @@ namespace nana { namespace experimental { namespace filesystem return{ pathstr_ }; } + path path::stem() const + { + auto pos = pathstr_.find_last_of(separators); + auto ext = pathstr_.find_last_of(punt); + + if (pos == pathstr_.npos) + pos = 0; + else + pos++; + + if (ext == pathstr_.npos || ext < pos) + return path(pathstr_.substr(pos)); + else + return path(pathstr_.substr(pos, ext-pos)); + } + void path::clear() noexcept { pathstr_.clear(); From 83eab4ba6368dd6899795357d27b805ffb058f05 Mon Sep 17 00:00:00 2001 From: Vina Rodriguez Date: Tue, 19 Mar 2019 20:26:42 +0100 Subject: [PATCH 04/16] use gcc 8 to have weakly_canonical in travis ? --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 08c3d6f0..5677645e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,11 +8,11 @@ cache: matrix: include: - - env: CXX=g++-5 CC=gcc-5 + - env: CXX=g++-8 addons: apt: packages: - - g++-5 + - g++-8 - libjpeg8-dev - libpng-dev - libasound2-dev @@ -21,7 +21,7 @@ matrix: - libx11-dev - libxft-dev - libxcursor-dev - sources: + sources: - ubuntu-toolchain-r-test allow_failures: From db0812fdabe4a3bd4b6d1053dbf13ba4145350d0 Mon Sep 17 00:00:00 2001 From: qPCR4vir Date: Wed, 20 Mar 2019 17:26:38 +0100 Subject: [PATCH 05/16] nana hotfix point to nana-demo hotfix. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 5677645e..7b71b9ec 100644 --- a/.travis.yml +++ b/.travis.yml @@ -44,7 +44,7 @@ matrix: before_install: - cd .. - - git clone --depth=1 --branch=cmake-dev https://github.com/qPCR4vir/nana-demo.git nana-demo + - git clone --depth=1 --branch=hotfix https://github.com/qPCR4vir/nana-demo.git nana-demo - export PATH="$HOME/bin:$PATH" #- mkdir ~/bin #it seemd that a bin already exists from 20170901 From 01b7f6ff090ddfac118f9f69c4b69df48898ef45 Mon Sep 17 00:00:00 2001 From: Jinhao Date: Fri, 22 Mar 2019 06:11:24 +0800 Subject: [PATCH 06/16] add lexically_normal and weakly_canonical to nana.fs --- include/nana/filesystem/filesystem.hpp | 5 + source/filesystem/filesystem.cpp | 142 +++++++++++++++++++++++++ 2 files changed, 147 insertions(+) diff --git a/include/nana/filesystem/filesystem.hpp b/include/nana/filesystem/filesystem.hpp index beaa1545..64b37d97 100644 --- a/include/nana/filesystem/filesystem.hpp +++ b/include/nana/filesystem/filesystem.hpp @@ -290,6 +290,8 @@ namespace nana { namespace experimental { namespace filesystem // std::u16string generic_u16string() const; // std::u32string generic_u32string() const; + path lexically_normal() const; + //appends path& operator/=(const path& other); @@ -544,6 +546,9 @@ namespace std { path canonical(const path& p); path canonical(const path& p, std::error_code& err); + + path weakly_canonical(const path& p); + path weakly_canonical(const path& p, std::error_code& err); #endif #if defined(NANA_FILESYSTEM_FORCE) || defined(NANA_MINGW) diff --git a/source/filesystem/filesystem.cpp b/source/filesystem/filesystem.cpp index 061a12f7..16f3e448 100644 --- a/source/filesystem/filesystem.cpp +++ b/source/filesystem/filesystem.cpp @@ -225,9 +225,11 @@ namespace nana { namespace experimental { namespace filesystem //Because of No wide character version of POSIX #if defined(NANA_POSIX) const char* separators = "/"; + const char separator = '/'; const char* punt = "."; #else const wchar_t* separators = L"/\\"; + const char separator = '\\'; const wchar_t* punt = L"."; #endif @@ -568,6 +570,71 @@ namespace nana { namespace experimental { namespace filesystem std::replace(str.begin(), str.end(), '\\', '/'); // uppss ... revise this !!!!! return to_utf8(str); } + + path path::lexically_normal() const + { + if (pathstr_.empty()) + return *this; + + std::vector elements; + path temp{ pathstr_ }; + while (!temp.empty()) + { + elements.emplace_back(temp.filename()); + temp.remove_filename(); + } + + auto start = elements.begin(); + auto last = elements.end(); + auto stop = last--; + for (auto itr(start); itr != stop; ++itr) + { + // ignore "." except at start and last + if (itr->native().size() == 1 + && (itr->native())[0] == '.' + && itr != start + && itr != last) continue; + + // ignore a name and following ".." + if (!temp.empty() + && itr->native().size() == 2 + && (itr->native())[0] == '.' + && (itr->native())[1] == '.') // dot dot + { + string_type lf(temp.filename().native()); + if (lf.size() > 0 + && (lf.size() != 1 + || (lf[0] != '.' + && (lf[0] != '/' && lf[0] != '\\'))) + && (lf.size() != 2 + || (lf[0] != '.' + && lf[1] != '.' +# ifdef NANA_WINDOWS + && lf[1] != ':' +# endif + ) + ) + ) + { + temp.remove_filename(); + auto next = itr; + if (temp.empty() && ++next != stop + && next == last && last->string() == ".") + { + temp /= "."; + } + continue; + } + } + + temp /= *itr; + }; + + if (temp.empty()) + temp = "."; + return temp; + } + path & path::operator/=(const path& p) { if (p.empty()) @@ -1272,6 +1339,81 @@ namespace std { return canonical(p, &err); } + + bool try_throw(int err_val, const path& p, std::error_code* ec, const char* message) + { + if (0 == err_val) + { + if (ec) ec->clear(); + } + else + { //error + if (nullptr == ec) + throw (filesystem_error( + "nana::filesystem::canonical", p, + error_code(err_val, generic_category()))); + else + ec->assign(err_val, system_category()); + } + return err_val != 0; + } + + path weakly_canonical(const path& p, std::error_code* err) + { + path head{ p }; + + std::error_code tmp_err; + std::vector elements; + while (!head.empty()) + { + auto head_status = status(head, tmp_err); + + if (head_status.type() == file_type::unknown) + { + if (try_throw(static_cast(errc::invalid_argument), head, err, "nana::filesystem::weakly_canonical")) + return path{}; + } + if (head_status.type() != file_type::not_found) + break; + + elements.emplace_back(head.filename()); + head.remove_filename(); + } + + bool tail_has_dots = false; + path tail; + + for (auto & e : elements) + { + tail /= e; + // for a later optimization, track if any dot or dot-dot elements are present + if (e.native().size() <= 2 + && e.native()[0] == '.' + && (e.native().size() == 1 || e.native()[1] == '.')) + tail_has_dots = true; + } + + if (head.empty()) + return p.lexically_normal(); + head = canonical(head, tmp_err); + if (try_throw(tmp_err.value(), head, err, "nana::filesystem::weakly_canonical")) + return path(); + return tail.empty() + ? head + : (tail_has_dots // optimization: only normalize if tail had dot or dot-dot element + ? (head / tail).lexically_normal() + : head / tail); + } + + path weakly_canonical(const path& p) + { + return weakly_canonical(p, nullptr); + } + + path weakly_canonical(const path& p, std::error_code& err) + { + return weakly_canonical(p, &err); + } #endif #if defined(NANA_FILESYSTEM_FORCE) || defined(NANA_MINGW) From bd01cb447e0a79dffe1703faf30dda6fe7bd92fc Mon Sep 17 00:00:00 2001 From: Jinhao Date: Sun, 24 Mar 2019 20:03:45 +0800 Subject: [PATCH 07/16] fix bug that mouse wheel when displaying a msgbox(#411) --- source/gui/detail/bedrock_windows.cpp | 6 ++++-- source/gui/msgbox.cpp | 17 +++++++++++++++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/source/gui/detail/bedrock_windows.cpp b/source/gui/detail/bedrock_windows.cpp index bd02a00d..baece504 100644 --- a/source/gui/detail/bedrock_windows.cpp +++ b/source/gui/detail/bedrock_windows.cpp @@ -1092,7 +1092,9 @@ namespace detail //The focus window receives the message in Windows system, it should be redirected to the hovered window ::POINT scr_pos{ pmdec.mouse.x, pmdec.mouse.y}; //Screen position auto pointer_wd = ::WindowFromPoint(scr_pos); - if (pointer_wd == root_window) + + //Ignore the message if the window is disabled. + if ((pointer_wd == root_window) && ::IsWindowEnabled(root_window)) { ::ScreenToClient(pointer_wd, &scr_pos); auto scrolled_wd = wd_manager.find_window(reinterpret_cast(pointer_wd), { scr_pos.x, scr_pos.y }); @@ -1124,7 +1126,7 @@ namespace detail wd_manager.do_lazy_refresh(scrolled_wd, false); } } - else + else if (pointer_wd != root_window) { DWORD pid = 0; ::GetWindowThreadProcessId(pointer_wd, &pid); diff --git a/source/gui/msgbox.cpp b/source/gui/msgbox.cpp index 7263aa84..3c9d691b 100644 --- a/source/gui/msgbox.cpp +++ b/source/gui/msgbox.cpp @@ -1,7 +1,7 @@ /* * A Message Box Class * 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 @@ -453,7 +453,20 @@ namespace nana default: break; } - auto bt = ::MessageBoxW(reinterpret_cast(API::root(wd_)), to_wstring(sstream_.str()).c_str(), to_wstring(title_).c_str(), type); + //Disables the owner window to prevent the owner window processing mouse wheel event + //when the message box is showing and scroll the wheel on the owner window. + auto native = reinterpret_cast(API::root(wd_)); + BOOL enabled = FALSE; + if (native) + { + enabled = ::IsWindowEnabled(native); + if (enabled) + ::EnableWindow(native, FALSE); + } + auto bt = ::MessageBoxW(native, to_wstring(sstream_.str()).c_str(), to_wstring(title_).c_str(), type); + + if (native && enabled) + ::EnableWindow(native, TRUE); switch(bt) { From 5803395b7edf52180ec92c01c83619a4bf01cff7 Mon Sep 17 00:00:00 2001 From: Jinhao Date: Sun, 31 Mar 2019 12:44:22 +0800 Subject: [PATCH 08/16] improve place.modify it is allowed to modify a field with a new div-text which contains an existing name in the field being modified --- source/gui/place.cpp | 46 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/source/gui/place.cpp b/source/gui/place.cpp index 9b278111..9c2816a2 100644 --- a/source/gui/place.cpp +++ b/source/gui/place.cpp @@ -1,7 +1,7 @@ /* * An Implementation of Place for Layout * 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 @@ -630,7 +630,8 @@ namespace nana void collocate(); static division * search_div_name(division* start, const std::string&) noexcept; - std::unique_ptr scan_div(place_parts::tokenizer&); + + std::unique_ptr scan_div(place_parts::tokenizer&, const std::string& ignore_duplicate = {}); void check_unique(const division*) const; //connect the field/dock with div object @@ -2706,7 +2707,8 @@ namespace nana throw std::invalid_argument("nana.place: the type of the " + std::string{pos_strs[pos]} +"th parameter for collapse should be integer."); } - auto place::implement::scan_div(place_parts::tokenizer& tknizer) -> std::unique_ptr + //ignore_duplicate A field is allowed to have same name if its has an ancestor which name is same with ignore_duplicate. + auto place::implement::scan_div(place_parts::tokenizer& tknizer, const std::string& ignore_duplicate) -> std::unique_ptr { using token = place_parts::tokenizer::token ; @@ -2757,7 +2759,7 @@ namespace nana break; case token::div_start: { - auto div = scan_div(tknizer); + auto div = scan_div(tknizer, ignore_duplicate); if (!children.empty()) children.back()->div_next = div.get(); @@ -2897,7 +2899,30 @@ namespace nana attached_field = i->second; //the field is attached to a division, it means there is another division with same name. if (attached_field->attached) - throw std::runtime_error("place, the name '" + name + "' is redefined."); + { + //The fields are allowed to have a same name. E.g. + //place.div("A "); + //place.modify("A", ""); Here the same name B must be allowed, otherwise it throws runtime error. + + bool allow_same_name = false; + if (!ignore_duplicate.empty()) + { + auto f = attached_field->attached->div_owner; + while (f) + { + if (f->name == ignore_duplicate) + { + allow_same_name = true; + break; + } + + f = f->div_owner; + } + } + + if (!allow_same_name) + throw std::runtime_error("place, the name '" + name + "' is redefined."); + } } token unmatch = token::width; @@ -2977,7 +3002,16 @@ namespace nana //attach the field to the division div->field = attached_field; if (attached_field) + { + //Replaces the previous div with the new div which is allowed to have a same name. + + //Detaches the field from the previous div. + if (attached_field->attached) + attached_field->attached->field = nullptr; + + //Attaches new div attached_field->attached = div.get(); + } if (children.size()) { @@ -3263,7 +3297,7 @@ namespace nana try { place_parts::tokenizer tknizer(div_text); - auto modified = impl_->scan_div(tknizer); + auto modified = impl_->scan_div(tknizer, name); auto modified_ptr = modified.get(); modified_ptr->name = name; From 73c68def7ec2bb7d3af1a4370209cb862f66b408 Mon Sep 17 00:00:00 2001 From: Jinhao Date: Mon, 1 Apr 2019 06:25:18 +0800 Subject: [PATCH 09/16] fix bug that child widgets may not be updated during event handling --- source/gui/detail/window_manager.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/source/gui/detail/window_manager.cpp b/source/gui/detail/window_manager.cpp index 6349685d..f7def397 100644 --- a/source/gui/detail/window_manager.cpp +++ b/source/gui/detail/window_manager.cpp @@ -1089,9 +1089,14 @@ namespace detail } else window_layer::paint(wd, paint_operation::try_refresh, refresh_tree); //only refreshing if it has an invisible parent + + //Map the requested children. + this->map_requester(wd); } + else + wd->other.mapping_requester.clear(); + wd->other.upd_state = core_window_t::update_state::none; - wd->other.mapping_requester.clear(); } void window_manager::map_requester(core_window_t* wd) From 6b8a89823966b4216e695d4b540cd089a3a2f1e9 Mon Sep 17 00:00:00 2001 From: Jinhao Date: Tue, 2 Apr 2019 01:19:07 +0800 Subject: [PATCH 10/16] improve fix 73c68def7ec2bb7d3af1a4370209cb862f66b408 --- source/gui/detail/window_layout.cpp | 4 ++++ source/gui/detail/window_manager.cpp | 6 +++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/source/gui/detail/window_layout.cpp b/source/gui/detail/window_layout.cpp index 12b5f86f..d23d2d05 100644 --- a/source/gui/detail/window_layout.cpp +++ b/source/gui/detail/window_layout.cpp @@ -39,6 +39,10 @@ namespace nana } else _m_paint_glass_window(wd, (paint_operation::try_refresh == operation), req_refresh_children, false, true); + + //maproot and _m_paint_glass_window always copy the children graphics, therefore the mapping requester should + //be cleared to avoid redundant copying + wd->other.mapping_requester.clear(); } bool window_layout::maproot(core_window_t* wd, bool have_refreshed, bool req_refresh_children) diff --git a/source/gui/detail/window_manager.cpp b/source/gui/detail/window_manager.cpp index f7def397..e1510423 100644 --- a/source/gui/detail/window_manager.cpp +++ b/source/gui/detail/window_manager.cpp @@ -1086,12 +1086,12 @@ namespace detail if (wd->flags.action != wd->flags.action_before) this->map(wd, true); } + + //Map the requested children. + this->map_requester(wd); } else window_layer::paint(wd, paint_operation::try_refresh, refresh_tree); //only refreshing if it has an invisible parent - - //Map the requested children. - this->map_requester(wd); } else wd->other.mapping_requester.clear(); From a4c3784efe2d30915ee3cbc6b5c1ebdfb323ebaf Mon Sep 17 00:00:00 2001 From: Jinhao Date: Wed, 3 Apr 2019 23:59:12 +0800 Subject: [PATCH 11/16] fix bug that wd.find_window unexpectedly returns wd.find_window unexpectedly returns a wrong handle if captured window ignores children windows. --- include/nana/gui/detail/window_manager.hpp | 6 ++++- source/gui/detail/window_manager.cpp | 29 +++++++++++++++++++--- source/gui/programming_interface.cpp | 2 +- 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/include/nana/gui/detail/window_manager.hpp b/include/nana/gui/detail/window_manager.hpp index 82e3c57c..d3f36b27 100644 --- a/include/nana/gui/detail/window_manager.hpp +++ b/include/nana/gui/detail/window_manager.hpp @@ -99,7 +99,11 @@ namespace detail bool show(core_window_t* wd, bool visible); - core_window_t* find_window(native_window_type root, const point& pos); + //find a widget window at specified position + //@param root A root window + //@param pos Position + //@param ignore_captured A flag indicates whether to ignore redirecting the result to its captured window. If this paramter is true, it returns the window at the position, if the parameter is false, it returns the captured window if the captured window don't ignore children. + core_window_t* find_window(native_window_type root, const point& pos, bool ignore_captured = false); //move the wnd and its all children window, x and y is a relatively coordinate for wnd's parent window bool move(core_window_t*, int x, int y, bool passive); diff --git a/source/gui/detail/window_manager.cpp b/source/gui/detail/window_manager.cpp index e1510423..8a653939 100644 --- a/source/gui/detail/window_manager.cpp +++ b/source/gui/detail/window_manager.cpp @@ -701,19 +701,40 @@ namespace detail return true; } - window_manager::core_window_t* window_manager::find_window(native_window_type root, const point& pos) + window_manager::core_window_t* window_manager::find_window(native_window_type root, const point& pos, bool ignore_captured) { if (nullptr == root) return nullptr; - if((false == attr_.capture.ignore_children) || (nullptr == attr_.capture.window) || (attr_.capture.window->root != root)) + //Thread-Safe Required! + std::lock_guard lock(mutex_); + + if (ignore_captured || (nullptr == attr_.capture.window)) { - //Thread-Safe Required! - std::lock_guard lock(mutex_); auto rrt = root_runtime(root); if (rrt && _m_effective(rrt->window, pos)) return _m_find(rrt->window, pos); + + return nullptr; } + + if (attr_.capture.ignore_children) + return attr_.capture.window; + + auto rrt = root_runtime(root); + if (rrt && _m_effective(rrt->window, pos)) + { + auto target = _m_find(rrt->window, pos); + + auto p = target; + while (p) + { + if (p == attr_.capture.window) + return target; + p = p->parent; + } + } + return attr_.capture.window; } diff --git a/source/gui/programming_interface.cpp b/source/gui/programming_interface.cpp index 148667ff..85d4d4c5 100644 --- a/source/gui/programming_interface.cpp +++ b/source/gui/programming_interface.cpp @@ -1403,7 +1403,7 @@ namespace API ::nana::point clipos{pos}; interface_type::calc_window_point(wd, clipos); return reinterpret_cast( - restrict::wd_manager().find_window(wd, clipos)); + restrict::wd_manager().find_window(wd, clipos, true)); } return nullptr; } From d931ebb61c3702aeeb445d3c0a29a87411a1d60a Mon Sep 17 00:00:00 2001 From: Jinhao Date: Wed, 10 Apr 2019 00:41:04 +0800 Subject: [PATCH 12/16] filter out the middle and right button for item selection(#413) --- source/gui/widgets/combox.cpp | 7 +++++-- source/gui/widgets/tabbar.cpp | 9 +++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/source/gui/widgets/combox.cpp b/source/gui/widgets/combox.cpp index 619446a2..6e949def 100644 --- a/source/gui/widgets/combox.cpp +++ b/source/gui/widgets/combox.cpp @@ -1,7 +1,7 @@ /* * A Combox 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 @@ -676,7 +676,10 @@ namespace nana { auto * editor = drawer_->editor(); editor->mouse_pressed(arg); - drawer_->open_lister_if_push_button_positioned(); + + //Pops up the droplist only if left button is clicked + if(arg.is_left_button()) + drawer_->open_lister_if_push_button_positioned(); drawer_->draw(); if(editor->attr().editable) diff --git a/source/gui/widgets/tabbar.cpp b/source/gui/widgets/tabbar.cpp index cf58e0f6..3cbe921d 100644 --- a/source/gui/widgets/tabbar.cpp +++ b/source/gui/widgets/tabbar.cpp @@ -1,6 +1,6 @@ /* * A Tabbar Implementation - * 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 @@ -1291,7 +1291,8 @@ namespace nana void trigger::mouse_down(graph_reference, const arg_mouse& arg) { - if(layouter_->press()) + //Activates the tab only if left button is clicked. + if(arg.is_left_button() && layouter_->press()) { if(false == layouter_->active_by_trace(arg)) layouter_->toolbox_answer(arg); @@ -1593,10 +1594,10 @@ namespace nana API::dev::lazy_refresh(); } - void driver::mouse_down(graph_reference graph, const arg_mouse&) + void driver::mouse_down(graph_reference graph, const arg_mouse& arg) { auto & indexes = model_->get_indexes(); - if ((indexes.hovered_pos == model_->npos) || (indexes.active_pos == indexes.hovered_pos)) + if ((indexes.hovered_pos == model_->npos) || (indexes.active_pos == indexes.hovered_pos) || !arg.is_left_button()) return; if (indexes.active_pos != indexes.hovered_pos) From 682e92c14de283d7402189cd26a166631c39a4c9 Mon Sep 17 00:00:00 2001 From: Jinhao Date: Wed, 10 Apr 2019 01:19:36 +0800 Subject: [PATCH 13/16] add check for matching numbers of left/right brackets of div-text --- source/gui/place.cpp | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/source/gui/place.cpp b/source/gui/place.cpp index 9c2816a2..ee1970ae 100644 --- a/source/gui/place.cpp +++ b/source/gui/place.cpp @@ -631,7 +631,7 @@ namespace nana static division * search_div_name(division* start, const std::string&) noexcept; - std::unique_ptr scan_div(place_parts::tokenizer&, const std::string& ignore_duplicate = {}); + std::unique_ptr scan_div(place_parts::tokenizer&, bool implicitly_started, const std::string& ignore_duplicate = {}); void check_unique(const division*) const; //connect the field/dock with div object @@ -1988,7 +1988,7 @@ namespace nana std::string::size_type tag_pos{ left ? div.find('<', bound.second + 2) : div.rfind('>', bound.first - 2) }; if (div.npos == tag_pos) - throw std::runtime_error("place report an issue if it throws"); + throw std::invalid_argument("please report an issue if it throws"); auto other_bound = get_field_boundary(div, tag_pos); @@ -2707,8 +2707,9 @@ namespace nana throw std::invalid_argument("nana.place: the type of the " + std::string{pos_strs[pos]} +"th parameter for collapse should be integer."); } + //implicitly_started indicates whether the field in div-text starts without < mark. //ignore_duplicate A field is allowed to have same name if its has an ancestor which name is same with ignore_duplicate. - auto place::implement::scan_div(place_parts::tokenizer& tknizer, const std::string& ignore_duplicate) -> std::unique_ptr + auto place::implement::scan_div(place_parts::tokenizer& tknizer, bool implicitly_started, const std::string& ignore_duplicate) -> std::unique_ptr { using token = place_parts::tokenizer::token ; @@ -2730,7 +2731,8 @@ namespace nana bool undisplayed = false; bool invisible = false; - for (token tk = tknizer.read(); (tk != token::eof && tk != token::div_end); tk = tknizer.read()) + token tk = token::eof; + for (tk = tknizer.read(); (tk != token::eof && tk != token::div_end); tk = tknizer.read()) { switch (tk) { @@ -2759,7 +2761,7 @@ namespace nana break; case token::div_start: { - auto div = scan_div(tknizer, ignore_duplicate); + auto div = scan_div(tknizer, false, ignore_duplicate); if (!children.empty()) children.back()->div_next = div.get(); @@ -2889,6 +2891,9 @@ namespace nana } } + if (implicitly_started && (tk == token::div_end)) + throw std::invalid_argument("nana.place: the div-text ends prematurely at " + std::to_string(tknizer.pos())); + field_gather * attached_field = nullptr; //find the field with specified name. @@ -3228,7 +3233,7 @@ namespace nana { place_parts::tokenizer tknizer(div_text.c_str()); impl_->disconnect(); - auto div = impl_->scan_div(tknizer); + auto div = impl_->scan_div(tknizer, true); try { impl_->connect(div.get()); //throws if there is a redefined name of field. From 3716961eb497e4688bcda44444b50c4ca41bc22b Mon Sep 17 00:00:00 2001 From: Jinhao Date: Wed, 10 Apr 2019 23:13:37 +0800 Subject: [PATCH 14/16] small fix that wrong parameter is passed to scan_div --- source/gui/place.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/gui/place.cpp b/source/gui/place.cpp index ee1970ae..90b9b189 100644 --- a/source/gui/place.cpp +++ b/source/gui/place.cpp @@ -3302,7 +3302,7 @@ namespace nana try { place_parts::tokenizer tknizer(div_text); - auto modified = impl_->scan_div(tknizer, name); + auto modified = impl_->scan_div(tknizer, true, name); auto modified_ptr = modified.get(); modified_ptr->name = name; From cff5a0e1c4d76825738fddbe6c03bbb2c9d16a23 Mon Sep 17 00:00:00 2001 From: Jinhao Date: Wed, 10 Apr 2019 23:17:20 +0800 Subject: [PATCH 15/16] hides place.splitter if one of leaves is undisplayed --- source/gui/place.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/source/gui/place.cpp b/source/gui/place.cpp index 90b9b189..92625b8a 100644 --- a/source/gui/place.cpp +++ b/source/gui/place.cpp @@ -2756,6 +2756,11 @@ namespace nana { auto splitter = new div_splitter(tknizer.number(), this); children.back()->div_next = splitter; + + //Hides the splitter if its left leaf is undisplayed. + if (!children.back()->display) + splitter->display = false; + children.emplace_back(std::unique_ptr{ splitter }); } break; @@ -2763,7 +2768,13 @@ namespace nana { auto div = scan_div(tknizer, false, ignore_duplicate); if (!children.empty()) + { + //Hides the splitter if its right leaf is undisplayed. + if ((children.back()->kind_of_division == division::kind::splitter) && !div->display) + children.back()->display = false; + children.back()->div_next = div.get(); + } children.emplace_back(std::move(div)); } From cff318d182bf49e54a040450ddeeb82c8e259713 Mon Sep 17 00:00:00 2001 From: Jinhao Date: Sat, 13 Apr 2019 00:03:40 +0800 Subject: [PATCH 16/16] enhance the performance of nana --- include/nana/gui/detail/basic_window.hpp | 13 +++-- include/nana/gui/detail/window_manager.hpp | 1 - source/gui/detail/basic_window.cpp | 38 ++++++++++++- source/gui/detail/bedrock_pi.cpp | 8 --- source/gui/detail/bedrock_posix.cpp | 38 ++++++++++++- source/gui/detail/bedrock_windows.cpp | 51 +++++++++++++++--- source/gui/detail/window_layout.cpp | 4 -- source/gui/detail/window_manager.cpp | 62 +++++----------------- 8 files changed, 140 insertions(+), 75 deletions(-) diff --git a/include/nana/gui/detail/basic_window.hpp b/include/nana/gui/detail/basic_window.hpp index b79ec168..bc0a116f 100644 --- a/include/nana/gui/detail/basic_window.hpp +++ b/include/nana/gui/detail/basic_window.hpp @@ -127,6 +127,9 @@ namespace detail basic_window * seek_non_lite_widget_ancestor() const; void set_action(mouse_action); + + /// Only refresh when the root of window is in lazy-updating mode + bool try_lazy_update(bool try_refresh); public: /// Override event_holder bool set_events(const std::shared_ptr&) override; @@ -176,8 +179,7 @@ namespace detail bool ignore_mouse_focus : 1; ///< A flag indicates whether the widget accepts focus when clicking on it bool space_click_enabled : 1; ///< A flag indicates whether enable mouse_down/click/mouse_up when pressing and releasing whitespace key. bool draggable : 1; - bool ignore_child_mapping : 1; - unsigned Reserved :16; + unsigned Reserved : 17; unsigned char tab; ///< indicate a window that can receive the keyboard TAB mouse_action action; mouse_action action_before; @@ -205,11 +207,14 @@ namespace detail { struct attr_root_tag { + bool ime_enabled{ false }; + bool lazy_update{ false }; ///< Indicates whether the window is in lazy-updating mode. + + container update_requesters; ///< Container for lazy-updating requesting windows. container tabstop; std::vector effects_edge_nimbus; basic_window* focus{nullptr}; basic_window* menubar{nullptr}; - bool ime_enabled{false}; cursor state_cursor{nana::cursor::arrow}; basic_window* state_cursor_window{ nullptr }; @@ -223,8 +228,6 @@ namespace detail update_state upd_state; dragdrop_status dnd_state{ dragdrop_status::not_ready }; - container mapping_requester; ///< Children which are ignored to mapping - union { attr_root_tag * root; diff --git a/include/nana/gui/detail/window_manager.hpp b/include/nana/gui/detail/window_manager.hpp index d3f36b27..d9c26326 100644 --- a/include/nana/gui/detail/window_manager.hpp +++ b/include/nana/gui/detail/window_manager.hpp @@ -120,7 +120,6 @@ namespace detail void refresh_tree(core_window_t*); void do_lazy_refresh(core_window_t*, bool force_copy_to_screen, bool refresh_tree = false); - void map_requester(core_window_t*); bool set_parent(core_window_t* wd, core_window_t* new_parent); core_window_t* set_focus(core_window_t*, bool root_has_been_focused, arg_focus::reason); diff --git a/source/gui/detail/basic_window.cpp b/source/gui/detail/basic_window.cpp index a52b2215..e52d402e 100644 --- a/source/gui/detail/basic_window.cpp +++ b/source/gui/detail/basic_window.cpp @@ -336,6 +336,43 @@ namespace nana flags.action = act; } + + bool basic_window::try_lazy_update(bool try_refresh) + { + if (drawer.graphics.empty()) + return true; + + if (!this->root_widget->other.attribute.root->lazy_update) + return false; + + if (nullptr == effect.bground) + { + if (try_refresh) + { + flags.refreshing = true; + drawer.refresh(); + flags.refreshing = false; + } + } + + for (auto i = this->root_widget->other.attribute.root->update_requesters.cbegin(); i != this->root_widget->other.attribute.root->update_requesters.cend();) + { + auto req = *i; + //Avoid redundancy, don't insert the window if it or its ancestor window already exist in the container. + if ((req == this) || req->is_ancestor_of(this)) + return true; + + //If there is a window which is a child or child's child of the window, remove it. + if (this->is_ancestor_of(req)) + i = this->root_widget->other.attribute.root->update_requesters.erase(i); + else + ++i; + } + + this->root_widget->other.attribute.root->update_requesters.push_back(this); + return true; + } + void basic_window::_m_init_pos_and_size(basic_window* parent, const rectangle& r) { pos_owner = pos_root = r.position(); @@ -390,7 +427,6 @@ namespace nana flags.ignore_menubar_focus = false; flags.ignore_mouse_focus = false; flags.space_click_enabled = false; - flags.ignore_child_mapping = false; visible = false; diff --git a/source/gui/detail/bedrock_pi.cpp b/source/gui/detail/bedrock_pi.cpp index 65d644ec..0cd1e796 100644 --- a/source/gui/detail/bedrock_pi.cpp +++ b/source/gui/detail/bedrock_pi.cpp @@ -641,13 +641,8 @@ namespace nana if (update_state::none == wd->other.upd_state) wd->other.upd_state = update_state::lazy; - auto ignore_mapping_value = wd->flags.ignore_child_mapping; - wd->flags.ignore_child_mapping = true; - _m_emit_core(evt_code, wd, false, arg, bForce__EmitInternal); - wd->flags.ignore_child_mapping = ignore_mapping_value; - bool good_wd = false; if(wd_manager().available(wd)) { @@ -658,10 +653,7 @@ namespace nana wd_manager().do_lazy_refresh(wd, false, (event_code::resized == evt_code)); } else - { - wd_manager().map_requester(wd); wd->other.upd_state = update_state::none; - } good_wd = true; } diff --git a/source/gui/detail/bedrock_posix.cpp b/source/gui/detail/bedrock_posix.cpp index 65e66701..9c96a7cd 100644 --- a/source/gui/detail/bedrock_posix.cpp +++ b/source/gui/detail/bedrock_posix.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include "inner_fwd_implement.hpp" #include @@ -555,6 +556,27 @@ namespace detail context.is_alt_pressed = false; } + class window_proc_guard + { + public: + window_proc_guard(detail::basic_window* wd) : + root_wd_(wd) + { + root_wd_->other.attribute.root->lazy_update = true; + } + + ~window_proc_guard() + { + if (!bedrock::instance().wd_manager().available(root_wd_)) + return; + + root_wd_->other.attribute.root->lazy_update = false; + root_wd_->other.attribute.root->update_requesters.clear(); + } + private: + detail::basic_window* const root_wd_; + }; + void window_proc_for_xevent(Display* /*display*/, XEvent& xevent) { typedef detail::bedrock::core_window_t core_window_t; @@ -569,10 +591,13 @@ namespace detail if(root_runtime) { - auto msgwnd = root_runtime->window; + auto const root_wd = root_runtime->window; + auto msgwnd = root_wd; + window_proc_guard wp_guard{ root_wd }; + auto& context = *brock.get_thread_context(msgwnd->thread_id); - auto pre_event_window = context.event_window; + auto const pre_event_window = context.event_window; auto pressed_wd = root_runtime->condition.pressed; auto pressed_wd_space = root_runtime->condition.pressed_by_space; auto hovered_wd = root_runtime->condition.hovered; @@ -1190,6 +1215,15 @@ namespace detail } } + if (wd_manager.available(root_wd) && root_wd->other.attribute.root->update_requesters.size()) + { + for (auto wd : root_wd->other.attribute.root->update_requesters) + { + window_layout::paint(wd, window_layout::paint_operation::have_refreshed, false); + wd_manager.map(wd, true); + } + } + root_runtime = wd_manager.root_runtime(native_window); if(root_runtime) { diff --git a/source/gui/detail/bedrock_windows.cpp b/source/gui/detail/bedrock_windows.cpp index baece504..551cda69 100644 --- a/source/gui/detail/bedrock_windows.cpp +++ b/source/gui/detail/bedrock_windows.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include "inner_fwd_implement.hpp" @@ -37,6 +38,7 @@ #include "bedrock_types.hpp" + typedef void (CALLBACK *win_event_proc_t)(HWINEVENTHOOK hWinEventHook, DWORD event, HWND hwnd, LONG idObject, LONG idChild, DWORD dwEventThread, DWORD dwmsEventTime); namespace nana @@ -738,6 +740,27 @@ namespace detail return static_cast(vkey); } + class window_proc_guard + { + public: + window_proc_guard(detail::basic_window* wd) : + root_wd_(wd) + { + root_wd_->other.attribute.root->lazy_update = true; + } + + ~window_proc_guard() + { + if (!bedrock::instance().wd_manager().available(root_wd_)) + return; + + root_wd_->other.attribute.root->lazy_update = false; + root_wd_->other.attribute.root->update_requesters.clear(); + } + private: + detail::basic_window* const root_wd_; + }; + LRESULT CALLBACK Bedrock_WIN32_WindowProc(HWND root_window, UINT message, WPARAM wParam, LPARAM lParam) { LRESULT window_proc_value = 0; @@ -757,6 +780,7 @@ namespace detail bool def_window_proc = false; auto& context = *brock.get_thread_context(); + auto const pre_event_window = context.event_window; auto pressed_wd = root_runtime->condition.pressed; auto pressed_wd_space = root_runtime->condition.pressed_by_space; auto hovered_wd = root_runtime->condition.hovered; @@ -766,7 +790,10 @@ namespace detail pmdec.raw_param.wparam = wParam; internal_scope_guard lock; - auto msgwnd = root_runtime->window; + auto const root_wd = root_runtime->window; + auto msgwnd = root_wd; + + window_proc_guard wp_guard{ root_wd }; switch (message) { @@ -775,10 +802,7 @@ namespace detail { auto i = root_runtime->wpassoc->accel_commands.find(LOWORD(wParam)); if (i != root_runtime->wpassoc->accel_commands.end()) - { - auto fn = i->second; - fn(); - } + i->second(); } break; case WM_IME_STARTCOMPOSITION: @@ -1452,7 +1476,7 @@ namespace detail wd_manager.do_lazy_refresh(msgwnd, false); } } - return 0; + break; case WM_KEYUP: if(wParam != VK_MENU) //MUST NOT BE AN ALT { @@ -1553,13 +1577,28 @@ namespace detail def_window_proc = true; } + if (wd_manager.available(root_wd) && root_wd->other.attribute.root->update_requesters.size()) + { + for (auto wd : root_wd->other.attribute.root->update_requesters) + { + window_layout::paint(wd, window_layout::paint_operation::have_refreshed, false); + wd_manager.map(wd, true); + } + } + root_runtime = wd_manager.root_runtime(native_window); if(root_runtime) { + context.event_window = pre_event_window; root_runtime->condition.pressed = pressed_wd; root_runtime->condition.hovered = hovered_wd; root_runtime->condition.pressed_by_space = pressed_wd_space; } + else + { + auto context = brock.get_thread_context(); + if(context) context->event_window = pre_event_window; + } if (!def_window_proc) return 0; diff --git a/source/gui/detail/window_layout.cpp b/source/gui/detail/window_layout.cpp index d23d2d05..12b5f86f 100644 --- a/source/gui/detail/window_layout.cpp +++ b/source/gui/detail/window_layout.cpp @@ -39,10 +39,6 @@ namespace nana } else _m_paint_glass_window(wd, (paint_operation::try_refresh == operation), req_refresh_children, false, true); - - //maproot and _m_paint_glass_window always copy the children graphics, therefore the mapping requester should - //be cleared to avoid redundant copying - wd->other.mapping_requester.clear(); } bool window_layout::maproot(core_window_t* wd, bool have_refreshed, bool req_refresh_children) diff --git a/source/gui/detail/window_manager.cpp b/source/gui/detail/window_manager.cpp index 8a653939..0d7d0eb8 100644 --- a/source/gui/detail/window_manager.cpp +++ b/source/gui/detail/window_manager.cpp @@ -1001,28 +1001,7 @@ namespace detail //Thread-Safe Required! std::lock_guard lock(mutex_); if (impl_->wd_register.available(wd) && !wd->is_draw_through()) - { - auto parent = wd->parent; - while (parent) - { - if(parent->flags.ignore_child_mapping || parent->flags.refreshing) - { - auto top = parent; - while(parent->parent) - { - parent = parent->parent; - if(parent->flags.ignore_child_mapping || parent->flags.refreshing) - top = parent; - } - - top->other.mapping_requester.push_back(wd); - return; - } - parent = parent->parent; - } - bedrock::instance().flush_surface(wd, forced, update_area); - } } //update @@ -1049,8 +1028,11 @@ namespace detail { if (!wd->flags.refreshing) { - window_layer::paint(wd, (redraw ? paint_operation::try_refresh : paint_operation::none), false); - this->map(wd, forced, update_area); + if (!wd->try_lazy_update(redraw)) + { + window_layer::paint(wd, (redraw ? paint_operation::try_refresh : paint_operation::none), false); + this->map(wd, forced, update_area); + } return true; } else if (forced) @@ -1097,46 +1079,30 @@ namespace detail { if ((wd->other.upd_state == core_window_t::update_state::refreshed) || (wd->other.upd_state == core_window_t::update_state::request_refresh) || force_copy_to_screen) { - window_layer::paint(wd, (wd->other.upd_state == core_window_t::update_state::request_refresh ? paint_operation::try_refresh : paint_operation::have_refreshed), refresh_tree); - this->map(wd, force_copy_to_screen); + if (!wd->try_lazy_update(wd->other.upd_state == core_window_t::update_state::request_refresh)) + { + window_layer::paint(wd, (wd->other.upd_state == core_window_t::update_state::request_refresh ? paint_operation::try_refresh : paint_operation::have_refreshed), refresh_tree); + this->map(wd, force_copy_to_screen); + } } else if (effects::edge_nimbus::none != wd->effect.edge_nimbus) { //The window is still mapped because of edge nimbus effect. //Avoid duplicate copy if action state is not changed and the window is not focused. if (wd->flags.action != wd->flags.action_before) - this->map(wd, true); + { + if (!wd->try_lazy_update(false)) + this->map(wd, true); + } } - - //Map the requested children. - this->map_requester(wd); } else window_layer::paint(wd, paint_operation::try_refresh, refresh_tree); //only refreshing if it has an invisible parent } - else - wd->other.mapping_requester.clear(); wd->other.upd_state = core_window_t::update_state::none; } - void window_manager::map_requester(core_window_t* wd) - { - //Thread-Safe Required! - std::lock_guard lock(mutex_); - - if (false == impl_->wd_register.available(wd)) - return; - - if (wd->visible_parents()) - { - for(auto requestor : wd->other.mapping_requester) - this->map(requestor, true); - } - - wd->other.mapping_requester.clear(); - } - bool window_manager::set_parent(core_window_t* wd, core_window_t* newpa) { //Thread-Safe Required!