Added highlighted text support for textbox

init commit for highlighted text support
This commit is contained in:
cnjinhao 2015-01-07 07:33:09 +08:00
parent 039d63cf1f
commit 0f24250972
5 changed files with 337 additions and 3 deletions

View File

@ -14,6 +14,7 @@
#define NANA_BASIC_TYPES_HPP
#include <nana/deploy.hpp>
#include <cctype>
namespace nana
{

View File

@ -127,6 +127,9 @@ namespace nana{ namespace widgets
class undo_backspace;
class undo_input_text;
class undo_move_text;
struct keywords;
class keyword_parser;
public:
typedef ::nana::char_t char_type;
typedef textbase<char_type>::size_type size_type;
@ -142,6 +145,11 @@ namespace nana{ namespace widgets
text_editor(window, graph_reference, const text_editor_scheme*);
~text_editor();
void set_highlight(const std::string& name, const ::nana::color&, const ::nana::color&);
void erase_highlight(const std::string& name);
void set_keyword(const ::nana::string& kw, const std::string& name, bool case_sensitive, bool whole_word_matched);
void erase_keyword(const ::nana::string& kw);
bool respone_keyboard(nana::char_t, bool enterable);
void typeface_changed();
@ -260,6 +268,7 @@ namespace nana{ namespace widgets
void _m_draw_tip_string() const;
void _m_draw_parse_string(const keyword_parser&, bool rtl, ::nana::point pos, const ::nana::color& fgcolor, const ::nana::char_t*, std::size_t len) const;
//_m_draw_string
//@brief: Draw a line of string
void _m_draw_string(int top, const ::nana::color&, const nana::upoint& str_pos, const nana::string&, bool if_mask) const;
@ -280,6 +289,7 @@ namespace nana{ namespace widgets
nana::window window_;
graph_reference graph_;
const text_editor_scheme* scheme_;
std::unique_ptr<keywords> keywords_;
skeletons::textbase<nana::char_t> textbase_;
nana::char_t mask_char_{0};

View File

@ -182,6 +182,12 @@ namespace nana
double to_double() const;
textbox& from(int);
textbox& from(double);
void set_highlight(const std::string& name, const ::nana::color& fgcolor, const ::nana::color& bgcolor);
void erase_highlight(const std::string& name);
void set_keywords(const std::string& name, bool case_sensitive, bool whole_word_match, std::initializer_list<nana::string> kw_list);
void set_keywords(const std::string& name, bool case_sensitive, bool whole_word_match, std::initializer_list<std::string> kw_list_utf8);
void erase_keyword(const nana::string& kw);
protected:
//Overrides widget's virtual functions
::nana::string _m_caption() const override;

View File

@ -15,6 +15,8 @@
#include <nana/system/dataexch.hpp>
#include <nana/unicode_bidi.hpp>
#include <numeric>
#include <cwctype>
#include <set>
namespace nana{ namespace widgets
{
@ -1139,10 +1141,167 @@ namespace nana{ namespace widgets
std::vector<line_metrics> linemtr_;
}; //end class behavior_linewrapped
struct keyword_scheme
{
::nana::color fgcolor;
::nana::color bgcolor;
};
struct keyword_desc
{
::nana::string text;
std::string scheme;
bool case_sensitive;
bool whole_word_matched;
keyword_desc(const ::nana::string& txt, const std::string& schm, bool cs, bool wwm)
: text(txt), scheme(schm), case_sensitive(cs), whole_word_matched(wwm)
{}
};
struct text_editor::keywords
{
std::map<std::string, std::shared_ptr<keyword_scheme>> schemes;
std::deque<keyword_desc> kwbase;
};
struct entity
{
const ::nana::char_t* begin;
const ::nana::char_t* end;
const keyword_scheme * scheme;
};
class text_editor::keyword_parser
{
public:
void parse(const ::nana::string& text, const keywords* kwptr)
{
if (text.empty())
return;
std::vector<entity> entities;
auto test_whole_word = [&text](std::size_t pos, std::size_t len)
{
if (pos)
{
auto chr = text[pos - 1];
if ((std::iswalpha(chr) && !std::isspace(chr)) || chr == '_')
return false;
}
if (pos + len < text.size())
{
auto chr = text[pos + len];
auto is = std::iswalpha(chr);
is = std::isspace(chr);
if ((std::iswalpha(chr) && !std::isspace(chr)) || chr == '_')
return false;
}
return true;
};
auto test_overlapped = [&entities](const ::nana::char_t* begin, const ::nana::char_t* end)
{
for (auto & en : entities)
{
if ((begin <= en.begin && en.begin < end) || (begin <= en.end && en.end < end))
return true;
}
return false;
};
::nana::cistring cistr;
for (auto & ds : kwptr->kwbase)
{
std::size_t pos;
const ::nana::char_t* begin;
const ::nana::char_t* end;
if (ds.case_sensitive)
{
pos = text.find(ds.text);
if (pos == text.npos)
continue;
if (ds.whole_word_matched)
{
if (!test_whole_word(pos, ds.text.size()))
continue;
}
begin = text.data() + pos;
end = begin + ds.text.size();
}
else
{
if (cistr.empty())
cistr.append(text.data(), text.size());
pos = cistr.find(ds.text.data());
if (pos == cistr.npos)
continue;
if (ds.whole_word_matched)
{
if (!test_whole_word(pos, ds.text.size()))
continue;
}
begin = text.data() + pos;
end = begin + ds.text.size();
}
auto ki = kwptr->schemes.find(ds.scheme);
if (ki != kwptr->schemes.end() && ki->second)
{
schemes_.emplace(ds.scheme, ki->second);
entities.emplace_back();
auto & last = entities.back();
last.begin = begin;
last.end = end;
last.scheme = ki->second.get();
}
}
if (!entities.empty())
{
std::sort(entities.begin(), entities.end(), [](const entity& a, const entity& b)
{
return (a.begin < b.begin);
});
auto previous = entities.begin();
auto i = previous + 1;
while(i != entities.end())
{
if (previous->end > i->begin)
i = entities.erase(i);
else
++i;
}
}
entities_.swap(entities);
}
const std::vector<entity>& entities() const
{
return entities_;
}
private:
std::vector<entity> entities_;
std::map<std::string, std::shared_ptr<keyword_scheme>> schemes_;
};
//class text_editor
text_editor::text_editor(window wd, graph_reference graph, const text_editor_scheme* schm)
: behavior_(new behavior_normal(*this)),
window_(wd), graph_(graph), scheme_(schm)
window_(wd), graph_(graph),
scheme_(schm), keywords_(new keywords)
{
text_area_.area = graph.size();
text_area_.captured = false;
@ -1162,6 +1321,50 @@ namespace nana{ namespace widgets
//For instance of unique_ptr pimpl idiom.
}
void text_editor::set_highlight(const std::string& name, const ::nana::color& fgcolor, const ::nana::color& bgcolor)
{
if (fgcolor.invisible() && fgcolor.invisible())
{
keywords_->schemes.erase(name);
return;
}
auto sp = std::make_shared<keyword_scheme>();
sp->fgcolor = fgcolor;
sp->bgcolor = bgcolor;
keywords_->schemes[name] = sp;
}
void text_editor::erase_highlight(const std::string& name)
{
keywords_->schemes.erase(name);
}
void text_editor::set_keyword(const ::nana::string& kw, const std::string& name, bool case_sensitive, bool whole_word_matched)
{
for (auto & ds : keywords_->kwbase)
{
if (ds.text == kw)
{
ds.scheme = name;
ds.case_sensitive = case_sensitive;
ds.whole_word_matched = whole_word_matched;
return;
}
}
keywords_->kwbase.emplace_back(kw, name, case_sensitive, whole_word_matched);
}
void text_editor::erase_keyword(const ::nana::string& kw)
{
auto i = std::find_if(keywords_->kwbase.begin(), keywords_->kwbase.end(), [&kw](keyword_desc& kd){
return (kd.text == kw);
});
if (i != keywords_->kwbase.end())
keywords_->kwbase.erase(i);
}
bool text_editor::respone_keyboard(nana::char_t key, bool enterable) //key is a character of ASCII code
{
switch (key)
@ -2498,6 +2701,74 @@ namespace nana{ namespace widgets
graph_.string({ text_area_.area.x - points_.offset.x, text_area_.area.y }, attributes_.tip_string, {0x78, 0x78, 0x78});
}
void text_editor::_m_draw_parse_string(const keyword_parser& parser, bool rtl, ::nana::point pos, const ::nana::color& fgcolor, const ::nana::char_t* str, std::size_t len) const
{
graph_.string(pos, str, len);
if (parser.entities().empty())
return;
std::unique_ptr<unsigned[]> glyph_px(new unsigned[len]);
graph_.glyph_pixels(str, len, glyph_px.get());
auto glyphs = glyph_px.get();
auto px_h = line_height();
auto px_w = std::accumulate(glyphs, glyphs + len, unsigned{});
::nana::paint::graphics canvas;
canvas.make(px_w, px_h);
canvas.typeface(graph_.typeface());
::nana::point canvas_text_pos;
auto ent_pos = pos;
const auto str_end = str + len;
auto & entities = parser.entities();
for (auto & ent : entities)
{
const ::nana::char_t* ent_begin = nullptr;
int ent_off = 0;
if (str <= ent.begin && ent.begin < str_end)
{
ent_begin = ent.begin;
ent_off = std::accumulate(glyphs, glyphs + (ent.begin - str), 0);
}
else if (ent.begin <= str && str < ent.end)
ent_begin = str;
if (ent_begin)
{
auto ent_end = (ent.end < str_end ? ent.end : str_end);
auto ent_pixels = std::accumulate(glyphs + (ent_begin - str), glyphs + (ent_end - str), unsigned{});
if (ent.scheme->bgcolor.invisible())
canvas.set_color(_m_bgcolor());
else
canvas.set_color(ent.scheme->bgcolor);
canvas.rectangle(true);
if (ent.scheme->fgcolor.invisible())
canvas.set_text_color(fgcolor);
else
canvas.set_text_color(ent.scheme->fgcolor);
ent_pos.x += ent_off;
if (rtl)
{
//draw the whole text if it is a RTL text, because Arbic language is transformable.
canvas.string({}, str, len);
graph_.bitblt({ ent_pos, ::nana::size{ ent_pixels, canvas.height() } }, canvas, ::nana::point{ ent_off, 0 });
}
else
{
canvas.string({}, ent_begin, ent_end - ent_begin);
graph_.bitblt({ ent_pos, ::nana::size{ ent_pixels, canvas.height() } }, canvas);
}
}
}
}
void text_editor::_m_draw_string(int top, const ::nana::color& clr, const nana::upoint& str_pos, const nana::string& linestr, bool if_mask) const
{
::nana::point text_pos{ text_area_.area.x - points_.offset.x, top };
@ -2515,6 +2786,10 @@ namespace nana{ namespace widgets
std::vector<unicode_bidi::entity> reordered;
bidi.linestr(linestr.c_str(), linestr.size(), reordered);
//Parse highlight keywords
keyword_parser parser;
parser.parse(linestr, keywords_.get());
auto whitespace_w = graph_.text_extent_size(STR(" "), 1).width;
const auto line_h_pixels = line_height();
@ -2540,10 +2815,11 @@ namespace nana{ namespace widgets
{
graph_.set_text_color(scheme_->selection_text.get_color());
graph_.rectangle({ text_pos, { str_w, line_h_pixels } }, true);
}
graph_.string(text_pos, ent.begin, len);
}
else
_m_draw_parse_string(parser, _m_is_right_text(ent), text_pos, clr, ent.begin, len);
}
text_pos.x += static_cast<int>(str_w);
}
if (selected)

View File

@ -485,6 +485,47 @@ namespace nana{ namespace drawerbase {
return *this;
}
void textbox::set_highlight(const std::string& name, const ::nana::color& fgcolor, const ::nana::color& bgcolor)
{
auto editor = get_drawer_trigger().editor();
if (editor)
editor->set_highlight(name, fgcolor, bgcolor);
}
void textbox::erase_highlight(const std::string& name)
{
auto editor = get_drawer_trigger().editor();
if (editor)
editor->erase_highlight(name);
}
void textbox::set_keywords(const std::string& name, bool case_sensitive, bool whole_word_match, std::initializer_list<nana::string> kw_list)
{
auto editor = get_drawer_trigger().editor();
if (editor)
{
for (auto & kw : kw_list)
editor->set_keyword(kw, name, case_sensitive, whole_word_match);
}
}
void textbox::set_keywords(const std::string& name, bool case_sensitive, bool whole_word_match, std::initializer_list<std::string> kw_list_utf8)
{
auto editor = get_drawer_trigger().editor();
if (editor)
{
for (auto & kw : kw_list_utf8)
editor->set_keyword(::nana::charset(kw, ::nana::unicode::utf8), name, case_sensitive, whole_word_match);
}
}
void textbox::erase_keyword(const nana::string& kw)
{
auto editor = get_drawer_trigger().editor();
if (editor)
editor->erase_keyword(kw);
}
//Override _m_caption for caption()
nana::string textbox::_m_caption() const
{