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();
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<upoint> _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;

View File

@ -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<string_type&&>(nana::charset{ str_mbs }));
if(text_cont_.back().size() > attr_max_.size)
text_cont_.emplace_back(new string_type(static_cast<string_type&&>(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<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;
}
@ -236,10 +236,10 @@ namespace skeletons
byte_order_translate_4bytes(str);
}
text_cont_.emplace_back(static_cast<string_type&&>(nana::charset{ str, encoding }));
if(text_cont_.back().size() > attr_max_.size)
text_cont_.emplace_back(new string_type(static_cast<string_type&&>(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<int>(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";
auto mbs = nana::charset(**(i++)).to_bytes(encoding);
ofs.write(mbs.c_str(), static_cast<std::streamsize>(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);
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<std::streamsize>(last_mbs.size()));
_m_saved(std::move(fs));
}
@ -311,7 +309,7 @@ namespace skeletons
const string_type& getline(size_type pos) const
{
if (pos < text_cont_.size())
return text_cont_[pos];
return *text_cont_[pos];
return nullstr_;
}
@ -325,11 +323,11 @@ namespace skeletons
{
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<unsigned>(text_cont_.size() - 1);
}
@ -359,9 +357,9 @@ 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));
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<std::unique_ptr<string_type>>::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<string_type> text_cont_;
std::deque<std::unique_ptr<string_type>> text_cont_;
textbase_event_agent_interface* evt_agent_{ nullptr };
mutable bool changed_{ false };

View File

@ -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,11 +388,16 @@ 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(),

View File

@ -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;

View File

@ -18,6 +18,7 @@
#include <nana/gui/widgets/widget.hpp>
#include "content_view.hpp"
#include <deque>
#include <numeric>
#include <cwctype>
#include <cstring>
@ -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<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:
text_editor& editor_;
std::vector<text_section> 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<std::size_t>(screen_rows));
coord = _m_textline(static_cast<std::size_t>(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<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:
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{});
}
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 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<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
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;
@ -2418,19 +2258,17 @@ namespace nana{ namespace widgets
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())
{ //Move to next line
points_.caret.x = 0;
++points_.caret.y;
do_render = impl_->capacities.behavior->adjust_caret_into_screen();
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<decltype(caret.y)>(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<decltype(caret.x)>(textbase().getline(caret.y).size());
changed = true;
}
--pos.x;
}
else if (pos.y != 0) {
--pos.y;
pos.x = static_cast<decltype(pos.x)>(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<int>(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<int>(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<decltype(caret.x)>(lnsz);
changed = true;
}
//move the caret to the end of the line
pos.x = static_cast<decltype(pos.x)>(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<int>(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<int>(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<int>(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<int>(impl_->cview->view_area().height), static_cast<int>(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<unsigned>(row.first) }).y;
auto top = _m_caret_to_coordinate(upoint{ 0, static_cast<unsigned>(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<unsigned>(chsize);
++lines;
}
++sct_pos;
}
}
@ -2803,13 +2615,26 @@ namespace nana{ namespace widgets
else
scrpos.x += _m_text_x(*sct_ptr);
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;
}
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<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 = 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<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;
}
@ -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<unsigned>(pos) }).y;
auto top = _m_caret_to_coordinate(upoint{ 0, static_cast<unsigned>(pos) }).y;
const unsigned pixels = line_height();
const rectangle update_area = { text_area_.area.x, top, width_pixels(), static_cast<unsigned>(pixels * secondary_count_before) };
@ -3009,13 +2837,13 @@ namespace nana{ namespace widgets
size csize;
if (this->attributes_.line_wrapped)
{
if (calc_lines)
{
//detect if vertical scrollbar is required
auto const max_lines = screen_lines(true);
auto text_lines = textbase().lines();
if (calc_lines)
{
auto text_lines = textbase().lines();
if (text_lines <= max_lines)
{
std::size_t lines = 0;
@ -3034,19 +2862,20 @@ 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
{
if (calc_lines)
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<unsigned>(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<unsigned>(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<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;
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<unsigned>(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<int>(width)))
{ //Out of screen text area
if (points_.caret.x > count)
origin.x = static_cast<int>(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;
}
}
else
if (coord.x < origin.x)
{
auto const right_pos = impl_->cview->view_area().right();
width += text_area_.area.x;
auto origin = impl_->cview->origin();
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;
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<int>(caret_size().width) >= origin.x + static_cast<int>(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<int>(caret_size().width) - static_cast<int>(view_area.width);
moved_origin.x = new_origin - origin.x;
}
}
return false;
//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)
{
//Top of caret is less than the top of view
moved_origin.y = (std::max)(0, coord.y - extra_px) - origin.y;
}
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)
@ -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