refactor text_editor

fix issues that caret works incorrectly in line-wrapped mode.
This commit is contained in:
Jinhao 2017-05-23 04:22:08 +08:00
parent df6c707356
commit a4f15f7bb0
5 changed files with 302 additions and 435 deletions

View File

@ -85,6 +85,8 @@ namespace nana{ namespace widgets
text_editor(window, graph_reference, const text_editor_scheme*); text_editor(window, graph_reference, const text_editor_scheme*);
~text_editor(); ~text_editor();
size caret_size() const;
void set_highlight(const ::std::string& name, const ::nana::color&, const ::nana::color&); void set_highlight(const ::std::string& name, const ::nana::color&, const ::nana::color&);
void erase_highlight(const ::std::string& name); 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); 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<upoint> _m_render_text(const ::nana::color& text_color); std::vector<upoint> _m_render_text(const ::nana::color& text_color);
void _m_pre_calc_lines(std::size_t line_off, std::size_t lines); void _m_pre_calc_lines(std::size_t line_off, std::size_t lines);
::nana::point _m_caret_to_screen(::nana::upoint pos) const; //Caret to screen coordinate or context coordiate(in pixels)
::nana::upoint _m_screen_to_caret(::nana::point pos) const; ::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_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; 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; unsigned _m_tabs_pixels(size_type tabs) const;
nana::size _m_text_extent_size(const char_type*, size_type n) const; nana::size _m_text_extent_size(const char_type*, size_type n) const;
/// Moves the view of window. /// Adjust position of view to make caret stay in screen
bool _m_move_offset_x_while_over_border(int many); bool _m_adjust_view();
bool _m_move_select(bool record_undo); bool _m_move_select(bool record_undo);
int _m_text_top_base() const; int _m_text_top_base() const;

View File

