new features of listbox

text line number and line indent
This commit is contained in:
Jinhao 2015-10-21 01:39:11 +08:00
parent c6f2f28f81
commit 5590dd293b
9 changed files with 232 additions and 20 deletions

View File

@ -14,7 +14,7 @@
#define NANA_GUI_WIDGETS_COMBOX_HPP #define NANA_GUI_WIDGETS_COMBOX_HPP
#include "widget.hpp" #include "widget.hpp"
#include "float_listbox.hpp" #include "float_listbox.hpp"
#include "skeletons/text_editor_scheme.hpp" #include "skeletons/text_editor_part.hpp"
#include <nana/key_type.hpp> #include <nana/key_type.hpp>
#include <nana/concepts.hpp> #include <nana/concepts.hpp>
#include <functional> #include <functional>

View File

@ -14,7 +14,7 @@
#ifndef NANA_GUI_SKELETONS_TEXT_EDITOR_HPP #ifndef NANA_GUI_SKELETONS_TEXT_EDITOR_HPP
#define NANA_GUI_SKELETONS_TEXT_EDITOR_HPP #define NANA_GUI_SKELETONS_TEXT_EDITOR_HPP
#include "textbase.hpp" #include "textbase.hpp"
#include "text_editor_scheme.hpp" #include "text_editor_part.hpp"
#include <nana/gui/widgets/scroll.hpp> #include <nana/gui/widgets/scroll.hpp>
#include <nana/unicode_bidi.hpp> #include <nana/unicode_bidi.hpp>
@ -135,6 +135,8 @@ namespace nana{ namespace widgets
using size_type = textbase<char_type>::size_type; using size_type = textbase<char_type>::size_type;
using string_type = textbase<char_type>::string_type; using string_type = textbase<char_type>::string_type;
using event_interface = text_editor_event_interface;
using graph_reference = ::nana::paint::graphics&; using graph_reference = ::nana::paint::graphics&;
struct ext_renderer_tag struct ext_renderer_tag
@ -162,6 +164,9 @@ namespace nana{ namespace widgets
void typeface_changed(); void typeface_changed();
void indent(bool, std::function<nana::string()> generator);
void set_event(event_interface*);
/// Determine whether the text_editor is line wrapped. /// Determine whether the text_editor is line wrapped.
bool line_wrapped() const; bool line_wrapped() const;
/// Set the text_editor whether it is line wrapped, it returns false if the state is not changed. /// Set the text_editor whether it is line wrapped, it returns false if the state is not changed.
@ -174,6 +179,10 @@ namespace nana{ namespace widgets
/// Sets the text area. /// Sets the text area.
/// @return true if the area is changed with the new value. /// @return true if the area is changed with the new value.
bool text_area(const nana::rectangle&); bool text_area(const nana::rectangle&);
/// Returns the text area
rectangle text_area(bool including_scroll) const;
bool tip_string(nana::string&&); bool tip_string(nana::string&&);
const attributes & attr() const; const attributes & attr() const;
@ -215,8 +224,13 @@ namespace nana{ namespace widgets
/// Returns width of text area excluding the vscroll size. /// Returns width of text area excluding the vscroll size.
unsigned width_pixels() const; unsigned width_pixels() const;
window window_handle() const; window window_handle() const;
/// Returns text position of each line that currently displays on screen
const std::vector<upoint>& text_position() const;
void set_text_position_changed(std::function<void(const std::vector<upoint>&)>);
public: public:
void draw_scroll_rectangle(); void draw_corner();
void render(bool focused); void render(bool focused);
public: public:
void put(nana::string); void put(nana::string);
@ -242,6 +256,8 @@ namespace nana{ namespace widgets
skeletons::textbase<nana::char_t>& textbase(); skeletons::textbase<nana::char_t>& textbase();
const skeletons::textbase<nana::char_t>& textbase() const; const skeletons::textbase<nana::char_t>& textbase() const;
std::vector<unsigned> get_lines() const;
private: private:
bool _m_accepts(char_type) const; bool _m_accepts(char_type) const;
::nana::color _m_bgcolor() const; ::nana::color _m_bgcolor() const;
@ -295,6 +311,7 @@ namespace nana{ namespace widgets
nana::window window_; nana::window window_;
graph_reference graph_; graph_reference graph_;
const text_editor_scheme* scheme_; const text_editor_scheme* scheme_;
event_interface * event_handler_{ nullptr };
std::unique_ptr<keywords> keywords_; std::unique_ptr<keywords> keywords_;
skeletons::textbase<nana::char_t> textbase_; skeletons::textbase<nana::char_t> textbase_;
@ -302,6 +319,15 @@ namespace nana{ namespace widgets
mutable ext_renderer_tag ext_renderer_; mutable ext_renderer_tag ext_renderer_;
std::vector<upoint> text_position_; //position of text from last rendering.
std::function<void(const std::vector<upoint>&)> text_position_function_;
struct indent_rep
{
bool enabled{ false };
std::function<nana::string()> generator;
}indent_;
struct attributes struct attributes
{ {
accepts acceptive{ accepts::no_restrict }; accepts acceptive{ accepts::no_restrict };

View File

@ -3,6 +3,7 @@
#define NANA_WIDGETS_SKELETONS_TEXT_EDITOR_SCHEME_HPP #define NANA_WIDGETS_SKELETONS_TEXT_EDITOR_SCHEME_HPP
#include "../../detail/widget_colors.hpp" #include "../../detail/widget_colors.hpp"
#include <vector>
namespace nana namespace nana
{ {
@ -16,6 +17,14 @@ namespace nana
color_proxy selection{static_cast<color_rgb>(0x3399FF)}; color_proxy selection{static_cast<color_rgb>(0x3399FF)};
color_proxy selection_text{colors::white}; color_proxy selection_text{colors::white};
}; };
class text_editor_event_interface
{
public:
virtual ~text_editor_event_interface() = default;
virtual void text_position_changed(const std::vector<upoint>&) = 0;
};
} }
} }
} }

View File

@ -13,7 +13,7 @@
#ifndef NANA_GUI_WIDGET_SPINBOX_HPP #ifndef NANA_GUI_WIDGET_SPINBOX_HPP
#define NANA_GUI_WIDGET_SPINBOX_HPP #define NANA_GUI_WIDGET_SPINBOX_HPP
#include "widget.hpp" #include "widget.hpp"
#include "skeletons/text_editor_scheme.hpp" #include "skeletons/text_editor_part.hpp"
namespace nana namespace nana
{ {

View File

@ -13,7 +13,7 @@
#define NANA_GUI_WIDGET_TEXTBOX_HPP #define NANA_GUI_WIDGET_TEXTBOX_HPP
#include <nana/gui/widgets/widget.hpp> #include <nana/gui/widgets/widget.hpp>
#include "skeletons/textbase_export_interface.hpp" #include "skeletons/textbase_export_interface.hpp"
#include "skeletons/text_editor_scheme.hpp" #include "skeletons/text_editor_part.hpp"
namespace nana namespace nana
{ {
@ -23,10 +23,20 @@ namespace nana
: public event_arg : public event_arg
{ {
textbox& widget; textbox& widget;
const std::vector<upoint>* text_position;
arg_textbox(textbox&); arg_textbox(textbox&);
}; };
struct arg_textbox_text_position
: public event_arg
{
textbox& widget;
const std::vector<upoint>& text_position;
arg_textbox_text_position(textbox&, const std::vector<upoint>&);
};
namespace widgets namespace widgets
{ {
namespace skeletons namespace skeletons
@ -44,15 +54,30 @@ namespace nana
{ {
basic_event<arg_textbox> first_change; basic_event<arg_textbox> first_change;
basic_event<arg_textbox> text_changed; basic_event<arg_textbox> text_changed;
basic_event<arg_textbox_text_position> text_position_changed;
};
class event_interface
{
public:
virtual ~event_interface() = default;
virtual void text_position_changed(const std::vector<upoint>&) = 0;
}; };
class event_agent class event_agent
: public widgets::skeletons::textbase_event_agent_interface : public widgets::skeletons::textbase_event_agent_interface,
public widgets::skeletons::text_editor_event_interface
{ {
public: public:
event_agent(::nana::textbox&); event_agent(::nana::textbox&);
private:
//Overrides textbase_event_agent_interface
void first_change() override; void first_change() override;
void text_changed() override; void text_changed() override;
private:
//Overrides text_editor_event_interface
void text_position_changed(const std::vector<upoint>&) override;
private: private:
::nana::textbox & widget_; ::nana::textbox & widget_;
}; };
@ -128,8 +153,12 @@ namespace nana
void store(nana::string file); void store(nana::string file);
void store(nana::string file, nana::unicode encoding); void store(nana::string file, nana::unicode encoding);
/// Enables/disables the textbox to indent a line. Idents a new line when it is created by pressing enter.
/// @param generator generates text for identing a line. If it is empty, textbox indents the line according to last line.
void enable_indent(bool, std::function<nana::string()> generator = {});
//A workaround for reset, explicit default constructor syntax, because VC2013 incorrectly treats {} as {0}. //A workaround for reset, explicit default constructor syntax, because VC2013 incorrectly treats {} as {0}.
textbox& reset(nana::string = nana::string()); ///< discard the old text and set a newtext textbox& reset(nana::string = nana::string()); ///< discard the old text and set a new text
/// The file of last store operation. /// The file of last store operation.
nana::string filename() const; nana::string filename() const;
@ -188,6 +217,16 @@ namespace nana
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<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 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); void erase_keyword(const nana::string& kw);
/// Returns the text position of each line that currently displays on screen.
std::vector<upoint> text_position() const;
/// Returns the rectangle of text area
rectangle text_area() const;
/// Returns the height of line in pixels
unsigned line_pixels() const;
protected: protected:
//Overrides widget's virtual functions //Overrides widget's virtual functions
::nana::string _m_caption() const throw() override; ::nana::string _m_caption() const throw() override;

View File

@ -20,6 +20,7 @@
#include "widgets/form.hpp" #include "widgets/form.hpp"
#include "drawing.hpp" #include "drawing.hpp"
#include "msgbox.hpp" #include "msgbox.hpp"
#include "place.hpp"
namespace nana namespace nana
{ {

View File

@ -252,7 +252,7 @@ namespace nana{ namespace widgets
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 void update_line(std::size_t textline, std::size_t secondary_before) = 0; virtual void update_line(std::size_t textline, std::size_t secondary_before) = 0;
virtual void render(const ::nana::color& fgcolor) = 0; virtual std::vector<::nana::upoint> render(const ::nana::color& fgcolor) = 0;
virtual nana::point caret_to_screen(upoint) = 0; virtual nana::point caret_to_screen(upoint) = 0;
virtual nana::upoint screen_to_caret(point scrpos) = 0; virtual nana::upoint screen_to_caret(point scrpos) = 0;
virtual bool move_caret_ns(bool to_north) = 0; virtual bool move_caret_ns(bool to_north) = 0;
@ -290,8 +290,10 @@ namespace nana{ namespace widgets
editor_._m_draw_string(top, API::fgcolor(editor_.window_), nana::upoint(0, editor_.points_.caret.y), editor_.textbase_.getline(textline), true); editor_._m_draw_string(top, API::fgcolor(editor_.window_), nana::upoint(0, editor_.points_.caret.y), editor_.textbase_.getline(textline), true);
} }
void render(const ::nana::color& fgcolor) override std::vector<upoint> render(const ::nana::color& fgcolor) override
{ {
std::vector<upoint> line_index;
::nana::upoint str_pos(0, static_cast<unsigned>(editor_.points_.offset.y)); ::nana::upoint str_pos(0, static_cast<unsigned>(editor_.points_.offset.y));
std::size_t scrlines = editor_.screen_lines() + str_pos.y; std::size_t scrlines = editor_.screen_lines() + str_pos.y;
@ -304,9 +306,12 @@ namespace nana{ namespace widgets
while( str_pos.y < scrlines) while( str_pos.y < scrlines)
{ {
editor_._m_draw_string(top, fgcolor, str_pos, editor_.textbase_.getline(str_pos.y), true); editor_._m_draw_string(top, fgcolor, str_pos, editor_.textbase_.getline(str_pos.y), true);
line_index.push_back(str_pos);
++str_pos.y; ++str_pos.y;
top += pixels; top += pixels;
} }
return line_index;
} }
nana::point caret_to_screen(nana::upoint pos) override nana::point caret_to_screen(nana::upoint pos) override
@ -692,12 +697,14 @@ namespace nana{ namespace widgets
editor_.render(API::is_focus_ready(editor_.window_)); editor_.render(API::is_focus_ready(editor_.window_));
} }
void render(const ::nana::color& fgcolor) override std::vector<upoint> render(const ::nana::color& fgcolor) override
{ {
std::vector<upoint> line_index;
std::size_t secondary; std::size_t secondary;
auto primary = _m_textline_from_screen(0, secondary); auto primary = _m_textline_from_screen(0, secondary);
if (primary >= linemtr_.size() || secondary >= linemtr_[primary].line_sections.size()) if (primary >= linemtr_.size() || secondary >= linemtr_[primary].line_sections.size())
return; return line_index;
nana::upoint str_pos(0, static_cast<unsigned>(primary)); nana::upoint str_pos(0, static_cast<unsigned>(primary));
str_pos.x = static_cast<unsigned>(linemtr_[primary].line_sections[secondary].begin - editor_.textbase_.getline(primary).data()); str_pos.x = static_cast<unsigned>(linemtr_[primary].line_sections[secondary].begin - editor_.textbase_.getline(primary).data());
@ -715,6 +722,7 @@ namespace nana{ namespace widgets
nana::string text(section.begin, section.end); nana::string text(section.begin, section.end);
editor_._m_draw_string(top, fgcolor, str_pos, text, true); editor_._m_draw_string(top, fgcolor, str_pos, text, true);
line_index.push_back(str_pos);
++secondary; ++secondary;
if (secondary >= mtr.line_sections.size()) if (secondary >= mtr.line_sections.size())
{ {
@ -729,6 +737,8 @@ namespace nana{ namespace widgets
else else
break; break;
} }
return line_index;
} }
nana::point caret_to_screen(upoint pos) override nana::point caret_to_screen(upoint pos) override
@ -1436,6 +1446,17 @@ namespace nana{ namespace widgets
behavior_->pre_calc_lines(width_pixels()); behavior_->pre_calc_lines(width_pixels());
} }
void text_editor::indent(bool enb, std::function<nana::string()> generator)
{
indent_.enabled = enb;
indent_.generator.swap(generator);
}
void text_editor::set_event(event_interface* ptr)
{
event_handler_ = ptr;
}
bool text_editor::line_wrapped() const bool text_editor::line_wrapped() const
{ {
return attributes_.line_wrapped; return attributes_.line_wrapped;
@ -1500,6 +1521,18 @@ namespace nana{ namespace widgets
return true; return true;
} }
rectangle text_editor::text_area(bool including_scroll) const
{
if (including_scroll)
return text_area_.area;
auto r = text_area_.area;
r.width = text_area_.area.width > text_area_.vscroll ? text_area_.area.width - text_area_.vscroll : 0;
r.height = text_area_.area.height > text_area_.hscroll ? text_area_.area.height - text_area_.hscroll : 0;
return r;
}
bool text_editor::tip_string(nana::string&& str) bool text_editor::tip_string(nana::string&& str)
{ {
if(attributes_.tip_string == str) if(attributes_.tip_string == str)
@ -1873,7 +1906,17 @@ namespace nana{ namespace widgets
return window_; return window_;
} }
void text_editor::draw_scroll_rectangle() const std::vector<upoint>& text_editor::text_position() const
{
return text_position_;
}
void text_editor::set_text_position_changed(std::function<void(const std::vector<upoint>&)> fn)
{
text_position_function_.swap(fn);
}
void text_editor::draw_corner()
{ {
if(text_area_.vscroll && text_area_.hscroll) if(text_area_.vscroll && text_area_.hscroll)
{ {
@ -1906,11 +1949,27 @@ namespace nana{ namespace widgets
//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,
//otherwise draw the tip string. //otherwise draw the tip string.
if ((false == textbase_.empty()) || has_focus) if ((false == textbase_.empty()) || has_focus)
behavior_->render(fgcolor); {
else //Draw tip string auto && text_pos = behavior_->render(fgcolor);
graph_.string({ text_area_.area.x - points_.offset.x, text_area_.area.y }, attributes_.tip_string, { 0x78, 0x78, 0x78 });
draw_scroll_rectangle();
if (text_pos.empty())
text_pos.push_back({ 0, 0 });
if (text_pos != text_position_)
{
text_position_.swap(text_pos);
if (event_handler_)
event_handler_->text_position_changed(text_position_);
}
}
else //Draw tip string
graph_.string({ text_area_.area.x - points_.offset.x, text_area_.area.y }, attributes_.tip_string, static_cast<color_rgb>(0x787878));
if (text_position_.empty())
text_position_.push_back({ 0, 0 });
draw_corner();
text_area_.border_renderer(graph_, bgcolor); text_area_.border_renderer(graph_, bgcolor);
} }
@ -1961,7 +2020,7 @@ namespace nana{ namespace widgets
if (refresh || _m_update_caret_line(secondary_before)) if (refresh || _m_update_caret_line(secondary_before))
render(true); render(true);
else else
draw_scroll_rectangle(); draw_corner();
_m_scrollbar(); _m_scrollbar();
@ -2049,6 +2108,29 @@ namespace nana{ namespace widgets
need_refresh = true; need_refresh = true;
} }
if (indent_.enabled)
{
nana::string indent_text;
if (indent_.generator)
{
indent_text = indent_.generator();
}
else
{
auto & text = textbase_.getline(points_.caret.y - 1);
auto indent_pos = text.find_first_not_of(L"\t ");
if (indent_pos != std::wstring::npos)
indent_text = text.substr(0, indent_pos);
else
indent_text = text;
}
if (indent_text.size())
put(indent_text);
}
if (behavior_->adjust_caret_into_screen() || need_refresh) if (behavior_->adjust_caret_into_screen() || need_refresh)
render(true); render(true);
@ -2108,7 +2190,7 @@ namespace nana{ namespace widgets
if(_m_move_offset_x_while_over_border(-2) == false) if(_m_move_offset_x_while_over_border(-2) == false)
{ {
behavior_->update_line(points_.caret.y, secondary); behavior_->update_line(points_.caret.y, secondary);
draw_scroll_rectangle(); draw_corner();
has_to_redraw = false; has_to_redraw = false;
} }
} }

View File

@ -410,7 +410,7 @@ namespace nana
{ {
auto spins_r = _m_spins_area(); auto spins_r = _m_spins_area();
if (spins_r.x == 0) if (spins_r.x == 0)
editor_->text_area({}); editor_->text_area(rectangle{});
else else
editor_->text_area({ 2, 2, graph_->width() - spins_r.width - 2, spins_r.height - 2 }); editor_->text_area({ 2, 2, graph_->width() - spins_r.width - 2, spins_r.height - 2 });
} }

View File

@ -24,6 +24,12 @@ namespace nana
: widget(wdg) : widget(wdg)
{} {}
arg_textbox_text_position::arg_textbox_text_position(textbox& wdg, const std::vector<upoint>& text_pos)
: widget(wdg),
text_position(text_pos)
{
}
namespace drawerbase { namespace drawerbase {
namespace textbox namespace textbox
{ {
@ -41,6 +47,12 @@ namespace drawerbase {
{ {
widget_.events().text_changed.emit(::nana::arg_textbox{ widget_ }); widget_.events().text_changed.emit(::nana::arg_textbox{ widget_ });
} }
void event_agent::text_position_changed(const std::vector<upoint>& text_pos)
{
::nana::arg_textbox_text_position arg(widget_, text_pos);
widget_.events().text_position_changed.emit(arg);
}
//end class event_agent //end class event_agent
//class draweer //class draweer
@ -69,6 +81,7 @@ namespace drawerbase {
editor_ = new text_editor(wd, graph, dynamic_cast<::nana::widgets::skeletons::text_editor_scheme*>(scheme)); editor_ = new text_editor(wd, graph, dynamic_cast<::nana::widgets::skeletons::text_editor_scheme*>(scheme));
editor_->textbase().set_event_agent(evt_agent_.get()); editor_->textbase().set_event_agent(evt_agent_.get());
editor_->set_event(evt_agent_.get());
_m_text_area(graph.width(), graph.height()); _m_text_area(graph.width(), graph.height());
@ -240,6 +253,16 @@ namespace drawerbase {
editor->textbase().store(std::move(file), encoding); editor->textbase().store(std::move(file), encoding);
} }
/// Enables/disables the textbox to indent a line. Idents a new line when it is created by pressing enter.
/// @param generator generates text for identing a line. If it is empty, textbox indents the line according to last line.
void textbox::enable_indent(bool enb, std::function<nana::string()> generator)
{
internal_scope_guard lock;
auto editor = get_drawer_trigger().editor();
if (editor)
editor->indent(enb, generator);
}
textbox& textbox::reset(nana::string str) textbox& textbox::reset(nana::string str)
{ {
internal_scope_guard lock; internal_scope_guard lock;
@ -495,6 +518,7 @@ namespace drawerbase {
void textbox::set_highlight(const std::string& name, const ::nana::color& fgcolor, const ::nana::color& bgcolor) void textbox::set_highlight(const std::string& name, const ::nana::color& fgcolor, const ::nana::color& bgcolor)
{ {
internal_scope_guard lock;
auto editor = get_drawer_trigger().editor(); auto editor = get_drawer_trigger().editor();
if (editor) if (editor)
editor->set_highlight(name, fgcolor, bgcolor); editor->set_highlight(name, fgcolor, bgcolor);
@ -502,6 +526,7 @@ namespace drawerbase {
void textbox::erase_highlight(const std::string& name) void textbox::erase_highlight(const std::string& name)
{ {
internal_scope_guard lock;
auto editor = get_drawer_trigger().editor(); auto editor = get_drawer_trigger().editor();
if (editor) if (editor)
editor->erase_highlight(name); editor->erase_highlight(name);
@ -509,6 +534,7 @@ namespace drawerbase {
void textbox::set_keywords(const std::string& name, bool case_sensitive, bool whole_word_match, std::initializer_list<nana::string> kw_list) void textbox::set_keywords(const std::string& name, bool case_sensitive, bool whole_word_match, std::initializer_list<nana::string> kw_list)
{ {
internal_scope_guard lock;
auto editor = get_drawer_trigger().editor(); auto editor = get_drawer_trigger().editor();
if (editor) if (editor)
{ {
@ -519,6 +545,7 @@ namespace drawerbase {
void textbox::set_keywords(const std::string& name, bool case_sensitive, bool whole_word_match, std::initializer_list<std::string> kw_list_utf8) void textbox::set_keywords(const std::string& name, bool case_sensitive, bool whole_word_match, std::initializer_list<std::string> kw_list_utf8)
{ {
internal_scope_guard lock;
auto editor = get_drawer_trigger().editor(); auto editor = get_drawer_trigger().editor();
if (editor) if (editor)
{ {
@ -529,11 +556,39 @@ namespace drawerbase {
void textbox::erase_keyword(const nana::string& kw) void textbox::erase_keyword(const nana::string& kw)
{ {
internal_scope_guard lock;
auto editor = get_drawer_trigger().editor(); auto editor = get_drawer_trigger().editor();
if (editor) if (editor)
editor->erase_keyword(kw); editor->erase_keyword(kw);
} }
std::vector<upoint> textbox::text_position() const
{
internal_scope_guard lock;
auto editor = get_drawer_trigger().editor();
if (editor)
return editor->text_position();
return{};
}
rectangle textbox::text_area() const
{
internal_scope_guard lock;
auto editor = get_drawer_trigger().editor();
if (editor)
return editor->text_area(false);
return{};
}
unsigned textbox::line_pixels() const
{
internal_scope_guard lock;
auto editor = get_drawer_trigger().editor();
return (editor ? editor->line_height() : 0);
}
//Override _m_caption for caption() //Override _m_caption for caption()
nana::string textbox::_m_caption() const throw() nana::string textbox::_m_caption() const throw()
{ {