From a4f15f7bb08100221aa5b314e679facc59871f6d Mon Sep 17 00:00:00 2001 From: Jinhao Date: Tue, 23 May 2017 04:22:08 +0800 Subject: [PATCH] refactor text_editor fix issues that caret works incorrectly in line-wrapped mode. --- .../gui/widgets/skeletons/text_editor.hpp | 13 +- .../nana/gui/widgets/skeletons/textbase.hpp | 109 ++-- source/gui/widgets/skeletons/content_view.cpp | 33 +- source/gui/widgets/skeletons/content_view.hpp | 1 + source/gui/widgets/skeletons/text_editor.cpp | 581 +++++++----------- 5 files changed, 302 insertions(+), 435 deletions(-) diff --git a/include/nana/gui/widgets/skeletons/text_editor.hpp b/include/nana/gui/widgets/skeletons/text_editor.hpp index 896027c0..b95b4fa7 100644 --- a/include/nana/gui/widgets/skeletons/text_editor.hpp +++ b/include/nana/gui/widgets/skeletons/text_editor.hpp @@ -85,6 +85,8 @@ namespace nana{ namespace widgets text_editor(window, graph_reference, const text_editor_scheme*); ~text_editor(); + size caret_size() const; + void set_highlight(const ::std::string& name, const ::nana::color&, const ::nana::color&); void erase_highlight(const ::std::string& name); void set_keyword(const ::std::wstring& kw, const std::string& name, bool case_sensitive, bool whole_word_matched); @@ -217,8 +219,10 @@ namespace nana{ namespace widgets std::vector _m_render_text(const ::nana::color& text_color); void _m_pre_calc_lines(std::size_t line_off, std::size_t lines); - ::nana::point _m_caret_to_screen(::nana::upoint pos) const; - ::nana::upoint _m_screen_to_caret(::nana::point pos) const; + //Caret to screen coordinate or context coordiate(in pixels) + ::nana::point _m_caret_to_coordinate(::nana::upoint pos, bool to_screen_coordinate = true) const; + //Screen coordinate or context coordinate(in pixels) to caret, + ::nana::upoint _m_coordinate_to_caret(::nana::point pos, bool from_screen_coordinate = true) const; bool _m_pos_from_secondary(std::size_t textline, const nana::upoint& secondary, unsigned & pos); bool _m_pos_secondary(const nana::upoint& charpos, nana::upoint& secondary_pos) const; @@ -243,8 +247,9 @@ namespace nana{ namespace widgets unsigned _m_tabs_pixels(size_type tabs) const; nana::size _m_text_extent_size(const char_type*, size_type n) const; - /// Moves the view of window. - bool _m_move_offset_x_while_over_border(int many); + /// Adjust position of view to make caret stay in screen + bool _m_adjust_view(); + bool _m_move_select(bool record_undo); int _m_text_top_base() const; diff --git a/include/nana/gui/widgets/skeletons/textbase.hpp b/include/nana/gui/widgets/skeletons/textbase.hpp index 55828351..751d9888 100644 --- a/include/nana/gui/widgets/skeletons/textbase.hpp +++ b/include/nana/gui/widgets/skeletons/textbase.hpp @@ -1,7 +1,7 @@ /* * A textbase class implementation * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2016 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2017 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -44,7 +44,7 @@ namespace skeletons { attr_max_.reset(); //Insert an empty string for the first line of empty text. - text_cont_.emplace_back(); + text_cont_.emplace_back(new string_type); } void set_event_agent(textbase_event_agent_interface * evt) @@ -55,7 +55,7 @@ namespace skeletons bool empty() const { return (text_cont_.empty() || - ((text_cont_.size() == 1) && (text_cont_[0].empty()))); + ((text_cont_.size() == 1) && (text_cont_.front()->empty()))); } bool load(const char* file_utf8) @@ -135,10 +135,10 @@ namespace skeletons while(ifs.good()) { std::getline(ifs, str_mbs); - text_cont_.emplace_back(static_cast(nana::charset{ str_mbs })); - if(text_cont_.back().size() > attr_max_.size) + text_cont_.emplace_back(new string_type(static_cast(nana::charset{ str_mbs }))); + if(text_cont_.back()->size() > attr_max_.size) { - attr_max_.size = text_cont_.back().size(); + attr_max_.size = text_cont_.back()->size(); attr_max_.line = text_cont_.size() - 1; } } @@ -218,9 +218,9 @@ namespace skeletons byte_order_translate_4bytes(str); } - text_cont_.emplace_back(static_cast(nana::charset{ str, encoding })); + text_cont_.emplace_back(new string_type(static_cast(nana::charset{ str, encoding }))); - attr_max_.size = text_cont_.back().size(); + attr_max_.size = text_cont_.back()->size(); attr_max_.line = 0; } @@ -236,10 +236,10 @@ namespace skeletons byte_order_translate_4bytes(str); } - text_cont_.emplace_back(static_cast(nana::charset{ str, encoding })); - if(text_cont_.back().size() > attr_max_.size) + text_cont_.emplace_back(new string_type(static_cast(nana::charset{ str, encoding }))); + if(text_cont_.back()->size() > attr_max_.size) { - attr_max_.size = text_cont_.back().size(); + attr_max_.size = text_cont_.back()->size(); attr_max_.line = text_cont_.size() - 1; } } @@ -253,6 +253,9 @@ namespace skeletons std::ofstream ofs(to_osmbstr(fs), std::ios::binary); if(ofs && text_cont_.size()) { + auto i = text_cont_.cbegin(); + auto const count = text_cont_.size() - 1; + std::string last_mbs; if (is_unicode) @@ -272,32 +275,27 @@ namespace skeletons if (bytes) ofs.write(le_boms[static_cast(encoding)], bytes); - if (text_cont_.size() > 1) + for (std::size_t pos = 0; pos < count; ++pos) { - std::string mbs; - for (auto i = text_cont_.cbegin(), end = text_cont_.cend() - 1; i != end; ++i) - { - std::string(nana::charset(*i).to_bytes(encoding)).swap(mbs); - mbs += "\r\n"; - ofs.write(mbs.c_str(), static_cast(mbs.size())); - } + auto mbs = nana::charset(**(i++)).to_bytes(encoding); + ofs.write(mbs.c_str(), static_cast(mbs.size())); + ofs.write("\r\n", 2); } - last_mbs = nana::charset(text_cont_.back()).to_bytes(encoding); + last_mbs = nana::charset(*text_cont_.back()).to_bytes(encoding); } else { - if (text_cont_.size() > 1) + for (std::size_t pos = 0; pos < count; ++pos) { - for (auto i = text_cont_.cbegin(), end = text_cont_.cend() - 1; i != end; ++i) - { - std::string mbs = nana::charset(*i); - ofs.write(mbs.c_str(), mbs.size()); - ofs.write("\r\n", 2); - } + std::string mbs = nana::charset(**(i++)); + ofs.write(mbs.c_str(), mbs.size()); + ofs.write("\r\n", 2); } - last_mbs = nana::charset(text_cont_.back()); + + last_mbs = nana::charset(*text_cont_.back()); } + ofs.write(last_mbs.c_str(), static_cast(last_mbs.size())); _m_saved(std::move(fs)); } @@ -310,8 +308,8 @@ namespace skeletons const string_type& getline(size_type pos) const { - if(pos < text_cont_.size()) - return text_cont_[pos]; + if (pos < text_cont_.size()) + return *text_cont_[pos]; return nullstr_; } @@ -323,13 +321,13 @@ namespace skeletons public: void replace(size_type pos, string_type && text) { - if(text_cont_.size() <= pos) + if (text_cont_.size() <= pos) { - text_cont_.emplace_back(std::move(text)); + text_cont_.emplace_back(new string_type(std::move(text))); pos = text_cont_.size() - 1; } else - text_cont_[pos].swap(text); + _m_at(pos).swap(text); _m_make_max(pos); _m_edited(); @@ -339,7 +337,7 @@ namespace skeletons { if(pos.y < text_cont_.size()) { - string_type& lnstr = text_cont_[pos.y]; + string_type& lnstr = _m_at(pos.y); if(pos.x < lnstr.size()) lnstr.insert(pos.x, str); @@ -348,7 +346,7 @@ namespace skeletons } else { - text_cont_.emplace_back(std::move(str)); + text_cont_.emplace_back(new string_type(std::move(str))); pos.y = static_cast(text_cont_.size() - 1); } @@ -358,10 +356,10 @@ namespace skeletons void insertln(size_type pos, string_type&& str) { - if(pos < text_cont_.size()) - text_cont_.emplace(text_cont_.begin() + pos, std::move(str)); + if (pos < text_cont_.size()) + text_cont_.emplace(_m_iat(pos), new string_type(std::move(str))); else - text_cont_.emplace_back(std::move(str)); + text_cont_.emplace_back(new string_type(std::move(str))); _m_make_max(pos); _m_edited(); @@ -371,7 +369,7 @@ namespace skeletons { if (line < text_cont_.size()) { - string_type& lnstr = text_cont_[line]; + string_type& lnstr = _m_at(line); if ((pos == 0) && (count >= lnstr.size())) lnstr.clear(); else @@ -393,7 +391,7 @@ namespace skeletons if (pos + n > text_cont_.size()) n = text_cont_.size() - pos; - text_cont_.erase(text_cont_.begin() + pos, text_cont_.begin() + (pos + n)); + text_cont_.erase(_m_iat(pos), _m_iat(pos + n)); if (pos <= attr_max_.line && attr_max_.line < pos + n) _m_scan_for_max(); @@ -408,7 +406,7 @@ namespace skeletons { text_cont_.clear(); attr_max_.reset(); - text_cont_.emplace_back(); //text_cont_ must not be empty + text_cont_.emplace_back(new string_type); //text_cont_ must not be empty _m_saved(std::string()); } @@ -417,9 +415,14 @@ namespace skeletons { if(pos + 1 < text_cont_.size()) { - text_cont_[pos] += text_cont_[pos + 1]; - text_cont_.erase(text_cont_.begin() + (pos + 1)); + auto i = _m_iat(pos + 1); + _m_at(pos) += **i; + text_cont_.erase(i); _m_make_max(pos); + + //If the maxline is behind the pos line, + //decrease the maxline. Because a line between maxline and pos line + //has been deleted. if(pos < attr_max_.line) --attr_max_.line; @@ -458,9 +461,19 @@ namespace skeletons return edited() || filename_.empty(); } private: + string_type& _m_at(size_type pos) + { + return **_m_iat(pos); + } + + typename std::deque>::iterator _m_iat(size_type pos) + { + return text_cont_.begin() + pos; + } + void _m_make_max(std::size_t pos) { - const string_type& str = text_cont_[pos]; + const string_type& str = _m_at(pos); if(str.size() > attr_max_.size) { attr_max_.size = str.size(); @@ -472,11 +485,11 @@ namespace skeletons { attr_max_.size = 0; std::size_t n = 0; - for(auto & s : text_cont_) + for(auto & p : text_cont_) { - if(s.size() > attr_max_.size) + if(p->size() > attr_max_.size) { - attr_max_.size = s.size(); + attr_max_.size = p->size(); attr_max_.line = n; } ++n; @@ -514,7 +527,7 @@ namespace skeletons evt_agent_->text_changed(); } private: - std::deque text_cont_; + std::deque> text_cont_; textbase_event_agent_interface* evt_agent_{ nullptr }; mutable bool changed_{ false }; diff --git a/source/gui/widgets/skeletons/content_view.cpp b/source/gui/widgets/skeletons/content_view.cpp index 438ce4ea..4e92845b 100644 --- a/source/gui/widgets/skeletons/content_view.cpp +++ b/source/gui/widgets/skeletons/content_view.cpp @@ -319,31 +319,31 @@ namespace nana { void content_view::content_size(const size& sz, bool try_update) { + auto const view_sz = this->view_area(sz); + if (sz.height < impl_->content_size.height) { - if (impl_->origin.y + impl_->disp_area.height > sz.height) + if (impl_->origin.y + view_sz.height > sz.height) { - if (impl_->disp_area.height > sz.height) + if (view_sz.height > sz.height) impl_->origin.y = 0; else - impl_->origin.y = sz.height - impl_->disp_area.height; + impl_->origin.y = sz.height - view_sz.height; } } if (sz.width < impl_->content_size.width) { - if (impl_->origin.x + impl_->disp_area.width > sz.width) + if (impl_->origin.x + view_sz.width > sz.width) { - if (impl_->disp_area.width > sz.width) + if (view_sz.width > sz.width) impl_->origin.x = 0; else - impl_->origin.x = sz.width - impl_->disp_area.width; + impl_->origin.x = sz.width - view_sz.width; } } - impl_->content_size = sz; - impl_->size_changed(try_update); } @@ -388,18 +388,23 @@ namespace nana { rectangle content_view::view_area() const { - unsigned extra_horz = (impl_->disp_area.width < impl_->content_size.width ? space() : 0); - unsigned extra_vert = (impl_->disp_area.height < impl_->content_size.height + extra_horz ? space() : 0); + return view_area(impl_->content_size); + } + + rectangle content_view::view_area(const size& alt_content_size) const + { + unsigned extra_horz = (impl_->disp_area.width < alt_content_size.width ? space() : 0); + unsigned extra_vert = (impl_->disp_area.height < alt_content_size.height + extra_horz ? space() : 0); if ((0 == extra_horz) && extra_vert) - extra_horz = (impl_->disp_area.width < impl_->content_size.width + extra_vert ? space() : 0); + extra_horz = (impl_->disp_area.width < alt_content_size.width + extra_vert ? space() : 0); return rectangle{ impl_->disp_area.position(), size{ - impl_->disp_area.width > extra_vert ? impl_->disp_area.width - extra_vert : 0, - impl_->disp_area.height > extra_horz ? impl_->disp_area.height - extra_horz : 0 - } + impl_->disp_area.width > extra_vert ? impl_->disp_area.width - extra_vert : 0, + impl_->disp_area.height > extra_horz ? impl_->disp_area.height - extra_horz : 0 + } }; } diff --git a/source/gui/widgets/skeletons/content_view.hpp b/source/gui/widgets/skeletons/content_view.hpp index 6d4b2d40..ce2c60b7 100644 --- a/source/gui/widgets/skeletons/content_view.hpp +++ b/source/gui/widgets/skeletons/content_view.hpp @@ -65,6 +65,7 @@ namespace skeletons void draw_corner(graph_reference); rectangle view_area() const; + rectangle view_area(const size& alt_content_size) const; unsigned extra_space(bool horz) const; diff --git a/source/gui/widgets/skeletons/text_editor.cpp b/source/gui/widgets/skeletons/text_editor.cpp index d008d70a..e81accb4 100644 --- a/source/gui/widgets/skeletons/text_editor.cpp +++ b/source/gui/widgets/skeletons/text_editor.cpp @@ -18,6 +18,7 @@ #include #include "content_view.hpp" +#include #include #include #include @@ -45,7 +46,7 @@ namespace nana{ namespace widgets { public: using command = EnumCommand; - using container = std::deque < std::unique_ptr> >; + using container = std::deque>>; void clear() { @@ -555,7 +556,6 @@ namespace nana{ namespace widgets virtual std::size_t take_lines() const = 0; /// Returns the number of lines that the line of text specified by pos takes. virtual std::size_t take_lines(std::size_t pos) const = 0; - virtual bool adjust_caret_into_screen() = 0; }; inline bool is_right_text(const unicode_bidi::entity& e) @@ -685,62 +685,6 @@ namespace nana{ namespace widgets { return 1; } - - //adjust_caret_into_screen - //@brief: Adjust the text offset in order to moving caret into visible area if it is out of the visible area - //@note: the function assumes the points_.caret is correct - bool adjust_caret_into_screen() override - { - const auto scrlines = editor_.screen_lines(); - if (0 == scrlines) - return false; - - auto const pre_origin = editor_.impl_->cview->origin(); - auto origin = pre_origin; - - auto& points = editor_.points_; - auto& textbase = editor_.textbase(); - - auto& lnstr = textbase.getline(points.caret.y); - const auto x = (std::min)(points.caret.x, static_cast(lnstr.size())); - auto const text_w = editor_._m_pixels_by_char(lnstr, x); - - auto area_w = editor_.impl_->cview->view_area().width; - - if (static_cast(text_w) < origin.x) - { - auto delta_pixels = editor_._m_text_extent_size(L" ", 4).width; - origin.x = (text_w > delta_pixels ? text_w - delta_pixels : 0); - } - else if (area_w && (text_w >= origin.x + area_w)) - origin.x = text_w - area_w + 2; - - int row = origin.y / static_cast(editor_.line_height()); - if (points.caret.y >= row + scrlines) //implicit condition scrlines > 0 - { - row = static_cast(points.caret.y - scrlines) + 1; - } - else if (static_cast(points.caret.y) < row) - { - if (scrlines >= static_cast(row)) - row = 0; - else - row = static_cast(row - scrlines); - } - else if (row && (textbase.lines() <= scrlines)) - row = 0; - - if(row != origin.y / static_cast(editor_.line_height())) - origin.y = row * editor_.line_height(); - - if (pre_origin != origin) - { - editor_.impl_->cview->move_origin(origin - pre_origin); - editor_.impl_->cview->sync(true); - return true; - } - return false; - } private: text_editor& editor_; std::vector sections_; @@ -773,9 +717,9 @@ namespace nana{ namespace widgets if ((0 == editor_.textbase().lines()) || (0 == line_px)) return coord; - auto screen_rows = (top - editor_.text_area_.area.y + editor_.impl_->cview->origin().y) / line_px; + auto text_row = (std::max)(0, (top - editor_.text_area_.area.y + editor_.impl_->cview->origin().y) / line_px); - coord = _m_textline(static_cast(screen_rows)); + coord = _m_textline(static_cast(text_row)); if (linemtr_.size() <= coord.first) { coord.first = linemtr_.size() - 1; @@ -795,22 +739,26 @@ namespace nana{ namespace widgets std::swap(first, second); if (second < linemtr_.size()) - linemtr_.erase(linemtr_.begin() + first + 1, linemtr_.begin() + second); + linemtr_.erase(linemtr_.begin() + first + 1, linemtr_.begin() + second + 1); - pre_calc_line(first, editor_.width_pixels()); + auto const width_px = editor_.width_pixels(); + pre_calc_line(first, width_px); + + /* //textbase is implement by using deque, and the linemtr holds the text pointers //If the textbase is changed, it will check the text pointers. std::size_t line = 0; - for (auto & mtr: linemtr_) + for (auto & mtr: linemtr_) //deprecated { auto& linestr = editor_.textbase().getline(line); auto p = mtr.line_sections.front().begin; if (p < linestr.c_str() || (linestr.c_str() + linestr.size() < p)) - pre_calc_line(line, editor_.width_pixels()); + pre_calc_line(line, width_px); ++line; } + */ } void add_lines(std::size_t pos, std::size_t lines) override @@ -942,42 +890,6 @@ namespace nana{ namespace widgets { return (pos < linemtr_.size() ? linemtr_[pos].take_lines : 0); } - - bool adjust_caret_into_screen() override - { - const auto scrlines = editor_.screen_lines(); - if (0 == scrlines) - return false; - - const auto & points = editor_.points_; - auto off_coord = _m_textline(static_cast(editor_._m_text_topline())); - - nana::upoint caret_secondary; - editor_._m_pos_secondary(points.caret, caret_secondary); - - //Use the caret line for the offset line when caret is in front of current offset line. - if (off_coord.first > points.caret.y || ((off_coord.first == points.caret.y) && (off_coord.second > caret_secondary.y))) - { - //Use the line which was specified by points.caret for the first line. - _m_set_offset_by_secondary(row_coordinate(points.caret.y, caret_secondary.y)); - return true; - } - - //Find the last screen line. If the text does not reach the bottom of screen, - //do not adjust the offset line. - row_coordinate bottom; - if (false == _m_advance_secondary(off_coord, static_cast(scrlines - 1), bottom)) - return false; - - //Do not adjust the offset line if the caret line does not reach the bottom line. - if (points.caret.y < bottom.first || ((points.caret.y == bottom.first) && (caret_secondary.y <= bottom.second))) - return false; - - _m_advance_secondary(row_coordinate(points.caret.y, caret_secondary.y), -static_cast(scrlines - 1), bottom); - - _m_set_offset_by_secondary(bottom); - return true; - } private: void _m_text_section(const std::wstring& str, std::vector& tsec) { @@ -1014,90 +926,6 @@ namespace nana{ namespace widgets tsec.emplace_back(word, end, unsigned{}); } - void _m_set_offset_by_secondary(row_coordinate row) - { - for (auto i = linemtr_.begin(), end = linemtr_.begin() + row.first; i != end; ++i) - row.second += i->take_lines; - - auto origin = editor_.impl_->cview->origin(); - origin.y = static_cast(row.second * editor_.line_height()); - editor_.impl_->cview->move_origin(origin - editor_.impl_->cview->origin()); - } - - bool _m_advance_secondary(row_coordinate row, int distance, row_coordinate& new_row) - { - if ((row.first >= linemtr_.size()) || (row.second >= linemtr_[row.first].take_lines)) - return false; - - if (0 == distance) - { - new_row = row; - return true; - } - - if (distance < 0) - { - std::size_t n = static_cast(-distance); - - if (row.second > n) - { - new_row.first = row.first; - new_row.second = row.second - n; - return true; - } - - if (0 == row.first) - return false; - - --row.first; - n -= (row.second + 1); - - while (true) - { - auto lines = linemtr_[row.first].take_lines; - if (lines >= n) - { - new_row.first = row.first; - new_row.second = lines - n; - return true; - } - if (0 == row.first) - return false; - - --row.first; - n -= lines; - } - } - else - { - std::size_t n = static_cast(distance); - - auto delta_lines = linemtr_[row.first].take_lines - (row.second + 1); - - if (delta_lines >= n) - { - new_row.first = row.first; - new_row.second = row.second + n; - return true; - } - - n -= delta_lines; - - while (++row.first < linemtr_.size()) - { - auto & mtr = linemtr_[row.first]; - if (mtr.take_lines >= n) - { - new_row.first = row.first; - new_row.second = n - 1; - return true; - } - n -= mtr.take_lines; - } - } - return false; - } - row_coordinate _m_textline(std::size_t scrline) const { row_coordinate coord; @@ -1236,6 +1064,12 @@ namespace nana{ namespace widgets this->reset_caret(); }; + impl_->cview->events().hover_outside = [this](const point& pos) { + mouse_caret(pos, false); + if (selection::mode::mouse_selected == select_.mode_selection || selection::mode::method_selected == select_.mode_selection) + set_end_caret(false); + }; + API::create_caret(wd, { 1, line_height() }); API::bgcolor(wd, colors::white); API::fgcolor(wd, colors::black); @@ -1249,6 +1083,11 @@ namespace nana{ namespace widgets delete impl_; } + size text_editor::caret_size() const + { + return { 1, line_height() }; + } + void text_editor::set_highlight(const std::string& name, const ::nana::color& fgcolor, const ::nana::color& bgcolor) { if (fgcolor.invisible() && bgcolor.invisible()) @@ -1680,7 +1519,7 @@ namespace nana{ namespace widgets API::set_capture(window_, true); text_area_.captured = true; - if (this->hit_select_area(_m_screen_to_caret(arg.pos), true)) + if (this->hit_select_area(_m_coordinate_to_caret(arg.pos), true)) { //The selected of text can be moved only if it is editable if (attributes_.editable) @@ -1732,7 +1571,7 @@ namespace nana{ namespace widgets //no move occurs select(false); - move_caret(_m_screen_to_caret(arg.pos)); + move_caret(_m_coordinate_to_caret(arg.pos)); } select_.mode_selection = selection::mode::no_selected; @@ -1798,7 +1637,8 @@ namespace nana{ namespace widgets if (graph_) { - impl_->capacities.behavior->adjust_caret_into_screen(); + this->_m_adjust_view(); + reset_caret(); impl_->try_refresh = sync_graph::refresh; points_.xpos = 0; @@ -1832,7 +1672,7 @@ namespace nana{ namespace widgets const unsigned line_pixels = line_height(); //The coordinate of caret - auto coord = _m_caret_to_screen(crtpos); + auto coord = _m_caret_to_coordinate(crtpos); const int line_bottom = coord.y + static_cast(line_pixels); @@ -1863,7 +1703,7 @@ namespace nana{ namespace widgets //Adjust the caret into screen when the caret position is modified by this function if (reset_caret && (!hit_text_area(coord))) { - impl_->capacities.behavior->adjust_caret_into_screen(); + this->_m_adjust_view(); impl_->try_refresh = sync_graph::refresh; caret->visible(true); return true; @@ -1949,7 +1789,7 @@ namespace nana{ namespace widgets select_.b = points_.caret; points_.xpos = points_.caret.x; - if(new_sel_end || (stay_in_view && impl_->capacities.behavior->adjust_caret_into_screen())) + if (new_sel_end || (stay_in_view && this->_m_adjust_view())) impl_->try_refresh = sync_graph::refresh; } @@ -1985,7 +1825,7 @@ namespace nana{ namespace widgets { points_.caret = select_.b; - if (impl_->capacities.behavior->adjust_caret_into_screen()) + if (this->_m_adjust_view()) impl_->try_refresh = sync_graph::refresh; reset_caret(); @@ -1994,7 +1834,7 @@ namespace nana{ namespace widgets if (_m_move_select(true)) { - impl_->capacities.behavior->adjust_caret_into_screen(); + this->_m_adjust_view(); impl_->try_refresh = sync_graph::refresh; return true; } @@ -2117,7 +1957,8 @@ namespace nana{ namespace widgets if(graph_) { - impl_->capacities.behavior->adjust_caret_into_screen(); + this->_m_adjust_view(); + reset_caret(); impl_->try_refresh = sync_graph::refresh; _m_reset_content_size(true); @@ -2269,7 +2110,8 @@ namespace nana{ namespace widgets } impl_->cview->move_origin(origin - impl_->cview->origin()); - if (impl_->capacities.behavior->adjust_caret_into_screen() || need_refresh) + + if (this->_m_adjust_view() || need_refresh) impl_->cview->sync(true); _m_reset_content_size(); @@ -2320,7 +2162,7 @@ namespace nana{ namespace widgets textbase.erase(points_.caret.y, points_.caret.x, erase_number); _m_pre_calc_lines(points_.caret.y, 1); - if(_m_move_offset_x_while_over_border(-2) == false) + if (!this->_m_adjust_view()) { _m_update_line(points_.caret.y, secondary); has_to_redraw = false; @@ -2349,11 +2191,11 @@ namespace nana{ namespace widgets if (record_undo) impl_->undo.push(std::move(undo_ptr)); - _m_reset_content_size(has_to_redraw); + _m_reset_content_size(false); if(has_to_redraw) { - impl_->capacities.behavior->adjust_caret_into_screen(); + this->_m_adjust_view(); impl_->try_refresh = sync_graph::refresh; } } @@ -2367,7 +2209,7 @@ namespace nana{ namespace widgets _m_reset_content_size(true); - impl_->capacities.behavior->adjust_caret_into_screen(); + this->_m_adjust_view(); impl_->try_refresh = sync_graph::refresh; } @@ -2391,10 +2233,8 @@ namespace nana{ namespace widgets if(points_.caret.x) { --points_.caret.x; - pending = false; - bool adjust_y = (attributes_.line_wrapped && impl_->capacities.behavior->adjust_caret_into_screen()); - if (_m_move_offset_x_while_over_border(-2) || adjust_y) + if (this->_m_adjust_view()) impl_->try_refresh = sync_graph::refresh; } else if (points_.caret.y) //Move to previous line @@ -2403,7 +2243,7 @@ namespace nana{ namespace widgets pending = false; } - if (pending && impl_->capacities.behavior->adjust_caret_into_screen()) + if (pending && this->_m_adjust_view()) impl_->try_refresh = sync_graph::refresh; points_.xpos = points_.caret.x; @@ -2412,25 +2252,23 @@ namespace nana{ namespace widgets void text_editor::move_right() { bool do_render = false; - if(_m_cancel_select(2) == false) + if (_m_cancel_select(2) == false) { auto lnstr = textbase().getline(points_.caret.y); - if(lnstr.size() > points_.caret.x) + if (lnstr.size() > points_.caret.x) { ++points_.caret.x; - - bool adjust_y = (attributes_.line_wrapped && impl_->capacities.behavior->adjust_caret_into_screen()); - do_render = (_m_move_offset_x_while_over_border(2) || adjust_y); + do_render = this->_m_adjust_view(); } - else if(points_.caret.y + 1 < textbase().lines()) + else if (points_.caret.y + 1 < textbase().lines()) { //Move to next line points_.caret.x = 0; - ++ points_.caret.y; - do_render = impl_->capacities.behavior->adjust_caret_into_screen(); + ++points_.caret.y; + do_render = this->_m_adjust_view(); } } else - do_render = impl_->capacities.behavior->adjust_caret_into_screen(); + do_render = this->_m_adjust_view(); if (do_render) impl_->try_refresh = sync_graph::refresh; @@ -2444,155 +2282,123 @@ namespace nana{ namespace widgets select_.a = select_.b = points_.caret; auto origin = impl_->cview->origin(); - origin.y = _m_text_topline(); + auto pos = points_.caret; + auto coord = _m_caret_to_coordinate(points_.caret, false); - bool changed = false; - nana::upoint caret = points_.caret; wchar_t key = arg.key; - size_t nlines = textbase().lines(); - if (arg.ctrl) { - switch (key) { - case keyboard::os_arrow_left: - case keyboard::os_arrow_right: - // TODO: move the caret word by word - break; - case keyboard::os_home: - if (caret.y != 0) { - caret.y = 0; - origin.y = 0; - changed = true; - } - break; - case keyboard::os_end: - if (caret.y != nlines - 1) { - caret.y = static_cast(nlines - 1); - changed = true; - } - break; - } - } - size_t lnsz = textbase().getline(caret.y).size(); + + auto const line_px = this->line_height(); + + //The number of text lines + auto const line_count = textbase().lines(); + + //The number of charecters in the line of caret + auto const text_length = textbase().getline(points_.caret.y).size(); + + switch (key) { case keyboard::os_arrow_left: if (select_.move_to_end && (select_.a != select_.b) && (!arg.shift)) { - caret = select_.a; - changed = true; + pos = select_.a; } - else + else if (pos.x != 0) { - if (caret.x != 0) { - --caret.x; - changed = true; - } - else { - if (caret.y != 0) { - --caret.y; - caret.x = static_cast(textbase().getline(caret.y).size()); - changed = true; - } - } + --pos.x; + } + else if (pos.y != 0) { + --pos.y; + pos.x = static_cast(textbase().getline(pos.y).size()); } break; case keyboard::os_arrow_right: if (select_.move_to_end && (select_.a != select_.b) && (!arg.shift)) { - caret = select_.b; - changed = true; + pos = select_.b; } - else + else if (pos.x < text_length) { - if (caret.x < lnsz) { - ++caret.x; - changed = true; - } - else { - if (caret.y != nlines - 1) { - ++caret.y; - caret.x = 0; - changed = true; - } - } + ++pos.x; + } + else if (pos.y != line_count - 1) + { + ++pos.y; + pos.x = 0; } break; case keyboard::os_arrow_up: + coord.y -= static_cast(line_px); + break; case keyboard::os_arrow_down: - { - auto screen_pt = _m_caret_to_screen(caret); - int offset = line_height(); - if (key == keyboard::os_arrow_up) { - offset = -offset; - } - screen_pt.y += offset; - - auto const new_caret = _m_screen_to_caret(screen_pt); - if (new_caret != caret) { - caret = new_caret; - if (screen_pt.y < 0) { - scroll(true, true); - } - changed = true; - } - } + coord.y += static_cast(line_px); break; case keyboard::os_home: - if (caret.x != 0) { - caret.x = 0; - changed = true; - } + //move the caret to the begining of the line + pos.x = 0; + + //move the caret to the begining of the text if Ctrl is pressed + if (arg.ctrl) + pos.y = 0; break; case keyboard::os_end: - if (caret.x < lnsz) { - caret.x = static_cast(lnsz); - changed = true; - } + //move the caret to the end of the line + pos.x = static_cast(text_length); + + //move the caret to the end of the text if Ctrl is pressed + if(arg.ctrl) + pos.y = (line_count - 1) * line_px; break; case keyboard::os_pageup: - if (caret.y >= screen_lines() && origin.y >= static_cast(screen_lines())) { - origin.y -= screen_lines(); - caret.y -= screen_lines(); - changed = true; + if(origin.y > 0) + { + auto off = coord - origin; + origin.y -= (std::min)(origin.y, static_cast(impl_->cview->view_area().height)); + coord = off + origin; } break; case keyboard::os_pagedown: - if (caret.y + screen_lines() <= impl_->capacities.behavior->take_lines()) { - origin.y += static_cast(screen_lines()); - caret.y += screen_lines(); - changed = true; + if (impl_->cview->content_size().height > impl_->cview->view_area().height) + { + auto off = coord - origin; + origin.y = (std::min)(origin.y + static_cast(impl_->cview->view_area().height), static_cast(impl_->cview->content_size().height - impl_->cview->view_area().height)); + coord = off + origin; } break; } - if (select_.a != caret || select_.b != caret) { - changed = true; + + if (pos == points_.caret) + { + impl_->cview->move_origin(origin - impl_->cview->origin()); + pos = _m_coordinate_to_caret(coord, false); } - if (changed) { + if (pos != points_.caret) { if (arg.shift) { switch (key) { case keyboard::os_arrow_left: case keyboard::os_arrow_up: case keyboard::os_home: case keyboard::os_pageup: - select_.b = caret; + select_.b = pos; break; case keyboard::os_arrow_right: case keyboard::os_arrow_down: case keyboard::os_end: case keyboard::os_pagedown: - select_.b = caret; + select_.b = pos; break; } - }else { - select_.b = caret; - select_.a = caret; } - points_.caret = caret; - - origin.y *= line_height(); - impl_->cview->move_origin(origin - impl_->cview->origin()); - impl_->cview->sync(true); + else { + select_.b = pos; + select_.a = pos; + } + points_.caret = pos; points_.xpos = points_.caret.x; impl_->try_refresh = sync_graph::refresh; + this->_m_adjust_view(); + impl_->cview->sync(true); + this->reset_caret(); } } @@ -2633,9 +2439,9 @@ namespace nana{ namespace widgets const upoint& text_editor::mouse_caret(const point& scrpos, bool stay_in_view) //From screen position { - points_.caret = _m_screen_to_caret(scrpos); + points_.caret = _m_coordinate_to_caret(scrpos); - if (stay_in_view && impl_->capacities.behavior->adjust_caret_into_screen()) + if (stay_in_view && this->_m_adjust_view()) impl_->try_refresh = sync_graph::refresh; move_caret(points_.caret); @@ -2649,7 +2455,7 @@ namespace nana{ namespace widgets point text_editor::caret_screen_pos() const { - return _m_caret_to_screen(points_.caret); + return _m_caret_to_coordinate(points_.caret); } bool text_editor::scroll(bool upwards, bool vert) @@ -2667,7 +2473,7 @@ namespace nana{ namespace widgets { auto const height = line_height(); - auto top = _m_caret_to_screen(upoint{ 0, static_cast(row.first) }).y; + auto top = _m_caret_to_coordinate(upoint{ 0, static_cast(row.first) }).y; std::size_t lines = 1; if (whole_line) @@ -2751,7 +2557,7 @@ namespace nana{ namespace widgets this->impl_->capacities.behavior->pre_calc_line(pos, width_px); } - nana::point text_editor::_m_caret_to_screen(nana::upoint pos) const + nana::point text_editor::_m_caret_to_coordinate(nana::upoint pos, bool to_screen_coordinate) const { auto const behavior = impl_->capacities.behavior; auto const sections = behavior->line(pos.y); @@ -2759,7 +2565,7 @@ namespace nana{ namespace widgets std::size_t lines = 0; //lines before the caret line; for (std::size_t i = 0; i < pos.y; ++i) { - lines += behavior->line(i).size(); + lines += behavior->take_lines(i); } const text_section * sct_ptr = nullptr; @@ -2767,6 +2573,8 @@ namespace nana{ namespace widgets if (0 != pos.x) { std::wstring str; + + std::size_t sct_pos = 0; for (auto & sct : sections) { std::size_t chsize = sct.end - sct.begin; @@ -2776,7 +2584,9 @@ namespace nana{ namespace widgets else str.append(sct.begin, sct.end); - if (pos.x <= chsize) + //In line-wrapped mode. If the caret is at the end of a line which is not the last section, + //the caret should be moved to the beginning of next section line. + if ((sct_pos + 1 < sections.size()) ? (pos.x < chsize) : (pos.x <= chsize)) { sct_ptr = &sct; if (pos.x == chsize) @@ -2790,6 +2600,8 @@ namespace nana{ namespace widgets pos.x -= static_cast(chsize); ++lines; } + + ++sct_pos; } } @@ -2803,13 +2615,26 @@ namespace nana{ namespace widgets else scrpos.x += _m_text_x(*sct_ptr); - scrpos.y = static_cast(lines * line_height()) - impl_->cview->origin().y + this->_m_text_top_base(); + if (!to_screen_coordinate) + { + scrpos.y = static_cast(lines * line_height()); + //_m_text_x includes origin x and text_area x. remove these factors + scrpos.x += (impl_->cview->origin().x - text_area_.area.x); + + } + else + scrpos.y = static_cast(lines * line_height()) - impl_->cview->origin().y + this->_m_text_top_base(); + return scrpos; } - upoint text_editor::_m_screen_to_caret(point scrpos) const + upoint text_editor::_m_coordinate_to_caret(point scrpos, bool from_screen_coordinate) const { + if (!from_screen_coordinate) + scrpos -= (impl_->cview->origin() - point{ text_area_.area.x, this->_m_text_top_base() }); + auto const behavior = impl_->capacities.behavior; + auto const row = behavior->text_position_from_screen(scrpos.y); auto sections = behavior->line(row.first); @@ -2833,10 +2658,8 @@ namespace nana{ namespace widgets unicode_bidi{}.linestr(text_ptr, text_size, reordered); nana::upoint res(static_cast(real_str.begin - sections.front().begin), static_cast(row.first)); - scrpos.x -= _m_text_x(sections[row.second]); - if (scrpos.x < 0) - scrpos.x = 0; + scrpos.x = (std::max)(0, (scrpos.x - _m_text_x(sections[row.second]))); for (auto & ent : reordered) { @@ -2849,7 +2672,12 @@ namespace nana{ namespace widgets } scrpos.x -= str_px; } - res.x = static_cast(textbase().getline(res.y).size()); + + //move the caret to the end of this section. + res.x = text_size; + for (std::size_t i = 0; i < row.second; ++i) + res.x += static_cast(sections[i].end - sections[i].begin); + return res; } @@ -2939,7 +2767,7 @@ namespace nana{ namespace widgets } _m_pos_from_secondary(points_.caret.y, secondary_pos, points_.caret.x); - return behavior->adjust_caret_into_screen(); + return this->_m_adjust_view(); } void text_editor::_m_update_line(std::size_t pos, std::size_t secondary_count_before) @@ -2947,7 +2775,7 @@ namespace nana{ namespace widgets auto behavior = impl_->capacities.behavior; if (behavior->take_lines(pos) == secondary_count_before) { - auto top = _m_caret_to_screen(upoint{ 0, static_cast(pos) }).y; + auto top = _m_caret_to_coordinate(upoint{ 0, static_cast(pos) }).y; const unsigned pixels = line_height(); const rectangle update_area = { text_area_.area.x, top, width_pixels(), static_cast(pixels * secondary_count_before) }; @@ -3010,12 +2838,12 @@ namespace nana{ namespace widgets if (this->attributes_.line_wrapped) { + //detect if vertical scrollbar is required + auto const max_lines = screen_lines(true); + if (calc_lines) { - //detect if vertical scrollbar is required - auto const max_lines = screen_lines(true); auto text_lines = textbase().lines(); - if (text_lines <= max_lines) { std::size_t lines = 0; @@ -3034,11 +2862,12 @@ namespace nana{ namespace widgets //enable vertical scrollbar when text_lines > max_lines csize.width = _m_width_px(text_lines <= max_lines); - impl_->capacities.behavior->pre_calc_lines(csize.width); } else + { csize.width = impl_->cview->content_size().width; + } } else { @@ -3046,7 +2875,7 @@ namespace nana{ namespace widgets impl_->capacities.behavior->pre_calc_lines(0); auto maxline = textbase().max_line(); - csize.width = _m_text_extent_size(textbase().getline(maxline.first).c_str(), maxline.second).width; + csize.width = _m_text_extent_size(textbase().getline(maxline.first).c_str(), maxline.second).width + caret_size().width; } csize.height = static_cast(impl_->capacities.behavior->take_lines() * line_height()); @@ -3237,11 +3066,13 @@ namespace nana{ namespace widgets { case 1: points_.caret = a; - _m_move_offset_x_while_over_border(-2); + //_m_move_offset_x_while_over_border(-2); //deprecated + this->_m_adjust_view(); break; case 2: points_.caret = b; - _m_move_offset_x_while_over_border(2); + //_m_move_offset_x_while_over_border(2); //deprecated + this->_m_adjust_view(); break; } select_.a = select_.b = points_.caret; @@ -3271,50 +3102,63 @@ namespace nana{ namespace widgets return graph_.text_extent_size(str, static_cast(n)); } - //_m_move_offset_x_while_over_border - //@brief: Move the view window - bool text_editor::_m_move_offset_x_while_over_border(int many) + bool text_editor::_m_adjust_view() { - //x never beyonds border in line-wrapped mode. - if (attributes_.line_wrapped || (0 == many)) + auto const view_area = impl_->cview->view_area(); + + auto const line_px = static_cast(this->line_height()); + auto coord = _m_caret_to_coordinate(points_.caret, true); + + if (view_area.is_hit(coord) && view_area.is_hit({coord.x, coord.y + line_px})) return false; - const string_type& lnstr = textbase().getline(points_.caret.y); - unsigned width = _m_text_extent_size(lnstr.c_str(), points_.caret.x).width; + unsigned extra_count_horz = 4; + unsigned extra_count_vert = 0; - const auto count = static_cast(std::abs(many)); - if(many < 0) + auto const origin = impl_->cview->origin(); + coord = _m_caret_to_coordinate(points_.caret, false); + + point moved_origin; + + //adjust x-axis if it isn't line-wrapped mode + if (!attributes_.line_wrapped) { - auto origin = impl_->cview->origin(); + auto extra = points_.caret; - if (origin.x && (origin.x >= static_cast(width))) - { //Out of screen text area - if (points_.caret.x > count) - origin.x = static_cast(width - _m_text_extent_size(lnstr.c_str() + points_.caret.x - count, count).width); - else - origin.x = 0; - - impl_->cview->move_origin(origin - impl_->cview->origin()); - return true; + if (coord.x < origin.x) + { + extra.x -= (std::min)(extra_count_horz, points_.caret.x); + moved_origin.x = _m_caret_to_coordinate(extra, false).x - origin.x; + } + else if (coord.x + static_cast(caret_size().width) >= origin.x + static_cast(view_area.width)) + { + extra.x = (std::min)(textbase().getline(points_.caret.y).size(), points_.caret.x + extra_count_horz); + auto new_origin = _m_caret_to_coordinate(extra, false).x + static_cast(caret_size().width) - static_cast(view_area.width); + moved_origin.x = new_origin - origin.x; } } - else + + //upoint pos2nd; + //this->_m_pos_secondary(points_.caret, pos2nd); //deprecated + + auto extra_px = static_cast(line_px * extra_count_vert); + + if (coord.y < origin.y) { - auto const right_pos = impl_->cview->view_area().right(); - width += text_area_.area.x; + //Top of caret is less than the top of view - auto origin = impl_->cview->origin(); - if (static_cast(width) - origin.x >= right_pos) - { //Out of screen text area - origin.x = static_cast(width) - right_pos + 1; - auto rest_size = lnstr.size() - points_.caret.x; - origin.x += static_cast(_m_text_extent_size(lnstr.c_str() + points_.caret.x, (rest_size >= static_cast(many) ? static_cast(many) : rest_size)).width); - - impl_->cview->move_origin(origin - impl_->cview->origin()); - return true; - } + moved_origin.y = (std::max)(0, coord.y - extra_px) - origin.y; } - return false; + else if (coord.y + line_px >= origin.y + static_cast(view_area.height)) + { + //Bottom of caret is greater than the bottom of view + + auto const bottom = static_cast(impl_->capacities.behavior->take_lines() * line_px); + auto new_origin = (std::min)(coord.y + line_px + extra_px, bottom) - static_cast(view_area.height); + moved_origin.y = new_origin - origin.y; + } + + return impl_->cview->move_origin(moved_origin); } bool text_editor::_m_move_select(bool record_undo) @@ -3696,9 +3540,9 @@ namespace nana{ namespace widgets bool text_editor::_m_update_caret_line(std::size_t secondary_before) { - if (false == impl_->capacities.behavior->adjust_caret_into_screen()) + if(false == this->_m_adjust_view()) { - if (_m_caret_to_screen(points_.caret).x < impl_->cview->view_area().right()) + if (_m_caret_to_coordinate(points_.caret).x < impl_->cview->view_area().right()) { _m_update_line(points_.caret.y, secondary_before); return false; @@ -3789,4 +3633,3 @@ namespace nana{ namespace widgets }//end namespace skeletons }//end namespace widgets }//end namespace nana -