diff --git a/include/nana/gui/widgets/combox.hpp b/include/nana/gui/widgets/combox.hpp index 3d1698d0..b7e9286e 100644 --- a/include/nana/gui/widgets/combox.hpp +++ b/include/nana/gui/widgets/combox.hpp @@ -14,7 +14,7 @@ #define NANA_GUI_WIDGETS_COMBOX_HPP #include "widget.hpp" #include "float_listbox.hpp" -#include "skeletons/text_editor_scheme.hpp" +#include "skeletons/text_editor_part.hpp" #include #include #include diff --git a/include/nana/gui/widgets/skeletons/text_editor.hpp b/include/nana/gui/widgets/skeletons/text_editor.hpp index 41812395..6f4c8fb9 100644 --- a/include/nana/gui/widgets/skeletons/text_editor.hpp +++ b/include/nana/gui/widgets/skeletons/text_editor.hpp @@ -14,7 +14,7 @@ #ifndef NANA_GUI_SKELETONS_TEXT_EDITOR_HPP #define NANA_GUI_SKELETONS_TEXT_EDITOR_HPP #include "textbase.hpp" -#include "text_editor_scheme.hpp" +#include "text_editor_part.hpp" #include #include @@ -135,6 +135,8 @@ namespace nana{ namespace widgets using size_type = textbase::size_type; using string_type = textbase::string_type; + using event_interface = text_editor_event_interface; + using graph_reference = ::nana::paint::graphics&; struct ext_renderer_tag @@ -162,6 +164,9 @@ namespace nana{ namespace widgets void typeface_changed(); + void indent(bool, std::function generator); + void set_event(event_interface*); + /// Determine whether the text_editor is line wrapped. bool line_wrapped() const; /// 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. /// @return true if the area is changed with the new value. bool text_area(const nana::rectangle&); + + /// Returns the text area + rectangle text_area(bool including_scroll) const; + bool tip_string(nana::string&&); const attributes & attr() const; @@ -215,8 +224,13 @@ namespace nana{ namespace widgets /// Returns width of text area excluding the vscroll size. unsigned width_pixels() const; window window_handle() const; + + /// Returns text position of each line that currently displays on screen + const std::vector& text_position() const; + + void set_text_position_changed(std::function&)>); public: - void draw_scroll_rectangle(); + void draw_corner(); void render(bool focused); public: void put(nana::string); @@ -242,6 +256,8 @@ namespace nana{ namespace widgets skeletons::textbase& textbase(); const skeletons::textbase& textbase() const; + + std::vector get_lines() const; private: bool _m_accepts(char_type) const; ::nana::color _m_bgcolor() const; @@ -294,7 +310,8 @@ namespace nana{ namespace widgets undoable undo_; nana::window window_; graph_reference graph_; - const text_editor_scheme* scheme_; + const text_editor_scheme* scheme_; + event_interface * event_handler_{ nullptr }; std::unique_ptr keywords_; skeletons::textbase textbase_; @@ -302,6 +319,15 @@ namespace nana{ namespace widgets mutable ext_renderer_tag ext_renderer_; + std::vector text_position_; //position of text from last rendering. + std::function&)> text_position_function_; + + struct indent_rep + { + bool enabled{ false }; + std::function generator; + }indent_; + struct attributes { accepts acceptive{ accepts::no_restrict }; diff --git a/include/nana/gui/widgets/skeletons/text_editor_scheme.hpp b/include/nana/gui/widgets/skeletons/text_editor_part.hpp similarity index 66% rename from include/nana/gui/widgets/skeletons/text_editor_scheme.hpp rename to include/nana/gui/widgets/skeletons/text_editor_part.hpp index 00806b3b..9a213844 100644 --- a/include/nana/gui/widgets/skeletons/text_editor_scheme.hpp +++ b/include/nana/gui/widgets/skeletons/text_editor_part.hpp @@ -3,6 +3,7 @@ #define NANA_WIDGETS_SKELETONS_TEXT_EDITOR_SCHEME_HPP #include "../../detail/widget_colors.hpp" +#include namespace nana { @@ -16,6 +17,14 @@ namespace nana color_proxy selection{static_cast(0x3399FF)}; 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&) = 0; + }; } } } diff --git a/include/nana/gui/widgets/spinbox.hpp b/include/nana/gui/widgets/spinbox.hpp index 5e283872..dc081018 100644 --- a/include/nana/gui/widgets/spinbox.hpp +++ b/include/nana/gui/widgets/spinbox.hpp @@ -13,7 +13,7 @@ #ifndef NANA_GUI_WIDGET_SPINBOX_HPP #define NANA_GUI_WIDGET_SPINBOX_HPP #include "widget.hpp" -#include "skeletons/text_editor_scheme.hpp" +#include "skeletons/text_editor_part.hpp" namespace nana { diff --git a/include/nana/gui/widgets/textbox.hpp b/include/nana/gui/widgets/textbox.hpp index f8759e05..bca29008 100644 --- a/include/nana/gui/widgets/textbox.hpp +++ b/include/nana/gui/widgets/textbox.hpp @@ -13,7 +13,7 @@ #define NANA_GUI_WIDGET_TEXTBOX_HPP #include #include "skeletons/textbase_export_interface.hpp" -#include "skeletons/text_editor_scheme.hpp" +#include "skeletons/text_editor_part.hpp" namespace nana { @@ -23,10 +23,20 @@ namespace nana : public event_arg { textbox& widget; + const std::vector* text_position; arg_textbox(textbox&); }; + struct arg_textbox_text_position + : public event_arg + { + textbox& widget; + const std::vector& text_position; + + arg_textbox_text_position(textbox&, const std::vector&); + }; + namespace widgets { namespace skeletons @@ -44,15 +54,30 @@ namespace nana { basic_event first_change; basic_event text_changed; + basic_event text_position_changed; + }; + + class event_interface + { + public: + virtual ~event_interface() = default; + + virtual void text_position_changed(const std::vector&) = 0; }; 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: event_agent(::nana::textbox&); + private: + //Overrides textbase_event_agent_interface void first_change() override; void text_changed() override; + private: + //Overrides text_editor_event_interface + void text_position_changed(const std::vector&) override; private: ::nana::textbox & widget_; }; @@ -128,8 +153,12 @@ namespace nana void store(nana::string file); 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 generator = {}); + //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. 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 kw_list); void set_keywords(const std::string& name, bool case_sensitive, bool whole_word_match, std::initializer_list kw_list_utf8); void erase_keyword(const nana::string& kw); + + + /// Returns the text position of each line that currently displays on screen. + std::vector 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: //Overrides widget's virtual functions ::nana::string _m_caption() const throw() override; diff --git a/include/nana/gui/wvl.hpp b/include/nana/gui/wvl.hpp index 4959e852..1f104bbd 100644 --- a/include/nana/gui/wvl.hpp +++ b/include/nana/gui/wvl.hpp @@ -20,6 +20,7 @@ #include "widgets/form.hpp" #include "drawing.hpp" #include "msgbox.hpp" +#include "place.hpp" namespace nana { diff --git a/source/gui/widgets/skeletons/text_editor.cpp b/source/gui/widgets/skeletons/text_editor.cpp index ff44e593..72c4fe22 100644 --- a/source/gui/widgets/skeletons/text_editor.cpp +++ b/source/gui/widgets/skeletons/text_editor.cpp @@ -252,7 +252,7 @@ namespace nana{ namespace widgets 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 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::upoint screen_to_caret(point scrpos) = 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); } - void render(const ::nana::color& fgcolor) override + std::vector render(const ::nana::color& fgcolor) override { + std::vector line_index; + ::nana::upoint str_pos(0, static_cast(editor_.points_.offset.y)); std::size_t scrlines = editor_.screen_lines() + str_pos.y; @@ -304,9 +306,12 @@ namespace nana{ namespace widgets while( str_pos.y < scrlines) { editor_._m_draw_string(top, fgcolor, str_pos, editor_.textbase_.getline(str_pos.y), true); + line_index.push_back(str_pos); ++str_pos.y; top += pixels; } + + return line_index; } 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_)); } - void render(const ::nana::color& fgcolor) override + std::vector render(const ::nana::color& fgcolor) override { + std::vector line_index; + std::size_t secondary; auto primary = _m_textline_from_screen(0, secondary); if (primary >= linemtr_.size() || secondary >= linemtr_[primary].line_sections.size()) - return; + return line_index; nana::upoint str_pos(0, static_cast(primary)); str_pos.x = static_cast(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); editor_._m_draw_string(top, fgcolor, str_pos, text, true); + line_index.push_back(str_pos); ++secondary; if (secondary >= mtr.line_sections.size()) { @@ -729,6 +737,8 @@ namespace nana{ namespace widgets else break; } + + return line_index; } nana::point caret_to_screen(upoint pos) override @@ -1436,6 +1446,17 @@ namespace nana{ namespace widgets behavior_->pre_calc_lines(width_pixels()); } + void text_editor::indent(bool enb, std::function 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 { return attributes_.line_wrapped; @@ -1500,6 +1521,18 @@ namespace nana{ namespace widgets 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) { if(attributes_.tip_string == str) @@ -1873,7 +1906,17 @@ namespace nana{ namespace widgets return window_; } - void text_editor::draw_scroll_rectangle() + const std::vector& text_editor::text_position() const + { + return text_position_; + } + + void text_editor::set_text_position_changed(std::function&)> fn) + { + text_position_function_.swap(fn); + } + + void text_editor::draw_corner() { 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, //otherwise draw the tip string. if ((false == textbase_.empty()) || has_focus) - behavior_->render(fgcolor); - else //Draw tip string - graph_.string({ text_area_.area.x - points_.offset.x, text_area_.area.y }, attributes_.tip_string, { 0x78, 0x78, 0x78 }); + { + auto && text_pos = behavior_->render(fgcolor); + - 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(0x787878)); + + if (text_position_.empty()) + text_position_.push_back({ 0, 0 }); + + draw_corner(); text_area_.border_renderer(graph_, bgcolor); } @@ -1961,7 +2020,7 @@ namespace nana{ namespace widgets if (refresh || _m_update_caret_line(secondary_before)) render(true); else - draw_scroll_rectangle(); + draw_corner(); _m_scrollbar(); @@ -2049,6 +2108,29 @@ namespace nana{ namespace widgets 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) render(true); @@ -2108,7 +2190,7 @@ namespace nana{ namespace widgets if(_m_move_offset_x_while_over_border(-2) == false) { behavior_->update_line(points_.caret.y, secondary); - draw_scroll_rectangle(); + draw_corner(); has_to_redraw = false; } } diff --git a/source/gui/widgets/spinbox.cpp b/source/gui/widgets/spinbox.cpp index e75b0031..a5fddd26 100644 --- a/source/gui/widgets/spinbox.cpp +++ b/source/gui/widgets/spinbox.cpp @@ -410,7 +410,7 @@ namespace nana { auto spins_r = _m_spins_area(); if (spins_r.x == 0) - editor_->text_area({}); + editor_->text_area(rectangle{}); else editor_->text_area({ 2, 2, graph_->width() - spins_r.width - 2, spins_r.height - 2 }); } diff --git a/source/gui/widgets/textbox.cpp b/source/gui/widgets/textbox.cpp index 59e51d57..40dd5c3a 100644 --- a/source/gui/widgets/textbox.cpp +++ b/source/gui/widgets/textbox.cpp @@ -24,6 +24,12 @@ namespace nana : widget(wdg) {} + arg_textbox_text_position::arg_textbox_text_position(textbox& wdg, const std::vector& text_pos) + : widget(wdg), + text_position(text_pos) + { + } + namespace drawerbase { namespace textbox { @@ -41,6 +47,12 @@ namespace drawerbase { { widget_.events().text_changed.emit(::nana::arg_textbox{ widget_ }); } + + void event_agent::text_position_changed(const std::vector& text_pos) + { + ::nana::arg_textbox_text_position arg(widget_, text_pos); + widget_.events().text_position_changed.emit(arg); + } //end class event_agent //class draweer @@ -69,6 +81,7 @@ namespace drawerbase { editor_ = new text_editor(wd, graph, dynamic_cast<::nana::widgets::skeletons::text_editor_scheme*>(scheme)); editor_->textbase().set_event_agent(evt_agent_.get()); + editor_->set_event(evt_agent_.get()); _m_text_area(graph.width(), graph.height()); @@ -240,6 +253,16 @@ namespace drawerbase { 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 generator) + { + internal_scope_guard lock; + auto editor = get_drawer_trigger().editor(); + if (editor) + editor->indent(enb, generator); + } + textbox& textbox::reset(nana::string str) { 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) { + internal_scope_guard lock; auto editor = get_drawer_trigger().editor(); if (editor) editor->set_highlight(name, fgcolor, bgcolor); @@ -502,6 +526,7 @@ namespace drawerbase { void textbox::erase_highlight(const std::string& name) { + internal_scope_guard lock; auto editor = get_drawer_trigger().editor(); if (editor) 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 kw_list) { + internal_scope_guard lock; auto editor = get_drawer_trigger().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 kw_list_utf8) { + internal_scope_guard lock; auto editor = get_drawer_trigger().editor(); if (editor) { @@ -529,11 +556,39 @@ namespace drawerbase { void textbox::erase_keyword(const nana::string& kw) { + internal_scope_guard lock; auto editor = get_drawer_trigger().editor(); if (editor) editor->erase_keyword(kw); } + std::vector 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() nana::string textbox::_m_caption() const throw() {