Improve the "double click word selection" method of textbox to follow some other rules other than white space.

This commit is contained in:
Eduardo Roeder 2018-11-03 15:43:08 -03:00
parent 7651b430eb
commit 64bd9b7491

View File

@ -1,15 +1,15 @@
/* /*
* A text editor implementation * A text editor implementation
* Nana C++ Library(http://www.nanapro.org) * Nana C++ Library(http://www.nanapro.org)
* Copyright(C) 2003-2018 Jinhao(cnjinhao@hotmail.com) * Copyright(C) 2003-2018 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
* http://www.boost.org/LICENSE_1_0.txt) * http://www.boost.org/LICENSE_1_0.txt)
* *
* @file: nana/gui/widgets/skeletons/text_editor.cpp * @file: nana/gui/widgets/skeletons/text_editor.cpp
* @contributors: Ariel Vina-Rodriguez, Oleg Smolsky * @contributors: Ariel Vina-Rodriguez, Oleg Smolsky
*/ */
#include <nana/gui/widgets/skeletons/text_editor.hpp> #include <nana/gui/widgets/skeletons/text_editor.hpp>
#include <nana/gui/widgets/skeletons/textbase_export_interface.hpp> #include <nana/gui/widgets/skeletons/textbase_export_interface.hpp>
#include <nana/gui/element.hpp> #include <nana/gui/element.hpp>
@ -25,8 +25,9 @@
#include <algorithm> #include <algorithm>
#include <map> #include <map>
namespace nana{ namespace widgets namespace nana {
{ namespace widgets
{
namespace skeletons namespace skeletons
{ {
@ -127,7 +128,7 @@ namespace nana{ namespace widgets
}; };
template<typename T> template<typename T>
using undo_command_ptr = std::unique_ptr <undoable_command_interface<T>> ; using undo_command_ptr = std::unique_ptr <undoable_command_interface<T>>;
template<typename EnumCommand> template<typename EnumCommand>
class text_editor::basic_undoable class text_editor::basic_undoable
@ -403,7 +404,7 @@ namespace nana{ namespace widgets
lazy_refresh lazy_refresh
}; };
colored_area_access_interface::~colored_area_access_interface(){} colored_area_access_interface::~colored_area_access_interface() {}
class colored_area_access class colored_area_access
: public colored_area_access_interface : public colored_area_access_interface
@ -446,7 +447,7 @@ namespace nana{ namespace widgets
} }
return *colored_areas_.emplace(i, return *colored_areas_.emplace(i,
std::make_shared<colored_area_type>(colored_area_type{line_pos, 1, color{}, color{}}) std::make_shared<colored_area_type>(colored_area_type{ line_pos, 1, color{}, color{} })
); );
} }
@ -840,7 +841,7 @@ namespace nana{ namespace widgets
{ {
std::size_t len = ts.end - ts.begin; std::size_t len = ts.end - ts.begin;
#ifdef _nana_std_has_string_view #ifdef _nana_std_has_string_view
auto pxbuf = editor_.graph_.glyph_pixels({ts.begin, len}); auto pxbuf = editor_.graph_.glyph_pixels({ ts.begin, len });
#else #else
std::unique_ptr<unsigned[]> pxbuf(new unsigned[len]); std::unique_ptr<unsigned[]> pxbuf(new unsigned[len]);
editor_.graph_.glyph_pixels(ts.begin, len, pxbuf.get()); editor_.graph_.glyph_pixels(ts.begin, len, pxbuf.get());
@ -941,7 +942,7 @@ namespace nana{ namespace widgets
word = i; word = i;
} }
if(word) if (word)
tsec.emplace_back(word, end, unsigned{}); tsec.emplace_back(word, end, unsigned{});
} }
@ -972,7 +973,7 @@ namespace nana{ namespace widgets
public: public:
void parse(const wchar_t* c_str, std::size_t len, implementation::inner_keywords& keywords) //need string_view void parse(const wchar_t* c_str, std::size_t len, implementation::inner_keywords& keywords) //need string_view
{ {
if ( keywords.base.empty() || (0 == len) || (*c_str == 0) ) if (keywords.base.empty() || (0 == len) || (*c_str == 0))
return; return;
std::wstring text{ c_str, len }; std::wstring text{ c_str, len };
@ -984,8 +985,8 @@ namespace nana{ namespace widgets
::nana::ciwstring cistr; ::nana::ciwstring cistr;
for (auto & ds : keywords.base) for (auto & ds : keywords.base)
{ {
index pos{0} ; index pos{ 0 };
for (index rest{text.size()}; rest >= ds.text.size() ; ++pos, rest = text.size() - pos) for (index rest{ text.size() }; rest >= ds.text.size(); ++pos, rest = text.size() - pos)
{ {
if (ds.case_sensitive) if (ds.case_sensitive)
{ {
@ -1139,7 +1140,7 @@ namespace nana{ namespace widgets
void text_editor::set_keyword(const ::std::wstring& kw, const std::string& name, bool case_sensitive, bool whole_word_matched) void text_editor::set_keyword(const ::std::wstring& kw, const std::string& name, bool case_sensitive, bool whole_word_matched)
{ {
for(auto & ds : impl_->keywords.base) for (auto & ds : impl_->keywords.base)
{ {
if (ds.text == kw) if (ds.text == kw)
{ {
@ -1296,7 +1297,7 @@ namespace nana{ namespace widgets
bool text_editor::text_area(const nana::rectangle& r) bool text_editor::text_area(const nana::rectangle& r)
{ {
if(text_area_.area == r) if (text_area_.area == r)
return false; return false;
text_area_.area = r; text_area_.area = r;
@ -1319,7 +1320,7 @@ namespace nana{ namespace widgets
bool text_editor::tip_string(::std::string&& str) bool text_editor::tip_string(::std::string&& str)
{ {
if(attributes_.tip_string == str) if (attributes_.tip_string == str)
return false; return false;
attributes_.tip_string = std::move(str); attributes_.tip_string = std::move(str);
@ -1356,7 +1357,7 @@ namespace nana{ namespace widgets
bool text_editor::multi_lines(bool ml) bool text_editor::multi_lines(bool ml)
{ {
if((ml == false) && attributes_.multi_lines) if ((ml == false) && attributes_.multi_lines)
{ {
//retain the first line and remove the extra lines //retain the first line and remove the extra lines
if (impl_->textbase.erase(1, impl_->textbase.lines() - 1)) if (impl_->textbase.erase(1, impl_->textbase.lines() - 1))
@ -1511,15 +1512,15 @@ namespace nana{ namespace widgets
bool text_editor::mouse_move(bool left_button, const point& scrpos) bool text_editor::mouse_move(bool left_button, const point& scrpos)
{ {
cursor cur = cursor::iterm; cursor cur = cursor::iterm;
if(((!hit_text_area(scrpos)) && (!text_area_.captured)) || !attributes_.enable_caret || !API::window_enabled(window_)) if (((!hit_text_area(scrpos)) && (!text_area_.captured)) || !attributes_.enable_caret || !API::window_enabled(window_))
cur = cursor::arrow; cur = cursor::arrow;
API::window_cursor(window_, cur); API::window_cursor(window_, cur);
if(!attributes_.enable_caret) if (!attributes_.enable_caret)
return false; return false;
if(left_button) if (left_button)
{ {
mouse_caret(scrpos, false); mouse_caret(scrpos, false);
@ -1536,7 +1537,7 @@ namespace nana{ namespace widgets
void text_editor::mouse_pressed(const arg_mouse& arg) void text_editor::mouse_pressed(const arg_mouse& arg)
{ {
if(!attributes_.enable_caret) if (!attributes_.enable_caret)
return; return;
if (event_code::mouse_down == arg.evt_code) if (event_code::mouse_down == arg.evt_code)
@ -1622,7 +1623,7 @@ namespace nana{ namespace widgets
//Oleg Smolsky //Oleg Smolsky
bool text_editor::select_word(const arg_mouse& arg) bool text_editor::select_word(const arg_mouse& arg)
{ {
if(!attributes_.enable_caret) if (!attributes_.enable_caret)
return false; return false;
// Set caret pos by screen point and get the caret pos. // Set caret pos by screen point and get the caret pos.
@ -1632,14 +1633,20 @@ namespace nana{ namespace widgets
select_.a = select_.b = points_.caret; select_.a = select_.b = points_.caret;
const auto& line = impl_->textbase.getline(select_.b.y); const auto& line = impl_->textbase.getline(select_.b.y);
if (select_.a.x < line.size() && !std::isalnum(line[select_.a.x]) && line[select_.a.x] != '_') {
++select_.b.x;
}
else {
// Expand the selection forward to the word's end. // Expand the selection forward to the word's end.
while (select_.b.x < line.size() && !std::iswspace(line[select_.b.x])) while (select_.b.x < line.size() && !std::iswspace(line[select_.b.x]) && (std::isalnum(line[select_.b.x]) || line[select_.b.x] == '_'))
++select_.b.x; ++select_.b.x;
// Expand the selection backward to the word's start. // Expand the selection backward to the word's start.
while (select_.a.x > 0 && !std::iswspace(line[select_.a.x - 1])) while (select_.a.x > 0 && !std::iswspace(line[select_.a.x - 1]) && (std::isalnum(line[select_.a.x - 1]) || line[select_.a.x - 1] == '_'))
--select_.a.x; --select_.a.x;
}
select_.mode_selection = selection::mode::method_selected; select_.mode_selection = selection::mode::method_selected;
impl_->try_refresh = sync_graph::refresh; impl_->try_refresh = sync_graph::refresh;
return true; return true;
@ -1716,10 +1723,10 @@ namespace nana{ namespace widgets
{ {
std::wstring str; std::wstring str;
std::size_t lines = impl_->textbase.lines(); std::size_t lines = impl_->textbase.lines();
if(lines > 0) if (lines > 0)
{ {
str = impl_->textbase.getline(0); str = impl_->textbase.getline(0);
for(std::size_t i = 1; i < lines; ++i) for (std::size_t i = 1; i < lines; ++i)
{ {
str += L"\n\r"; str += L"\n\r";
str += impl_->textbase.getline(i); str += impl_->textbase.getline(i);
@ -1773,11 +1780,11 @@ namespace nana{ namespace widgets
reset_caret_pixels(); reset_caret_pixels();
} }
if(!attributes_.enable_caret) if (!attributes_.enable_caret)
visible = false; visible = false;
caret->visible(visible); caret->visible(visible);
if(visible) if (visible)
caret->position(coord); caret->position(coord);
//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
@ -1796,7 +1803,7 @@ namespace nana{ namespace widgets
void text_editor::move_caret_end(bool update) void text_editor::move_caret_end(bool update)
{ {
points_.caret.y = static_cast<unsigned>(impl_->textbase.lines()); points_.caret.y = static_cast<unsigned>(impl_->textbase.lines());
if(points_.caret.y) --points_.caret.y; if (points_.caret.y) --points_.caret.y;
points_.caret.x = static_cast<unsigned>(impl_->textbase.getline(points_.caret.y).size()); points_.caret.x = static_cast<unsigned>(impl_->textbase.getline(points_.caret.y).size());
if (update) if (update)
@ -1815,7 +1822,7 @@ namespace nana{ namespace widgets
void text_editor::show_caret(bool isshow) void text_editor::show_caret(bool isshow)
{ {
if(isshow == false || API::is_focus_ready(window_)) if (isshow == false || API::is_focus_ready(window_))
API::open_caret(window_, true).get()->visible(isshow); API::open_caret(window_, true).get()->visible(isshow);
} }
@ -1903,7 +1910,7 @@ namespace nana{ namespace widgets
} }
} }
if((pos.y > a.y || (pos.y == a.y && pos.x >= a.x)) && ((pos.y < b.y) || (pos.y == b.y && pos.x < b.x))) if ((pos.y > a.y || (pos.y == a.y && pos.x >= a.x)) && ((pos.y < b.y) || (pos.y == b.y && pos.x < b.x)))
return true; return true;
} }
return false; return false;
@ -1911,7 +1918,7 @@ namespace nana{ namespace widgets
bool text_editor::move_select() bool text_editor::move_select()
{ {
if(hit_select_area(points_.caret, true) || (select_.b == points_.caret)) if (hit_select_area(points_.caret, true) || (select_.b == points_.caret))
{ {
points_.caret = select_.b; points_.caret = select_.b;
@ -2010,7 +2017,7 @@ namespace nana{ namespace widgets
if (impl_->customized_renderers.background) if (impl_->customized_renderers.background)
impl_->customized_renderers.background(graph_, text_area_.area, bgcolor); impl_->customized_renderers.background(graph_, text_area_.area, bgcolor);
if(impl_->counterpart.buffer && !text_area_.area.empty()) if (impl_->counterpart.buffer && !text_area_.area.empty())
impl_->counterpart.buffer.bitblt(rectangle{ text_area_.area.dimension() }, graph_, text_area_.area.position()); impl_->counterpart.buffer.bitblt(rectangle{ text_area_.area.dimension() }, graph_, text_area_.area.position());
//Render the content when the text isn't empty or the window has got focus, //Render the content when the text isn't empty or the window has got focus,
@ -2064,9 +2071,9 @@ namespace nana{ namespace widgets
if (perform_event) if (perform_event)
textbase().text_changed(); textbase().text_changed();
if(graph_) if (graph_)
{ {
if(this->_m_adjust_view()) if (this->_m_adjust_view())
impl_->cview->sync(false); impl_->cview->sync(false);
reset_caret(); reset_caret();
@ -2078,11 +2085,11 @@ namespace nana{ namespace widgets
{ {
std::wstring ch_str(1, ch); std::wstring ch_str(1, ch);
auto undo_ptr = std::unique_ptr<undo_input_text>{new undo_input_text(*this, ch_str)}; auto undo_ptr = std::unique_ptr<undo_input_text>{ new undo_input_text(*this, ch_str) };
bool refresh = (select_.a != select_.b); bool refresh = (select_.a != select_.b);
undo_ptr->set_selected_text(); undo_ptr->set_selected_text();
if(refresh) if (refresh)
points_.caret = _m_erase_select(false); points_.caret = _m_erase_select(false);
undo_ptr->set_caret_pos(); undo_ptr->set_caret_pos();
@ -2095,7 +2102,7 @@ namespace nana{ namespace widgets
textbase().text_changed(); textbase().text_changed();
points_.caret.x ++; points_.caret.x++;
_m_reset_content_size(); _m_reset_content_size();
@ -2158,7 +2165,7 @@ namespace nana{ namespace widgets
void text_editor::enter(bool record_undo, bool perform_event) void text_editor::enter(bool record_undo, bool perform_event)
{ {
if(false == attributes_.multi_lines) if (false == attributes_.multi_lines)
return; return;
auto undo_ptr = std::unique_ptr<undo_input_text>(new undo_input_text(*this, std::wstring(1, '\n'))); auto undo_ptr = std::unique_ptr<undo_input_text>(new undo_input_text(*this, std::wstring(1, '\n')));
@ -2173,7 +2180,7 @@ namespace nana{ namespace widgets
const string_type& lnstr = textbase.getline(points_.caret.y); const string_type& lnstr = textbase.getline(points_.caret.y);
++points_.caret.y; ++points_.caret.y;
if(lnstr.size() > points_.caret.x) if (lnstr.size() > points_.caret.x)
{ {
//Breaks the line and moves the rest part to a new line //Breaks the line and moves the rest part to a new line
auto rest_part_len = lnstr.size() - points_.caret.x; //Firstly get the length of rest part, because lnstr may be invalid after insertln auto rest_part_len = lnstr.size() - points_.caret.x; //Firstly get the length of rest part, because lnstr may be invalid after insertln
@ -2228,9 +2235,9 @@ namespace nana{ namespace widgets
void text_editor::del() void text_editor::del()
{ {
if(select_.a == select_.b) if (select_.a == select_.b)
{ {
if(textbase().getline(points_.caret.y).size() > points_.caret.x) if (textbase().getline(points_.caret.y).size() > points_.caret.x)
{ {
++points_.caret.x; ++points_.caret.x;
} }
@ -2250,10 +2257,10 @@ namespace nana{ namespace widgets
{ {
auto undo_ptr = std::unique_ptr<undo_backspace>(new undo_backspace(*this)); auto undo_ptr = std::unique_ptr<undo_backspace>(new undo_backspace(*this));
bool has_to_redraw = true; bool has_to_redraw = true;
if(select_.a == select_.b) if (select_.a == select_.b)
{ {
auto & textbase = this->textbase(); auto & textbase = this->textbase();
if(points_.caret.x) if (points_.caret.x)
{ {
unsigned erase_number = 1; unsigned erase_number = 1;
--points_.caret.x; --points_.caret.x;
@ -2300,7 +2307,7 @@ namespace nana{ namespace widgets
textbase().text_changed(); textbase().text_changed();
if(has_to_redraw) if (has_to_redraw)
{ {
this->_m_adjust_view(); this->_m_adjust_view();
impl_->try_refresh = sync_graph::refresh; impl_->try_refresh = sync_graph::refresh;
@ -2335,9 +2342,9 @@ namespace nana{ namespace widgets
void text_editor::move_left() void text_editor::move_left()
{ {
bool pending = true; bool pending = true;
if(_m_cancel_select(1) == false) if (_m_cancel_select(1) == false)
{ {
if(points_.caret.x) if (points_.caret.x)
{ {
--points_.caret.x; --points_.caret.x;
pending = false; pending = false;
@ -2448,11 +2455,11 @@ namespace nana{ namespace widgets
pos.x = static_cast<decltype(pos.x)>(text_length); pos.x = static_cast<decltype(pos.x)>(text_length);
//move the caret to the end of the text if Ctrl is pressed //move the caret to the end of the text if Ctrl is pressed
if(arg.ctrl) if (arg.ctrl)
pos.y = static_cast<unsigned>((line_count - 1) * line_px); pos.y = static_cast<unsigned>((line_count - 1) * line_px);
break; break;
case keyboard::os_pageup: case keyboard::os_pageup:
if(origin.y > 0) if (origin.y > 0)
{ {
auto off = coord - origin; auto off = coord - origin;
origin.y -= (std::min)(origin.y, static_cast<int>(impl_->cview->view_area().height)); origin.y -= (std::min)(origin.y, static_cast<int>(impl_->cview->view_area().height));
@ -2920,7 +2927,7 @@ namespace nana{ namespace widgets
if ('+' == ch || '-' == ch) if ('+' == ch || '-' == ch)
return str.empty(); return str.empty();
if((accepts::real == impl_->capacities.acceptive) && ('.' == ch)) if ((accepts::real == impl_->capacities.acceptive) && ('.' == ch))
return (str.find(L'.') == str.npos); return (str.find(L'.') == str.npos);
return ('0' <= ch && ch <= '9'); return ('0' <= ch && ch <= '9');
@ -3047,7 +3054,7 @@ namespace nana{ namespace widgets
if (get_selected_points(a, b)) if (get_selected_points(a, b))
{ {
auto & textbase = this->textbase(); auto & textbase = this->textbase();
if(a.y != b.y) if (a.y != b.y)
{ {
textbase.erase(a.y, a.x, std::wstring::npos); textbase.erase(a.y, a.x, std::wstring::npos);
textbase.erase(a.y + 1, b.y - a.y - 1); textbase.erase(a.y + 1, b.y - a.y - 1);
@ -3188,7 +3195,7 @@ namespace nana{ namespace widgets
nana::size text_editor::_m_text_extent_size(const char_type* str, size_type n) const nana::size text_editor::_m_text_extent_size(const char_type* str, size_type n) const
{ {
if(mask_char_) if (mask_char_)
{ {
std::wstring maskstr; std::wstring maskstr;
maskstr.append(n, mask_char_); maskstr.append(n, mask_char_);
@ -3208,7 +3215,7 @@ namespace nana{ namespace widgets
auto const line_px = static_cast<int>(this->line_height()); auto const line_px = static_cast<int>(this->line_height());
auto coord = _m_caret_to_coordinate(points_.caret, true); 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})) if (view_area.is_hit(coord) && view_area.is_hit({ coord.x, coord.y + line_px }))
return false; return false;
unsigned extra_count_horz = 4; unsigned extra_count_horz = 4;
@ -3316,10 +3323,10 @@ namespace nana{ namespace widgets
int text_editor::_m_text_top_base() const int text_editor::_m_text_top_base() const
{ {
if(false == attributes_.multi_lines) if (false == attributes_.multi_lines)
{ {
unsigned px = line_height(); unsigned px = line_height();
if(text_area_.area.height > px) if (text_area_.area.height > px)
return text_area_.area.y + static_cast<int>((text_area_.area.height - px) >> 1); return text_area_.area.y + static_cast<int>((text_area_.area.height - px) >> 1);
} }
return text_area_.area.y; return text_area_.area.y;
@ -3439,11 +3446,11 @@ namespace nana{ namespace widgets
class text_editor::helper_pencil class text_editor::helper_pencil
{ {
public: public:
helper_pencil(paint::graphics& graph, const text_editor& editor, keyword_parser& parser): helper_pencil(paint::graphics& graph, const text_editor& editor, keyword_parser& parser) :
graph_( graph ), graph_(graph),
editor_( editor ), editor_(editor),
parser_( parser ), parser_(parser),
line_px_( editor.line_height() ) line_px_(editor.line_height())
{} {}
color selection_color(bool fgcolor, bool focused) const color selection_color(bool fgcolor, bool focused) const
@ -3464,7 +3471,7 @@ namespace nana{ namespace widgets
#else #else
graph_.palette(true, selection_color(true, has_focused)); graph_.palette(true, selection_color(true, has_focused));
graph_.rectangle(::nana::rectangle{ text_pos, { text_px, line_px_ } }, true, graph_.rectangle(::nana::rectangle{ text_pos,{ text_px, line_px_ } }, true,
selection_color(false, has_focused)); selection_color(false, has_focused));
graph_.string(text_pos, text, len); graph_.string(text_pos, text, len);
@ -3575,7 +3582,7 @@ namespace nana{ namespace widgets
bool extra_space = false; bool extra_space = false;
//Create a flag for indicating whether the whole line is selected //Create a flag for indicating whether the whole line is selected
const bool text_selected = (sbegin == text_ptr && send == text_ptr+ text_len); const bool text_selected = (sbegin == text_ptr && send == text_ptr + text_len);
//The text is not selected or the whole line text is selected //The text is not selected or the whole line text is selected
if ((!sbegin || !send) || text_selected) if ((!sbegin || !send) || text_selected)
@ -3690,7 +3697,7 @@ namespace nana{ namespace widgets
#else #else
auto whitespace_w = graph_.text_extent_size(L" ", 1).width; auto whitespace_w = graph_.text_extent_size(L" ", 1).width;
#endif #endif
graph_.rectangle(::nana::rectangle{ text_draw_pos, { whitespace_w, line_h_pixels } }, true); graph_.rectangle(::nana::rectangle{ text_draw_pos,{ whitespace_w, line_h_pixels } }, true);
} }
} }
} }
@ -3782,7 +3789,7 @@ namespace nana{ namespace widgets
//Characters of some bidi languages may transform in a word. //Characters of some bidi languages may transform in a word.
//RTL //RTL
#ifdef _nana_std_has_string_view #ifdef _nana_std_has_string_view
auto pxbuf = graph_.glyph_pixels({ent.begin, len}); auto pxbuf = graph_.glyph_pixels({ ent.begin, len });
#else #else
std::unique_ptr<unsigned[]> pxbuf(new unsigned[len]); std::unique_ptr<unsigned[]> pxbuf(new unsigned[len]);
graph_.glyph_pixels(ent.begin, len, pxbuf.get()); graph_.glyph_pixels(ent.begin, len, pxbuf.get());
@ -3800,5 +3807,5 @@ namespace nana{ namespace widgets
//end class text_editor //end class text_editor
}//end namespace skeletons }//end namespace skeletons
}//end namespace widgets }//end namespace widgets
}//end namespace nana }//end namespace nana