@ -1,7 +1,7 @@
/* /*
* A textbase class implementation * A textbase class implementation
* Nana C++ Library(http://www.nanapro.org) * 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. * Distributed under the Boost Software License, Version 1.0.
* (See accompanying file LICENSE_1_0.txt or copy at * (See accompanying file LICENSE_1_0.txt or copy at
@ -44,7 +44,7 @@ namespace skeletons
{ {
attr_max_.reset(); attr_max_.reset();
//Insert an empty string for the first line of empty text. //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) void set_event_agent(textbase_event_agent_interface * evt)
@ -55,7 +55,7 @@ namespace skeletons
bool empty() const bool empty() const
{ {
return (text_cont_.empty() || 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) bool load(const char* file_utf8)
@ -135,10 +135,10 @@ namespace skeletons
while(ifs.good()) while(ifs.good())
{ {
std::getline(ifs, str_mbs); std::getline(ifs, str_mbs);
text_cont_.emplace_back(static_cast<string_type&&>(nana::charset{ str_mbs })); text_cont_.emplace_back(new string_type(static_cast<string_type&&>(nana::charset{ str_mbs })));
if(text_cont_.back().size() > attr_max_.size) 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; attr_max_.line = text_cont_.size() - 1;
} }
} }
@ -218,9 +218,9 @@ namespace skeletons
byte_order_translate_4bytes(str); byte_order_translate_4bytes(str);
} }
text_cont_.emplace_back(static_cast<string_type&&>(nana::charset{ str, encoding })); text_cont_.emplace_back(new string_type(static_cast<string_type&&>(nana::charset{ str, encoding })));
attr_max_.size = text_cont_.back().size(); attr_max_.size = text_cont_.back()->size();
attr_max_.line = 0; attr_max_.line = 0;
} }
@ -236,10 +236,10 @@ namespace skeletons
byte_order_translate_4bytes(str); byte_order_translate_4bytes(str);
} }
text_cont_.emplace_back(static_cast<string_type&&>(nana::charset{ str, encoding })); text_cont_.emplace_back(new string_type(static_cast<string_type&&>(nana::charset{ str, encoding })));
if(text_cont_.back().size() > attr_max_.size) 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; attr_max_.line = text_cont_.size() - 1;
} }
} }
@ -253,6 +253,9 @@ namespace skeletons
std::ofstream ofs(to_osmbstr(fs), std::ios::binary); std::ofstream ofs(to_osmbstr(fs), std::ios::binary);
if(ofs && text_cont_.size()) if(ofs && text_cont_.size())
{ {
auto i = text_cont_.cbegin();
auto const count = text_cont_.size() - 1;
std::string last_mbs; std::string last_mbs;
if (is_unicode) if (is_unicode)
@ -272,32 +275,27 @@ namespace skeletons
if (bytes) if (bytes)
ofs.write(le_boms[static_cast<int>(encoding)], bytes); ofs.write(le_boms[static_cast<int>(encoding)], bytes);
if (text_cont_.size() > 1) for (std::size_t pos = 0; pos < count; ++pos)
{ {
std::string mbs; auto mbs = nana::charset(**(i++)).to_bytes(encoding);
for (auto i = text_cont_.cbegin(), end = text_cont_.cend() - 1; i != end; ++i) ofs.write(mbs.c_str(), static_cast<std::streamsize>(mbs.size()));
{ ofs.write("\r\n", 2);
std::string(nana::charset(*i).to_bytes(encoding)).swap(mbs);
mbs += "\r\n";
ofs.write(mbs.c_str(), static_cast<std::streamsize>(mbs.size()));
}
} }
last_mbs = nana::charset(text_cont_.back()).to_bytes(encoding); last_mbs = nana::charset(*text_cont_.back()).to_bytes(encoding);
} }
else 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());
std::string mbs = nana::charset(*i); ofs.write("\r\n", 2);
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<std::streamsize>(last_mbs.size())); ofs.write(last_mbs.c_str(), static_cast<std::streamsize>(last_mbs.size()));
_m_saved(std::move(fs)); _m_saved(std::move(fs));
} }
@ -310,8 +308,8 @@ namespace skeletons
const string_type& getline(size_type pos) const const string_type& getline(size_type pos) const
{ {
if(pos < text_cont_.size()) if (pos < text_cont_.size())
return text_cont_[pos]; return *text_cont_[pos];
return nullstr_; return nullstr_;
} }
@ -323,13 +321,13 @@ namespace skeletons
public: public:
void replace(size_type pos, string_type && text) 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; pos = text_cont_.size() - 1;
} }
else else
text_cont_[pos].swap(text); _m_at(pos).swap(text);
_m_make_max(pos); _m_make_max(pos);
_m_edited(); _m_edited();
@ -339,7 +337,7 @@ namespace skeletons
{ {
if(pos.y < text_cont_.size()) 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()) if(pos.x < lnstr.size())
lnstr.insert(pos.x, str); lnstr.insert(pos.x, str);
@ -348,7 +346,7 @@ namespace skeletons
} }
else else
{ {
text_cont_.emplace_back(std::move(str)); text_cont_.emplace_back(new string_type(std::move(str)));
pos.y = static_cast<unsigned>(text_cont_.size() - 1); pos.y = static_cast<unsigned>(text_cont_.size() - 1);
} }
@ -358,10 +356,10 @@ namespace skeletons
void insertln(size_type pos, string_type&& str) void insertln(size_type pos, string_type&& str)
{ {
if(pos < text_cont_.size()) if (pos < text_cont_.size())
text_cont_.emplace(text_cont_.begin() + pos, std::move(str)); text_cont_.emplace(_m_iat(pos), new string_type(std::move(str)));
else else
text_cont_.emplace_back(std::move(str)); text_cont_.emplace_back(new string_type(std::move(str)));
_m_make_max(pos); _m_make_max(pos);
_m_edited(); _m_edited();
@ -371,7 +369,7 @@ namespace skeletons
{ {
if (line < text_cont_.size()) if (line < text_cont_.size())
{ {
string_type& lnstr = text_cont_[line]; string_type& lnstr = _m_at(line);
if ((pos == 0) && (count >= lnstr.size())) if ((pos == 0) && (count >= lnstr.size()))
lnstr.clear(); lnstr.clear();
else else
@ -393,7 +391,7 @@ namespace skeletons
if (pos + n > text_cont_.size()) if (pos + n > text_cont_.size())
n = text_cont_.size() - pos; 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) if (pos <= attr_max_.line && attr_max_.line < pos + n)
_m_scan_for_max(); _m_scan_for_max();
@ -408,7 +406,7 @@ namespace skeletons
{ {
text_cont_.clear(); text_cont_.clear();
attr_max_.reset(); 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()); _m_saved(std::string());
} }
@ -417,9 +415,14 @@ namespace skeletons
{ {
if(pos + 1 < text_cont_.size()) if(pos + 1 < text_cont_.size())
{ {
text_cont_[pos] += text_cont_[pos + 1]; auto i = _m_iat(pos + 1);
text_cont_.erase(text_cont_.begin() + (pos + 1)); _m_at(pos) += **i;
text_cont_.erase(i);
_m_make_max(pos); _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) if(pos < attr_max_.line)
--attr_max_.line; --attr_max_.line;
@ -458,9 +461,19 @@ namespace skeletons
return edited() || filename_.empty(); return edited() || filename_.empty();
} }
private: private:
string_type& _m_at(size_type pos)
{
return **_m_iat(pos);
}
typename std::deque<std::unique_ptr<string_type>>::iterator _m_iat(size_type pos)
{
return text_cont_.begin() + pos;
}
void _m_make_max(std::size_t 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) if(str.size() > attr_max_.size)
{ {
attr_max_.size = str.size(); attr_max_.size = str.size();
@ -472,11 +485,11 @@ namespace skeletons
{ {
attr_max_.size = 0; attr_max_.size = 0;
std::size_t n = 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; attr_max_.line = n;
} }
++n; ++n;
@ -514,7 +527,7 @@ namespace skeletons
evt_agent_->text_changed(); evt_agent_->text_changed();
} }
private: private:
std::deque<string_type> text_cont_; std::deque<std::unique_ptr<string_type>> text_cont_;
textbase_event_agent_interface* evt_agent_{ nullptr }; textbase_event_agent_interface* evt_agent_{ nullptr };
mutable bool changed_{ false }; mutable bool changed_{ false };

View File

@ -319,31 +319,31 @@ namespace nana {
void content_view::content_size(const size& sz, bool try_update) 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 (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; impl_->origin.y = 0;
else 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 (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; impl_->origin.x = 0;
else else
impl_->origin.x = sz.width - impl_->disp_area.width; impl_->origin.x = sz.width - view_sz.width;
} }
} }
impl_->content_size = sz; impl_->content_size = sz;
impl_->size_changed(try_update); impl_->size_changed(try_update);
} }
@ -388,18 +388,23 @@ namespace nana {
rectangle content_view::view_area() const rectangle content_view::view_area() const
{ {
unsigned extra_horz = (impl_->disp_area.width < impl_->content_size.width ? space() : 0); return view_area(impl_->content_size);
unsigned extra_vert = (impl_->disp_area.height < impl_->content_size.height + extra_horz ? space() : 0); }
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) 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{ return rectangle{
impl_->disp_area.position(), impl_->disp_area.position(),
size{ size{
impl_->disp_area.width > extra_vert ? impl_->disp_area.width - extra_vert : 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 impl_->disp_area.height > extra_horz ? impl_->disp_area.height - extra_horz : 0
} }
}; };
} }

View File

@ -65,6 +65,7 @@ namespace skeletons
void draw_corner(graph_reference); void draw_corner(graph_reference);
rectangle view_area() const; rectangle view_area() const;
rectangle view_area(const size& alt_content_size) const;
unsigned extra_space(bool horz) const; unsigned extra_space(bool horz) const;

View File

@ -18,6 +18,7 @@
#include <nana/gui/widgets/widget.hpp> #include <nana/gui/widgets/widget.hpp>
#include "content_view.hpp" #include "content_view.hpp"
#include <deque>
#include <numeric> #include <numeric>
#include <cwctype> #include <cwctype>
#include <cstring> #include <cstring>
@ -45,7 +46,7 @@ namespace nana{ namespace widgets
{ {
public: public:
using command = EnumCommand; using command = EnumCommand;
using container = std::deque < std::unique_ptr<undoable_command_interface<command>> >; using container = std::deque<std::unique_ptr<undoable_command_interface<command>>>;
void clear() void clear()
{ {
@ -555,7 +556,6 @@ namespace nana{ namespace widgets
virtual std::size_t take_lines() const = 0; virtual std::size_t take_lines() const = 0;
/// Returns the number of lines that the line of text specified by pos takes. /// 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 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) inline bool is_right_text(const unicode_bidi::entity& e)
@ -685,62 +685,6 @@ namespace nana{ namespace widgets
{ {
return 1; 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<decltype(points.caret.x)>(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<int>(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<int>(editor_.line_height());
if (points.caret.y >= row + scrlines) //implicit condition scrlines > 0
{
row = static_cast<int>(points.caret.y - scrlines) + 1;
}
else if (static_cast<int>(points.caret.y) < row)
{
if (scrlines >= static_cast<unsigned>(row))
row = 0;
else
row = static_cast<int>(row - scrlines);
}
else if (row && (textbase.lines() <= scrlines))
row = 0;
if(row != origin.y / static_cast<int>(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: private:
text_editor& editor_; text_editor& editor_;
std::vector<text_section> sections_; std::vector<text_section> sections_;
@ -773,9 +717,9 @@ namespace nana{ namespace widgets
if ((0 == editor_.textbase().lines()) || (0 == line_px)) if ((0 == editor_.textbase().lines()) || (0 == line_px))
return coord; 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<std::size_t>(screen_rows)); coord = _m_textline(static_cast<std::size_t>(text_row));
if (linemtr_.size() <= coord.first) if (linemtr_.size() <= coord.first)
{ {
coord.first = linemtr_.size() - 1; coord.first = linemtr_.size() - 1;
@ -795,22 +739,26 @@ namespace nana{ namespace widgets
std::swap(first, second); std::swap(first, second);
if (second < linemtr_.size()) 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 //textbase is implement by using deque, and the linemtr holds the text pointers
//If the textbase is changed, it will check the text pointers. //If the textbase is changed, it will check the text pointers.
std::size_t line = 0; std::size_t line = 0;
for (auto & mtr: linemtr_) for (auto & mtr: linemtr_) //deprecated
{ {
auto& linestr = editor_.textbase().getline(line); auto& linestr = editor_.textbase().getline(line);
auto p = mtr.line_sections.front().begin; auto p = mtr.line_sections.front().begin;
if (p < linestr.c_str() || (linestr.c_str() + linestr.size() < p)) 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; ++line;
} }
*/
} }
void add_lines(std::size_t pos, std::size_t lines) override 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); 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<std::size_t>(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<int>(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<int>(scrlines - 1), bottom);
_m_set_offset_by_secondary(bottom);
return true;
}
private: private:
void _m_text_section(const std::wstring& str, std::vector<text_section>& tsec) void _m_text_section(const std::wstring& str, std::vector<text_section>& tsec)
{ {
@ -1014,90 +926,6 @@ namespace nana{ namespace widgets
tsec.emplace_back(word, end, unsigned{}); 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<int>(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<std::size_t>(-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<std::size_t>(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 _m_textline(std::size_t scrline) const
{ {
row_coordinate coord; row_coordinate coord;
@ -1236,6 +1064,12 @@ namespace nana{ namespace widgets
this->reset_caret(); 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::create_caret(wd, { 1, line_height() });
API::bgcolor(wd, colors::white); API::bgcolor(wd, colors::white);
API::fgcolor(wd, colors::black); API::fgcolor(wd, colors::black);
@ -1249,6 +1083,11 @@ namespace nana{ namespace widgets
delete impl_; 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) void text_editor::set_highlight(const std::string& name, const ::nana::color& fgcolor, const ::nana::color& bgcolor)
{ {
if (fgcolor.invisible() && bgcolor.invisible()) if (fgcolor.invisible() && bgcolor.invisible())
@ -1680,7 +1519,7 @@ namespace nana{ namespace widgets
API::set_capture(window_, true); API::set_capture(window_, true);
text_area_.captured = 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 //The selected of text can be moved only if it is editable
if (attributes_.editable) if (attributes_.editable)
@ -1732,7 +1571,7 @@ namespace nana{ namespace widgets
//no move occurs //no move occurs
select(false); 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; select_.mode_selection = selection::mode::no_selected;
@ -1798,7 +1637,8 @@ namespace nana{ namespace widgets
if (graph_) if (graph_)
{ {
impl_->capacities.behavior->adjust_caret_into_screen(); this->_m_adjust_view();
reset_caret(); reset_caret();
impl_->try_refresh = sync_graph::refresh; impl_->try_refresh = sync_graph::refresh;
points_.xpos = 0; points_.xpos = 0;
@ -1832,7 +1672,7 @@ namespace nana{ namespace widgets
const unsigned line_pixels = line_height(); const unsigned line_pixels = line_height();
//The coordinate of caret //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<int>(line_pixels); const int line_bottom = coord.y + static_cast<int>(line_pixels);
@ -1863,7 +1703,7 @@ namespace nana{ namespace widgets
//Adjust the caret into screen when the caret position is modified by this function //Adjust the caret into screen when the caret position is modified by this function
if (reset_caret && (!hit_text_area(coord))) if (reset_caret && (!hit_text_area(coord)))
{ {
impl_->capacities.behavior->adjust_caret_into_screen(); this->_m_adjust_view();
impl_->try_refresh = sync_graph::refresh; impl_->try_refresh = sync_graph::refresh;
caret->visible(true); caret->visible(true);
return true; return true;
@ -1949,7 +1789,7 @@ namespace nana{ namespace widgets
select_.b = points_.caret; select_.b = points_.caret;
points_.xpos = points_.caret.x; 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; impl_->try_refresh = sync_graph::refresh;
} }
@ -1985,7 +1825,7 @@ namespace nana{ namespace widgets
{ {
points_.caret = select_.b; points_.caret = select_.b;
if (impl_->capacities.behavior->adjust_caret_into_screen()) if (this->_m_adjust_view())
impl_->try_refresh = sync_graph::refresh; impl_->try_refresh = sync_graph::refresh;
reset_caret(); reset_caret();
@ -1994,7 +1834,7 @@ namespace nana{ namespace widgets
if (_m_move_select(true)) if (_m_move_select(true))
{ {
impl_->capacities.behavior->adjust_caret_into_screen(); this->_m_adjust_view();
impl_->try_refresh = sync_graph::refresh; impl_->try_refresh = sync_graph::refresh;
return true; return true;
} }
@ -2117,7 +1957,8 @@ namespace nana{ namespace widgets
if(graph_) if(graph_)
{ {
impl_->capacities.behavior->adjust_caret_into_screen(); this->_m_adjust_view();
reset_caret(); reset_caret();
impl_->try_refresh = sync_graph::refresh; impl_->try_refresh = sync_graph::refresh;
_m_reset_content_size(true); _m_reset_content_size(true);
@ -2269,7 +2110,8 @@ namespace nana{ namespace widgets
} }
impl_->cview->move_origin(origin - impl_->cview->origin()); 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); impl_->cview->sync(true);
_m_reset_content_size(); _m_reset_content_size();
@ -2320,7 +2162,7 @@ namespace nana{ namespace widgets
textbase.erase(points_.caret.y, points_.caret.x, erase_number); textbase.erase(points_.caret.y, points_.caret.x, erase_number);
_m_pre_calc_lines(points_.caret.y, 1); _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); _m_update_line(points_.caret.y, secondary);
has_to_redraw = false; has_to_redraw = false;
@ -2349,11 +2191,11 @@ namespace nana{ namespace widgets
if (record_undo) if (record_undo)
impl_->undo.push(std::move(undo_ptr)); impl_->undo.push(std::move(undo_ptr));
_m_reset_content_size(has_to_redraw); _m_reset_content_size(false);
if(has_to_redraw) if(has_to_redraw)
{ {
impl_->capacities.behavior->adjust_caret_into_screen(); this->_m_adjust_view();
impl_->try_refresh = sync_graph::refresh; impl_->try_refresh = sync_graph::refresh;
} }
} }
@ -2367,7 +2209,7 @@ namespace nana{ namespace widgets
_m_reset_content_size(true); _m_reset_content_size(true);
impl_->capacities.behavior->adjust_caret_into_screen(); this->_m_adjust_view();
impl_->try_refresh = sync_graph::refresh; impl_->try_refresh = sync_graph::refresh;
} }
@ -2391,10 +2233,8 @@ namespace nana{ namespace widgets
if(points_.caret.x) if(points_.caret.x)
{ {
--points_.caret.x; --points_.caret.x;
pending = false; pending = false;
bool adjust_y = (attributes_.line_wrapped && impl_->capacities.behavior->adjust_caret_into_screen()); if (this->_m_adjust_view())
if (_m_move_offset_x_while_over_border(-2) || adjust_y)
impl_->try_refresh = sync_graph::refresh; impl_->try_refresh = sync_graph::refresh;
} }
else if (points_.caret.y) //Move to previous line else if (points_.caret.y) //Move to previous line
@ -2403,7 +2243,7 @@ namespace nana{ namespace widgets
pending = false; pending = false;
} }
if (pending && impl_->capacities.behavior->adjust_caret_into_screen()) if (pending && this->_m_adjust_view())
impl_->try_refresh = sync_graph::refresh; impl_->try_refresh = sync_graph::refresh;
points_.xpos = points_.caret.x; points_.xpos = points_.caret.x;
@ -2412,25 +2252,23 @@ namespace nana{ namespace widgets
void text_editor::move_right() void text_editor::move_right()
{ {
bool do_render = false; bool do_render = false;
if(_m_cancel_select(2) == false) if (_m_cancel_select(2) == false)
{ {
auto lnstr = textbase().getline(points_.caret.y); auto lnstr = textbase().getline(points_.caret.y);
if(lnstr.size() > points_.caret.x) if (lnstr.size() > points_.caret.x)
{ {
++points_.caret.x; ++points_.caret.x;
do_render = this->_m_adjust_view();
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);
} }
else if(points_.caret.y + 1 < textbase().lines()) else if (points_.caret.y + 1 < textbase().lines())
{ //Move to next line { //Move to next line
points_.caret.x = 0; points_.caret.x = 0;
++ points_.caret.y; ++points_.caret.y;
do_render = impl_->capacities.behavior->adjust_caret_into_screen(); do_render = this->_m_adjust_view();
} }
} }
else else
do_render = impl_->capacities.behavior->adjust_caret_into_screen(); do_render = this->_m_adjust_view();
if (do_render) if (do_render)
impl_->try_refresh = sync_graph::refresh; impl_->try_refresh = sync_graph::refresh;
@ -2444,155 +2282,123 @@ namespace nana{ namespace widgets
select_.a = select_.b = points_.caret; select_.a = select_.b = points_.caret;
auto origin = impl_->cview->origin(); 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; wchar_t key = arg.key;
size_t nlines = textbase().lines();
if (arg.ctrl) { auto const line_px = this->line_height();
switch (key) {
case keyboard::os_arrow_left: //The number of text lines
case keyboard::os_arrow_right: auto const line_count = textbase().lines();
// TODO: move the caret word by word
break; //The number of charecters in the line of caret
case keyboard::os_home: auto const text_length = textbase().getline(points_.caret.y).size();
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<decltype(caret.y)>(nlines - 1);
changed = true;
}
break;
}
}
size_t lnsz = textbase().getline(caret.y).size();
switch (key) { switch (key) {
case keyboard::os_arrow_left: case keyboard::os_arrow_left:
if (select_.move_to_end && (select_.a != select_.b) && (!arg.shift)) if (select_.move_to_end && (select_.a != select_.b) && (!arg.shift))
{ {
caret = select_.a; pos = select_.a;
changed = true;
} }
else else if (pos.x != 0)
{ {
if (caret.x != 0) { --pos.x;
--caret.x; }
changed = true; else if (pos.y != 0) {
} --pos.y;
else { pos.x = static_cast<decltype(pos.x)>(textbase().getline(pos.y).size());
if (caret.y != 0) {
--caret.y;
caret.x = static_cast<decltype(caret.x)>(textbase().getline(caret.y).size());
changed = true;
}
}
} }
break; break;
case keyboard::os_arrow_right: case keyboard::os_arrow_right:
if (select_.move_to_end && (select_.a != select_.b) && (!arg.shift)) if (select_.move_to_end && (select_.a != select_.b) && (!arg.shift))
{ {
caret = select_.b; pos = select_.b;
changed = true;
} }
else else if (pos.x < text_length)
{ {
if (caret.x < lnsz) { ++pos.x;
++caret.x; }
changed = true; else if (pos.y != line_count - 1)
} {
else { ++pos.y;
if (caret.y != nlines - 1) { pos.x = 0;
++caret.y;
caret.x = 0;
changed = true;
}
}
} }
break; break;
case keyboard::os_arrow_up: case keyboard::os_arrow_up:
coord.y -= static_cast<int>(line_px);
break;
case keyboard::os_arrow_down: case keyboard::os_arrow_down:
{ coord.y += static_cast<int>(line_px);
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;
}
}
break; break;
case keyboard::os_home: case keyboard::os_home:
if (caret.x != 0) { //move the caret to the begining of the line
caret.x = 0; pos.x = 0;
changed = true;
} //move the caret to the begining of the text if Ctrl is pressed
if (arg.ctrl)
pos.y = 0;
break; break;
case keyboard::os_end: case keyboard::os_end:
if (caret.x < lnsz) { //move the caret to the end of the line
caret.x = static_cast<decltype(caret.x)>(lnsz); pos.x = static_cast<decltype(pos.x)>(text_length);
changed = true;
} //move the caret to the end of the text if Ctrl is pressed
if(arg.ctrl)
pos.y = (line_count - 1) * line_px;
break; break;
case keyboard::os_pageup: case keyboard::os_pageup:
if (caret.y >= screen_lines() && origin.y >= static_cast<int>(screen_lines())) { if(origin.y > 0)
origin.y -= screen_lines(); {
caret.y -= screen_lines(); auto off = coord - origin;
changed = true; origin.y -= (std::min)(origin.y, static_cast<int>(impl_->cview->view_area().height));
coord = off + origin;
} }
break; break;
case keyboard::os_pagedown: case keyboard::os_pagedown:
if (caret.y + screen_lines() <= impl_->capacities.behavior->take_lines()) { if (impl_->cview->content_size().height > impl_->cview->view_area().height)
origin.y += static_cast<int>(screen_lines()); {
caret.y += screen_lines(); auto off = coord - origin;
changed = true; origin.y = (std::min)(origin.y + static_cast<int>(impl_->cview->view_area().height), static_cast<int>(impl_->cview->content_size().height - impl_->cview->view_area().height));
coord = off + origin;
} }
break; 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) { if (arg.shift) {
switch (key) { switch (key) {
case keyboard::os_arrow_left: case keyboard::os_arrow_left:
case keyboard::os_arrow_up: case keyboard::os_arrow_up:
case keyboard::os_home: case keyboard::os_home:
case keyboard::os_pageup: case keyboard::os_pageup:
select_.b = caret; select_.b = pos;
break; break;
case keyboard::os_arrow_right: case keyboard::os_arrow_right:
case keyboard::os_arrow_down: case keyboard::os_arrow_down:
case keyboard::os_end: case keyboard::os_end:
case keyboard::os_pagedown: case keyboard::os_pagedown:
select_.b = caret; select_.b = pos;
break; break;
} }
}else {
select_.b = caret;
select_.a = caret;
} }
points_.caret = caret; else {
select_.b = pos;
origin.y *= line_height(); select_.a = pos;
impl_->cview->move_origin(origin - impl_->cview->origin()); }
impl_->cview->sync(true); points_.caret = pos;
points_.xpos = points_.caret.x; points_.xpos = points_.caret.x;
impl_->try_refresh = sync_graph::refresh; 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 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; impl_->try_refresh = sync_graph::refresh;
move_caret(points_.caret); move_caret(points_.caret);
@ -2649,7 +2455,7 @@ namespace nana{ namespace widgets
point text_editor::caret_screen_pos() const 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) bool text_editor::scroll(bool upwards, bool vert)
@ -2667,7 +2473,7 @@ namespace nana{ namespace widgets
{ {
auto const height = line_height(); auto const height = line_height();
auto top = _m_caret_to_screen(upoint{ 0, static_cast<unsigned>(row.first) }).y; auto top = _m_caret_to_coordinate(upoint{ 0, static_cast<unsigned>(row.first) }).y;
std::size_t lines = 1; std::size_t lines = 1;
if (whole_line) if (whole_line)
@ -2751,7 +2557,7 @@ namespace nana{ namespace widgets
this->impl_->capacities.behavior->pre_calc_line(pos, width_px); 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 behavior = impl_->capacities.behavior;
auto const sections = behavior->line(pos.y); 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; std::size_t lines = 0; //lines before the caret line;
for (std::size_t i = 0; i < pos.y; ++i) 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; const text_section * sct_ptr = nullptr;
@ -2767,6 +2573,8 @@ namespace nana{ namespace widgets
if (0 != pos.x) if (0 != pos.x)
{ {
std::wstring str; std::wstring str;
std::size_t sct_pos = 0;
for (auto & sct : sections) for (auto & sct : sections)
{ {
std::size_t chsize = sct.end - sct.begin; std::size_t chsize = sct.end - sct.begin;
@ -2776,7 +2584,9 @@ namespace nana{ namespace widgets
else else
str.append(sct.begin, sct.end); 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; sct_ptr = &sct;
if (pos.x == chsize) if (pos.x == chsize)
@ -2790,6 +2600,8 @@ namespace nana{ namespace widgets
pos.x -= static_cast<unsigned>(chsize); pos.x -= static_cast<unsigned>(chsize);
++lines; ++lines;
} }
++sct_pos;
} }
} }
@ -2803,13 +2615,26 @@ namespace nana{ namespace widgets
else else
scrpos.x += _m_text_x(*sct_ptr); scrpos.x += _m_text_x(*sct_ptr);
scrpos.y = static_cast<int>(lines * line_height()) - impl_->cview->origin().y + this->_m_text_top_base(); if (!to_screen_coordinate)
{
scrpos.y = static_cast<int>(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<int>(lines * line_height()) - impl_->cview->origin().y + this->_m_text_top_base();
return scrpos; 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 behavior = impl_->capacities.behavior;
auto const row = behavior->text_position_from_screen(scrpos.y); auto const row = behavior->text_position_from_screen(scrpos.y);
auto sections = behavior->line(row.first); auto sections = behavior->line(row.first);
@ -2833,10 +2658,8 @@ namespace nana{ namespace widgets
unicode_bidi{}.linestr(text_ptr, text_size, reordered); unicode_bidi{}.linestr(text_ptr, text_size, reordered);
nana::upoint res(static_cast<unsigned>(real_str.begin - sections.front().begin), static_cast<unsigned>(row.first)); nana::upoint res(static_cast<unsigned>(real_str.begin - sections.front().begin), static_cast<unsigned>(row.first));
scrpos.x -= _m_text_x(sections[row.second]);
if (scrpos.x < 0) scrpos.x = (std::max)(0, (scrpos.x - _m_text_x(sections[row.second])));
scrpos.x = 0;
for (auto & ent : reordered) for (auto & ent : reordered)
{ {
@ -2849,7 +2672,12 @@ namespace nana{ namespace widgets
} }
scrpos.x -= str_px; scrpos.x -= str_px;
} }
res.x = static_cast<unsigned>(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<int>(sections[i].end - sections[i].begin);
return res; return res;
} }
@ -2939,7 +2767,7 @@ namespace nana{ namespace widgets
} }
_m_pos_from_secondary(points_.caret.y, secondary_pos, points_.caret.x); _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) 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; auto behavior = impl_->capacities.behavior;
if (behavior->take_lines(pos) == secondary_count_before) if (behavior->take_lines(pos) == secondary_count_before)
{ {
auto top = _m_caret_to_screen(upoint{ 0, static_cast<unsigned>(pos) }).y; auto top = _m_caret_to_coordinate(upoint{ 0, static_cast<unsigned>(pos) }).y;
const unsigned pixels = line_height(); const unsigned pixels = line_height();
const rectangle update_area = { text_area_.area.x, top, width_pixels(), static_cast<unsigned>(pixels * secondary_count_before) }; const rectangle update_area = { text_area_.area.x, top, width_pixels(), static_cast<unsigned>(pixels * secondary_count_before) };
@ -3010,12 +2838,12 @@ namespace nana{ namespace widgets
if (this->attributes_.line_wrapped) if (this->attributes_.line_wrapped)
{ {
//detect if vertical scrollbar is required
auto const max_lines = screen_lines(true);
if (calc_lines) if (calc_lines)
{ {
//detect if vertical scrollbar is required
auto const max_lines = screen_lines(true);
auto text_lines = textbase().lines(); auto text_lines = textbase().lines();
if (text_lines <= max_lines) if (text_lines <= max_lines)
{ {
std::size_t lines = 0; std::size_t lines = 0;
@ -3034,11 +2862,12 @@ namespace nana{ namespace widgets
//enable vertical scrollbar when text_lines > max_lines //enable vertical scrollbar when text_lines > max_lines
csize.width = _m_width_px(text_lines <= max_lines); csize.width = _m_width_px(text_lines <= max_lines);
impl_->capacities.behavior->pre_calc_lines(csize.width); impl_->capacities.behavior->pre_calc_lines(csize.width);
} }
else else
{
csize.width = impl_->cview->content_size().width; csize.width = impl_->cview->content_size().width;
}
} }
else else
{ {
@ -3046,7 +2875,7 @@ namespace nana{ namespace widgets
impl_->capacities.behavior->pre_calc_lines(0); impl_->capacities.behavior->pre_calc_lines(0);
auto maxline = textbase().max_line(); 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<unsigned>(impl_->capacities.behavior->take_lines() * line_height()); csize.height = static_cast<unsigned>(impl_->capacities.behavior->take_lines() * line_height());
@ -3237,11 +3066,13 @@ namespace nana{ namespace widgets
{ {
case 1: case 1:
points_.caret = a; 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; break;
case 2: case 2:
points_.caret = b; 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; break;
} }
select_.a = select_.b = points_.caret; select_.a = select_.b = points_.caret;
@ -3271,50 +3102,63 @@ namespace nana{ namespace widgets
return graph_.text_extent_size(str, static_cast<unsigned>(n)); return graph_.text_extent_size(str, static_cast<unsigned>(n));
} }
//_m_move_offset_x_while_over_border bool text_editor::_m_adjust_view()
//@brief: Move the view window
bool text_editor::_m_move_offset_x_while_over_border(int many)
{ {
//x never beyonds border in line-wrapped mode. auto const view_area = impl_->cview->view_area();
if (attributes_.line_wrapped || (0 == many))
auto const line_px = static_cast<int>(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; return false;
const string_type& lnstr = textbase().getline(points_.caret.y); unsigned extra_count_horz = 4;
unsigned width = _m_text_extent_size(lnstr.c_str(), points_.caret.x).width; unsigned extra_count_vert = 0;
const auto count = static_cast<unsigned>(std::abs(many)); auto const origin = impl_->cview->origin();
if(many < 0) 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<int>(width))) if (coord.x < origin.x)
{ //Out of screen text area {
if (points_.caret.x > count) extra.x -= (std::min)(extra_count_horz, points_.caret.x);
origin.x = static_cast<int>(width - _m_text_extent_size(lnstr.c_str() + points_.caret.x - count, count).width); moved_origin.x = _m_caret_to_coordinate(extra, false).x - origin.x;
else }
origin.x = 0; else if (coord.x + static_cast<int>(caret_size().width) >= origin.x + static_cast<int>(view_area.width))
{
impl_->cview->move_origin(origin - impl_->cview->origin()); extra.x = (std::min)(textbase().getline(points_.caret.y).size(), points_.caret.x + extra_count_horz);
return true; auto new_origin = _m_caret_to_coordinate(extra, false).x + static_cast<int>(caret_size().width) - static_cast<int>(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<int>(line_px * extra_count_vert);
if (coord.y < origin.y)
{ {
auto const right_pos = impl_->cview->view_area().right(); //Top of caret is less than the top of view
width += text_area_.area.x;
auto origin = impl_->cview->origin(); moved_origin.y = (std::max)(0, coord.y - extra_px) - origin.y;
if (static_cast<int>(width) - origin.x >= right_pos)
{ //Out of screen text area
origin.x = static_cast<int>(width) - right_pos + 1;
auto rest_size = lnstr.size() - points_.caret.x;
origin.x += static_cast<int>(_m_text_extent_size(lnstr.c_str() + points_.caret.x, (rest_size >= static_cast<unsigned>(many) ? static_cast<unsigned>(many) : rest_size)).width);
impl_->cview->move_origin(origin - impl_->cview->origin());
return true;
}
} }
return false; else if (coord.y + line_px >= origin.y + static_cast<int>(view_area.height))
{
//Bottom of caret is greater than the bottom of view
auto const bottom = static_cast<int>(impl_->capacities.behavior->take_lines() * line_px);
auto new_origin = (std::min)(coord.y + line_px + extra_px, bottom) - static_cast<int>(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) 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) 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); _m_update_line(points_.caret.y, secondary_before);
return false; return false;
@ -3789,4 +3633,3 @@ namespace nana{ namespace widgets
}//end namespace skeletons }//end namespace skeletons
}//end namespace widgets }//end namespace widgets
}//end namespace nana }//end namespace nana