From a10d2aa64ab3a03e60b5ab17b2a71c955c3572a2 Mon Sep 17 00:00:00 2001 From: cnjinhao Date: Wed, 15 May 2024 19:44:24 +0800 Subject: [PATCH] changes --- include/nana/gui/detail/virtual_keyboard.hpp | 6 +- .../gui/widgets/skeletons/text_editor.hpp | 5 +- source/gui/detail/keyboard_im.hpp | 23 +-- source/gui/detail/native_window_interface.cpp | 1 - source/gui/detail/virtual_keyboard.cpp | 185 +++++++++++++----- source/gui/filebox.cpp | 2 - source/gui/programming_interface.cpp | 4 + source/gui/widgets/menubar.cpp | 2 +- source/gui/widgets/skeletons/text_editor.cpp | 68 +++++-- 9 files changed, 204 insertions(+), 92 deletions(-) diff --git a/include/nana/gui/detail/virtual_keyboard.hpp b/include/nana/gui/detail/virtual_keyboard.hpp index 6fef68100..7dc709eff 100644 --- a/include/nana/gui/detail/virtual_keyboard.hpp +++ b/include/nana/gui/detail/virtual_keyboard.hpp @@ -1,7 +1,7 @@ /* * Virtual Keyboard Implementations * Nana C++ Library(http://www.nanapro.org) -* Copyright(C) 2003-2023 Jinhao(cnjinhao@hotmail.com) +* Copyright(C) 2003-2024 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -41,9 +41,11 @@ namespace nana::detail virtual_keyboard(); ~virtual_keyboard(); + static std::string& default_im_value() noexcept; + void attach(window); bool qwerty(window, std::vector langs, behaves, modes); - bool numeric(window); + bool numeric(window, bool padding); private: implementation* const impl_; }; diff --git a/include/nana/gui/widgets/skeletons/text_editor.hpp b/include/nana/gui/widgets/skeletons/text_editor.hpp index eecf3edc0..7b2ffdf88 100644 --- a/include/nana/gui/widgets/skeletons/text_editor.hpp +++ b/include/nana/gui/widgets/skeletons/text_editor.hpp @@ -1,7 +1,7 @@ /* * A text editor implementation * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2023 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2024 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -164,6 +164,7 @@ namespace nana::widgets::skeletons bool select(bool); bool select_points(nana::upoint arg_a, nana::upoint arg_b); + ::std::wstring make_select_string() const; /// IM candidate mode void im_candidate_mode(bool); @@ -196,6 +197,7 @@ namespace nana::widgets::skeletons void render(bool focused); public: upoint im_input(upoint insert_pos, const std::wstring& str, bool candidate); + void im_cancel(); void put(std::wstring, bool perform_event); void put(wchar_t); void copy() const; @@ -255,7 +257,6 @@ namespace nana::widgets::skeletons ::nana::upoint _m_erase_select(bool perform_event); - ::std::wstring _m_make_select_string() const; static bool _m_resolve_text(const ::std::wstring&, std::vector> & lines); bool _m_cancel_select(int align); diff --git a/source/gui/detail/keyboard_im.hpp b/source/gui/detail/keyboard_im.hpp index 6e229591c..b55460a1e 100644 --- a/source/gui/detail/keyboard_im.hpp +++ b/source/gui/detail/keyboard_im.hpp @@ -16,6 +16,7 @@ namespace nana::detail enum class labels { name, + short_name, alphabet, space, done @@ -58,6 +59,7 @@ namespace nana::detail switch (l) { case labels::name: + case labels::short_name: return L"EN"; case labels::alphabet: return L"ABC"; @@ -119,6 +121,7 @@ namespace nana::detail switch (l) { case labels::name: + case labels::short_name: return L"RU"; case labels::alphabet: return L"АБВ"; @@ -614,14 +617,6 @@ namespace nana::detail table[L"zun"] = L"拵捘栫袸尊僔銌遵墫撙嶟噂樽罇繜瀳譐鐏鳟鷷鱒"; table[L"zuo"] = L"左作坐阼佐苲怍岝咗岞侳柮柞昨祚胙唑座秨袏莋笮做捽唶葄酢葃琢蓙稓筰鈼飵撮諎嘬穝繓糳"; - for (auto& m : table) - { - auto idx = pinyin_list_.size(); - pinyin_list_.push_back(m.first); - - for (auto ch : m.second) - chartable_[ch].push_back(idx); - } //常用词组 phrase_.insert(L"我们"); @@ -649,7 +644,6 @@ namespace nana::detail start = end; } - return pinyins; } @@ -788,14 +782,15 @@ namespace nana::detail { auto candidates = matched; - for (auto i = candidates.begin(); i != candidates.end();) + auto piece = s.substr(start, pos - start + 1); + for (auto i = candidates.cbegin(); i != candidates.cend(); ) { - if ((pos - start + 1 > i->size()) || ((*i)[pos - start] != s[pos])) + if ((piece.size() > i->size()) || (std::wstring::npos == i->find(piece))) { i = candidates.erase(i); continue; } - + ++i; } @@ -809,8 +804,6 @@ namespace nana::detail } private: std::map table_; - std::vector pinyin_list_; - std::map> chartable_; std::set phrase_; }; @@ -834,6 +827,8 @@ namespace nana::detail return L"空格"; case labels::done: return L"完成"; + default: + break; } return {}; } diff --git a/source/gui/detail/native_window_interface.cpp b/source/gui/detail/native_window_interface.cpp index 564010d70..c2319bcb0 100644 --- a/source/gui/detail/native_window_interface.cpp +++ b/source/gui/detail/native_window_interface.cpp @@ -1365,7 +1365,6 @@ namespace detail{ } else ::MoveWindow(reinterpret_cast(wd), x, y, r.right - r.left, r.bottom - r.top, true); - #elif defined(NANA_X11) Display * disp = restrict::spec.open_display(); diff --git a/source/gui/detail/virtual_keyboard.cpp b/source/gui/detail/virtual_keyboard.cpp index dc235b983..b65ed2c9c 100644 --- a/source/gui/detail/virtual_keyboard.cpp +++ b/source/gui/detail/virtual_keyboard.cpp @@ -1,4 +1,4 @@ -/** +/** * Virtual Keyboard Implementations * Nana C++ Library(http://www.nanapro.org) * Copyright(C) 2003-2024 Jinhao(cnjinhao@hotmail.com) @@ -64,7 +64,7 @@ namespace nana::detail static constexpr unsigned border_px = 5; static constexpr unsigned button_border_px = 2; - numeric(window wd, window host, const keyboards::images* images ): + numeric(window wd, window host, const keyboards::images* images, bool padding): panel(wd), window_(wd), host_(host), @@ -74,10 +74,7 @@ namespace nana::detail for (int i = 0; i < 13; ++i) keys_.emplace_back().value = values[i]; - api::window_size(wd, { 320, 320 }); - this->size({ 320, 320 }); - - _m_resized(); + _m_adjust_size(padding); nana::drawing{ *this }.draw([this](nana::paint::graphics& graph) { _m_render(graph); @@ -134,10 +131,38 @@ namespace nana::detail return true; } - void _m_resized() + void _m_adjust_size(bool padding) { - auto sz = this->size(); + nana::size sz{320, 320}; + + auto dsk_size = nana::screen::desktop_size(); + + sz.height = std::min(sz.height, dsk_size.height * 3 / 5); + if(sz.height > dsk_size.width) + sz.width = sz.height = dsk_size.width; + else + sz.width = sz.height; + if (padding) + { + api::window_size(window_, { dsk_size.width, sz.height }); + + nana::point pt{ + static_cast(dsk_size.width - sz.width) / 2 + ,0 }; + this->move(nana::rectangle{ pt, sz }); + } + else + { + api::window_size(window_, sz); + this->size(sz); + } + + _m_resized(sz); + } + + void _m_resized(nana::size sz) + { if (sz.width <= border_px * 2 || sz.height <= border_px * 2) return; @@ -237,8 +262,8 @@ namespace nana::detail { public: static const unsigned topbar_px = 48; - static const unsigned bottom_px = 32; - static const unsigned height_px = 280; + static const unsigned bottom_px = 6; //32; + static const unsigned height_px = 240; using labels = im_interface::labels; @@ -289,7 +314,7 @@ namespace nana::detail { cntpart_.lang = im_->lang(); - nana::size dim{ 480, 280 }; + nana::size dim{ 480, height_px }; api::window_size(window_, dim); this->size(dim); @@ -318,10 +343,13 @@ namespace nana::detail return; } _m_press_key(text_wd, arg.pos, true); - }); + }); this->events().mouse_move([this](const nana::arg_mouse& arg) { - if (1 == candidate_.state || 2 == candidate_.state) + + auto move_condition = (std::abs(candidate_.origin_arg_pos.x - arg.pos.x) >= 5 || std::abs(candidate_.origin_arg_pos.y - arg.pos.y) >= 5); + + if ((1 == candidate_.state && move_condition) || 2 == candidate_.state) { candidate_.state = 2; @@ -351,7 +379,7 @@ namespace nana::detail } } } - }); + }); this->events().mouse_up([this, text_wd](const nana::arg_mouse& arg) { if (1 == candidate_.state) @@ -366,12 +394,12 @@ namespace nana::detail } candidate_.state = 0; - }); + }); this->events().dbl_click([this, text_wd](const nana::arg_mouse& arg) { pressed_key_ = 0; _m_press_key(text_wd, arg.pos); - }); + }); api::refresh_window(*this); @@ -391,7 +419,8 @@ namespace nana::detail static std::unique_ptr _m_default_im() { - return _m_im_from_lang("en"); + nana::internal_scope_guard lock; + return _m_im_from_lang(virtual_keyboard::default_im_value()); } void _m_press_key(nana::window text_wd, const nana::point& arg_pos, bool is_pressed = false) @@ -476,7 +505,7 @@ namespace nana::detail if (i < langs.size()) im_ = _m_im_from_lang(langs[i]); else - im_ = _m_im_from_lang("en"); + im_ = _m_im_from_lang(langs[0]); mode_ = modes::letter_lower; @@ -515,6 +544,7 @@ namespace nana::detail insert_pos_ = nana::api::dev::im_input(text_wd, insert_pos_, content, im_->has_intermediate_input()); candidate_.words = im_->candidates(); + candidate_.wordsize.clear(); candidate_.word_areas.clear(); candidate_.left = 0; candidate_.top = 0; @@ -566,6 +596,7 @@ namespace nana::detail insert_pos_ = nana::api::dev::im_input(text_wd, insert_pos_, content, true); candidate_.words = im_->candidates(); + candidate_.wordsize.clear(); candidate_.word_areas.clear(); } } @@ -874,7 +905,7 @@ namespace nana::detail void _m_render(nana::paint::graphics& graph) { - // ѡǵģʽʱ򣬲ŻƼ + //Draw the keyboard if the candidate area is single mode if (candidate_.single_mode) { if (cntpart_.mode && (cntpart_.mode.value() == mode_) && (cntpart_.lang == im_->lang())) @@ -908,9 +939,9 @@ namespace nana::detail _m_render_candidates_ml(graph); } - /// Ⱦкѡ + /// Renders the single candidate area /** - * ıѡģʽģʽͶģʽȾʱѲֺѡֵ¼Ӧ + * There are 2 modes for rendering candidate area, single-line mode and multiline mode */ void _m_render_candidates(nana::paint::graphics& graph) { @@ -927,37 +958,65 @@ namespace nana::detail r.height = topbar_px; candidate_.offset_idx = 0; - - for (auto& cd : candidate_.words) + if(im_->lang() == "zh-CN") { - auto ts = graph.text_extent_size(cd); - - r.width = (static_cast(ts.width) + candidate_gap >= space ? ts.width + candidate_gap : space); + //Chinese characters have same wide. + auto ts = graph.text_extent_size(u8"中文汉字等宽"); + ts.width /= 6; - if (r.right() > 0 && (r.x + static_cast(ts.width) < right)) + for (auto& cd : candidate_.words) { - candidate_.word_areas.push_back(r); + r.width = (static_cast(ts.width) + candidate_gap >= space ? ts.width + candidate_gap : space); - nana::point pt{ - r.x, - static_cast(r.height - ts.height) / 2 - }; + if (r.right() > 0 && (r.x + static_cast(ts.width) < right)) + { + candidate_.word_areas.push_back(r); - graph.string(pt, cd, nana::colors::black); - } - else if (r.right() <= 0) - candidate_.offset_idx++; + nana::point pt{ + r.x, + static_cast(r.height - ts.height) / 2 + }; - r.x += r.width; + graph.string(pt, cd, nana::colors::black); + } + else if (r.right() <= 0) + candidate_.offset_idx++; + + r.x += r.width; + } + } + else + { + + for (auto& cd : candidate_.words) + { + auto ts = graph.text_extent_size(cd); - //ʱr.xұߵĵ + r.width = (static_cast(ts.width) + candidate_gap >= space ? ts.width + candidate_gap : space); - candidate_.content_px = (r.x - candidate_.left); + if (r.right() > 0 && (r.x + static_cast(ts.width) < right)) + { + candidate_.word_areas.push_back(r); + + nana::point pt{ + r.x, + static_cast(r.height - ts.height) / 2 + }; + + graph.string(pt, cd, nana::colors::black); + } + else if (r.right() <= 0) + candidate_.offset_idx++; + r.x += r.width; + } + } + //Now, r.x represents the right end point. + candidate_.content_px = (r.x - candidate_.left); - // Ⱦť + // Renders dropdown button if (candidate_.content_px > static_cast(graph.width())) { key_candidate_switch_.x = right; @@ -981,6 +1040,11 @@ namespace nana::detail candidate_.left = 0; candidate_.word_areas.clear(); + //Calcuates all candidate words' size. + if (candidate_.words.size() != candidate_.wordsize.size()) + for (auto& cd : candidate_.words) + candidate_.wordsize.push_back(graph.text_extent_size(cd)); + const int right = (graph.width() - key_candidate_switch_.width - 5); auto const space = (right / 13); @@ -991,9 +1055,10 @@ namespace nana::detail r.height = topbar_px; candidate_.offset_idx = 0; - for (auto& cd : candidate_.words) + for (std::size_t i = 0; i < candidate_.words.size(); ++i) { - auto ts = graph.text_extent_size(cd); + auto& cd = candidate_.words[i]; + auto ts = candidate_.wordsize[i]; r.width = (static_cast(ts.width) + candidate_gap >= space ? ts.width + candidate_gap : space); @@ -1022,6 +1087,15 @@ namespace nana::detail candidate_.content_px = (r.bottom() - candidate_.top); + if (candidate_.content_px > static_cast(graph.height())) + { + //Height of scrollbar + unsigned scroll_px = graph.height() * graph.height() / candidate_.content_px; + + int top = (-candidate_.top) * graph.height() / candidate_.content_px; + graph.rectangle(nana::rectangle{ right - 10, top, 6, scroll_px }, true, static_cast(0xa0a0a0)); + } + //Candidate switch button key_candidate_switch_.x = right; @@ -1034,7 +1108,6 @@ namespace nana::detail arrow.direction(nana::direction::north); arrow.draw(graph, static_cast(0xD3D6DF), nana::colors::black, arrow_r, nana::element_state::normal); - } private: window const window_; @@ -1073,6 +1146,7 @@ namespace nana::detail nana::point origin_arg_pos; ///< cursor coordinates when button is pressed std::vector words; + std::vector wordsize; std::vector word_areas; }candidate_; @@ -1105,12 +1179,11 @@ namespace nana::detail this->events().resized([this](const arg_resized&) { _m_resized(); }); - - this->show(); } ~virtual_keyboard_window() { + api::dev::im_cancel(host_); api::move_window(host_form_, origin_pt_); delete board_; } @@ -1125,9 +1198,9 @@ namespace nana::detail board_ = new keyboards::qwerty(*this, host_, langs, behave, mode, images_); } - void numeric() + void numeric(bool padding) { - board_ = new keyboards::numeric{*this, host_, images_}; + board_ = new keyboards::numeric{*this, host_, images_, padding}; } private: /// \todo: generalize dpi to v2 awareness @@ -1239,6 +1312,7 @@ namespace nana::detail struct keyboard_host { bool qwerty{ true }; + bool padding{ false }; std::unique_ptr qwerty_param; //Specific qwerty parameters nana::event_handle focus; @@ -1298,6 +1372,12 @@ namespace nana::detail } } + std::string& virtual_keyboard::default_im_value() noexcept + { + static std::string value = "en"; + return value; + } + void virtual_keyboard::attach(window wd) { nana::internal_scope_guard lock; @@ -1331,8 +1411,7 @@ namespace nana::detail if ((!impl_->win) || impl_->win->host() != arg.window_handle) { //popup keyboard - - impl_->win = &nana::form_loader{}(arg.window_handle, &(impl_->images)); + impl_->win = &nana::form_loader{}(arg.window_handle, &(impl_->images)); impl_->win->events().destroy([this](const arg_destroy&) { impl_->win = nullptr; @@ -1351,7 +1430,10 @@ namespace nana::detail impl_->win->qwerty(qp->langs, qp->behave, qp->mode); } else - impl_->win->numeric(); + impl_->win->numeric(host.padding); + + //Show the keyboard window after configuration to avoid flickering + impl_->win->show(); } } }); @@ -1379,13 +1461,14 @@ namespace nana::detail return true; } - bool virtual_keyboard::numeric(window wd) + bool virtual_keyboard::numeric(window wd, bool padding) { auto i = impl_->hosts.find(wd); if (i == impl_->hosts.cend()) return false; i->second.qwerty = false; + i->second.padding = padding; i->second.qwerty_param.reset(); return true; } diff --git a/source/gui/filebox.cpp b/source/gui/filebox.cpp index 2563641ad..2388940f9 100644 --- a/source/gui/filebox.cpp +++ b/source/gui/filebox.cpp @@ -38,8 +38,6 @@ # include "../detail/posix/theme.hpp" #endif -#include //debug - namespace fs = std::filesystem; namespace fs_ext = nana::filesystem_ext; diff --git a/source/gui/programming_interface.cpp b/source/gui/programming_interface.cpp index b918a6142..859789e73 100644 --- a/source/gui/programming_interface.cpp +++ b/source/gui/programming_interface.cpp @@ -1719,8 +1719,12 @@ namespace api void keyboard_default_language(const std::string& lang) { +#ifdef NANA_ENABLE_VIRTUAL_KEYBOARD internal_scope_guard lock; restrict::bedrock.vkeyboard().default_im_value() = lang; +#else + (void)lang; +#endif } /// Configures the qwerty keyboard for a text editor diff --git a/source/gui/widgets/menubar.cpp b/source/gui/widgets/menubar.cpp index e0862615c..7239e4181 100644 --- a/source/gui/widgets/menubar.cpp +++ b/source/gui/widgets/menubar.cpp @@ -188,7 +188,7 @@ namespace nana { auto menubar_size = widget_ptr->size(); int offset = platform_abstraction::dpi_scale(widget_ptr->handle(), 2); - if ((offset <= pos.x) && (offset <= pos.y) && (pos.y < menubar_size.height)) + if ((offset <= pos.x) && (offset <= pos.y) && (pos.y < static_cast(menubar_size.height))) { int item_x = offset; std::size_t index = 0; diff --git a/source/gui/widgets/skeletons/text_editor.cpp b/source/gui/widgets/skeletons/text_editor.cpp index b504b868b..0e3e72b28 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-2022 Jinhao(cnjinhao@hotmail.com) +* Copyright(C) 2003-2024 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -127,7 +127,7 @@ namespace nana::widgets::skeletons if (sel_a_ != sel_b_) { - selected_text_ = editor_._m_make_select_string(); + selected_text_ = editor_.make_select_string(); } } @@ -316,7 +316,7 @@ namespace nana::widgets::skeletons editor_.select_.b = dest_b_; editor_.points_.caret = (sel_a_ < sel_b_ ? sel_a_ : sel_b_); - const auto text = editor_._m_make_select_string(); + const auto text = editor_.make_select_string(); editor_._m_erase_select(false); editor_._m_put(text, false); @@ -2097,19 +2097,31 @@ namespace nana::widgets::skeletons else move_caret(insert_pos, true); - nana::arg_keyboard arg; - arg.evt_code = event_code::key_char; - arg.window_handle = window_; - arg.ignore = false; - arg.ctrl = false; - arg.shift = false; - arg.alt = false; - - for (auto ch : str) + if (str.size() == 1) { - arg.key = ch; + nana::arg_keyboard arg; + arg.evt_code = event_code::key_char; + arg.window_handle = window_; + arg.ignore = false; + arg.ctrl = false; + arg.shift = false; + arg.alt = false; + + arg.key = str.front(); respond_char(arg); } + else if(!str.empty()) + { + //Use put for text is more faster than respond_char for each character. + + //Don't trigger text_changed if candidate is true. + put(str, !candidate); + } + else + { + //Restore the position of caret if candidate input is empty + move_caret(insert_pos, true); + } if (candidate) { @@ -2125,6 +2137,27 @@ namespace nana::widgets::skeletons return insert_pos; } + void text_editor::im_cancel() + { + if (!this->selected()) + return; + + auto text = this->make_select_string(); + + for (std::size_t i = 0; i < text.size();) + { + if (' ' == text[i]) + text.erase(i, 1); + else + ++i; + } + + this->put(text, true); + + if (this->try_refresh()) + api::update_window(window_); + } + void text_editor::put(std::wstring text, bool perform_event) { if (text.empty()) @@ -2189,7 +2222,6 @@ namespace nana::widgets::skeletons } else impl_->try_refresh = sync_graph::refresh; - } void text_editor::copy() const @@ -2198,7 +2230,7 @@ namespace nana::widgets::skeletons if (mask_char_) return; - auto text = _m_make_select_string(); + auto text = make_select_string(); if (!text.empty()) nana::system::dataexch().set(text, api::root(this->window_)); } @@ -2381,8 +2413,6 @@ namespace nana::widgets::skeletons if (perform_event) textbase().text_changed(); - textbase().text_changed(); - if (has_to_redraw) { this->_m_adjust_view(); @@ -3206,7 +3236,7 @@ namespace nana::widgets::skeletons return points_.caret; } - std::wstring text_editor::_m_make_select_string() const + std::wstring text_editor::make_select_string() const { std::wstring text; @@ -3393,7 +3423,7 @@ namespace nana::widgets::skeletons return false; nana::upoint caret = points_.caret; - const auto text = _m_make_select_string(); + const auto text = make_select_string(); if (!text.empty()) { auto undo_ptr = std::unique_ptr(new undo_move_text(*this));