diff --git a/source/gui/widgets/skeletons/text_editor.cpp b/source/gui/widgets/skeletons/text_editor.cpp index d7a9fd4a..304ed929 100644 --- a/source/gui/widgets/skeletons/text_editor.cpp +++ b/source/gui/widgets/skeletons/text_editor.cpp @@ -1,15 +1,15 @@ /* - * A text editor implementation - * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2018 Jinhao(cnjinhao@hotmail.com) - * - * Distributed under the Boost Software License, Version 1.0. - * (See accompanying file LICENSE_1_0.txt or copy at - * http://www.boost.org/LICENSE_1_0.txt) - * - * @file: nana/gui/widgets/skeletons/text_editor.cpp - * @contributors: Ariel Vina-Rodriguez, Oleg Smolsky - */ +* A text editor implementation +* Nana C++ Library(http://www.nanapro.org) +* Copyright(C) 2003-2018 Jinhao(cnjinhao@hotmail.com) +* +* Distributed under the Boost Software License, Version 1.0. +* (See accompanying file LICENSE_1_0.txt or copy at +* http://www.boost.org/LICENSE_1_0.txt) +* +* @file: nana/gui/widgets/skeletons/text_editor.cpp +* @contributors: Ariel Vina-Rodriguez, Oleg Smolsky +*/ #include #include #include @@ -25,637 +25,613 @@ #include #include -namespace nana{ namespace widgets -{ - namespace skeletons +namespace nana { + namespace widgets { - - template - class undoable_command_interface + namespace skeletons { - public: - virtual ~undoable_command_interface() = default; - virtual EnumCommand get() const = 0; - virtual bool merge(const undoable_command_interface&) = 0; - virtual void execute(bool redo) = 0; - }; - - template - class undoable - { - public: - using command = EnumCommand; - using container = std::deque>>; - - void clear() + template + class undoable_command_interface { - commands_.clear(); - pos_ = 0; - } + public: + virtual ~undoable_command_interface() = default; - void max_steps(std::size_t maxs) + virtual EnumCommand get() const = 0; + virtual bool merge(const undoable_command_interface&) = 0; + virtual void execute(bool redo) = 0; + }; + + template + class undoable { - max_steps_ = maxs; - if (maxs && (commands_.size() >= maxs)) - commands_.erase(commands_.begin(), commands_.begin() + (commands_.size() - maxs + 1)); - } + public: + using command = EnumCommand; + using container = std::deque>>; - std::size_t max_steps() const - { - return max_steps_; - } - - void enable(bool enb) - { - enabled_ = enb; - if (!enb) - clear(); - } - - bool enabled() const - { - return enabled_; - } - - void push(std::unique_ptr> && ptr) - { - if (!ptr || !enabled_) - return; - - if (pos_ < commands_.size()) - commands_.erase(commands_.begin() + pos_, commands_.end()); - else if (max_steps_ && (commands_.size() >= max_steps_)) - commands_.erase(commands_.begin(), commands_.begin() + (commands_.size() - max_steps_ + 1)); - - pos_ = commands_.size(); - if (!commands_.empty()) + void clear() { - if (commands_.back().get()->merge(*ptr)) + commands_.clear(); + pos_ = 0; + } + + void max_steps(std::size_t maxs) + { + max_steps_ = maxs; + if (maxs && (commands_.size() >= maxs)) + commands_.erase(commands_.begin(), commands_.begin() + (commands_.size() - maxs + 1)); + } + + std::size_t max_steps() const + { + return max_steps_; + } + + void enable(bool enb) + { + enabled_ = enb; + if (!enb) + clear(); + } + + bool enabled() const + { + return enabled_; + } + + void push(std::unique_ptr> && ptr) + { + if (!ptr || !enabled_) return; + + if (pos_ < commands_.size()) + commands_.erase(commands_.begin() + pos_, commands_.end()); + else if (max_steps_ && (commands_.size() >= max_steps_)) + commands_.erase(commands_.begin(), commands_.begin() + (commands_.size() - max_steps_ + 1)); + + pos_ = commands_.size(); + if (!commands_.empty()) + { + if (commands_.back().get()->merge(*ptr)) + return; + } + + commands_.emplace_back(std::move(ptr)); + ++pos_; } - commands_.emplace_back(std::move(ptr)); - ++pos_; - } - - std::size_t count(bool is_undo) const - { - return (is_undo ? pos_ : commands_.size() - pos_); - } - - void undo() - { - if (pos_ > 0) + std::size_t count(bool is_undo) const { - --pos_; - commands_[pos_].get()->execute(false); + return (is_undo ? pos_ : commands_.size() - pos_); } - } - void redo() - { - if (pos_ != commands_.size()) - commands_[pos_++].get()->execute(true); - } - - private: - container commands_; - bool enabled_{ true }; - std::size_t max_steps_{ 30 }; - std::size_t pos_{ 0 }; - }; - - template - using undo_command_ptr = std::unique_ptr > ; - - template - class text_editor::basic_undoable - : public undoable_command_interface - { - public: - basic_undoable(text_editor& te, EnumCommand cmd) - : editor_(te), cmd_(cmd) - {} - - void set_selected_text() - { - //sel_a_ and sel_b_ are not sorted, sel_b_ keeps the caret position. - sel_a_ = editor_.select_.a; - sel_b_ = editor_.select_.b; - - if (sel_a_ != sel_b_) + void undo() { - selected_text_ = editor_._m_make_select_string(); + if (pos_ > 0) + { + --pos_; + commands_[pos_].get()->execute(false); + } } - } - void set_caret_pos() - { - pos_ = editor_.caret(); - } - protected: - EnumCommand get() const override - { - return cmd_; - } - - virtual bool merge(const undoable_command_interface&) override - { - return false; - } - protected: - text_editor & editor_; - upoint pos_; - upoint sel_a_, sel_b_; - std::wstring selected_text_; - private: - const EnumCommand cmd_; - }; - - class text_editor::undo_backspace - : public basic_undoable < command > - { - public: - undo_backspace(text_editor& editor) - : basic_undoable(editor, command::backspace) - { - } - - void set_removed(std::wstring str) - { - selected_text_ = std::move(str); - } - - void execute(bool redo) override - { - editor_._m_cancel_select(0); - editor_.points_.caret = pos_; - - bool is_enter = ((selected_text_.size() == 1) && ('\n' == selected_text_[0])); - if (redo) + void redo() { + if (pos_ != commands_.size()) + commands_[pos_++].get()->execute(true); + } + + private: + container commands_; + bool enabled_{ true }; + std::size_t max_steps_{ 30 }; + std::size_t pos_{ 0 }; + }; + + template + using undo_command_ptr = std::unique_ptr >; + + template + class text_editor::basic_undoable + : public undoable_command_interface + { + public: + basic_undoable(text_editor& te, EnumCommand cmd) + : editor_(te), cmd_(cmd) + {} + + void set_selected_text() + { + //sel_a_ and sel_b_ are not sorted, sel_b_ keeps the caret position. + sel_a_ = editor_.select_.a; + sel_b_ = editor_.select_.b; + if (sel_a_ != sel_b_) { - editor_.select_.a = sel_a_; - editor_.select_.b = sel_b_; - editor_._m_erase_select(false); - editor_.select_.a = editor_.select_.b; - editor_.points_.caret = sel_a_; + selected_text_ = editor_._m_make_select_string(); + } + } + + void set_caret_pos() + { + pos_ = editor_.caret(); + } + protected: + EnumCommand get() const override + { + return cmd_; + } + + virtual bool merge(const undoable_command_interface&) override + { + return false; + } + protected: + text_editor & editor_; + upoint pos_; + upoint sel_a_, sel_b_; + std::wstring selected_text_; + private: + const EnumCommand cmd_; + }; + + class text_editor::undo_backspace + : public basic_undoable < command > + { + public: + undo_backspace(text_editor& editor) + : basic_undoable(editor, command::backspace) + { + } + + void set_removed(std::wstring str) + { + selected_text_ = std::move(str); + } + + void execute(bool redo) override + { + editor_._m_cancel_select(0); + editor_.points_.caret = pos_; + + bool is_enter = ((selected_text_.size() == 1) && ('\n' == selected_text_[0])); + if (redo) + { + if (sel_a_ != sel_b_) + { + editor_.select_.a = sel_a_; + editor_.select_.b = sel_b_; + editor_._m_erase_select(false); + editor_.select_.a = editor_.select_.b; + editor_.points_.caret = sel_a_; + } + else + { + if (is_enter) + { + editor_.points_.caret = nana::upoint(0, pos_.y + 1); + editor_.backspace(false, false); + } + else + editor_.textbase().erase(pos_.y, pos_.x, selected_text_.size()); + } } else { if (is_enter) { - editor_.points_.caret = nana::upoint(0, pos_.y + 1); - editor_.backspace(false, false); + editor_.enter(false, false); } else - editor_.textbase().erase(pos_.y, pos_.x, selected_text_.size()); - } - } - else - { - if (is_enter) - { - editor_.enter(false, false); - } - else - { - editor_._m_put(selected_text_, false); - if (sel_a_ != sel_b_) { - editor_.select_.a = sel_a_; - editor_.select_.b = sel_b_; - editor_.points_.caret = sel_b_; + editor_._m_put(selected_text_, false); + if (sel_a_ != sel_b_) + { + editor_.select_.a = sel_a_; + editor_.select_.b = sel_b_; + editor_.points_.caret = sel_b_; + } + else + ++editor_.points_.caret.x; } - else - ++editor_.points_.caret.x; - } - } - - editor_.textbase().text_changed(); - - editor_.reset_caret(); - } - }; - - class text_editor::undo_input_text - : public basic_undoable - { - public: - undo_input_text(text_editor & editor, const std::wstring& text) - : basic_undoable(editor, command::input_text), - text_(text) - { - } - - void execute(bool redo) override - { - bool is_enter = (text_.size() == 1 && '\n' == text_[0]); - editor_._m_cancel_select(0); - editor_.points_.caret = pos_; //The pos_ specifies the caret position before input - - if (redo) - { - if (is_enter) - { - editor_.enter(false, false); - } - else - { - if (!selected_text_.empty()) - { - editor_.select_.a = sel_a_; - editor_.select_.b = sel_b_; - editor_._m_erase_select(false); - } - editor_.points_.caret = editor_._m_put(text_, false); //redo - } - } - else - { - if (is_enter) - { - editor_.points_.caret.x = 0; - ++editor_.points_.caret.y; - editor_.backspace(false, false); - } - else - { - std::vector> lines; - if (editor_._m_resolve_text(text_, lines)) - { - editor_.select_.a = pos_; - editor_.select_.b = upoint(static_cast(lines.back().second - lines.back().first), static_cast(pos_.y + lines.size() - 1)); - editor_.backspace(false, false); - editor_.select_.a = editor_.select_.b; - } - else - editor_.textbase().erase(pos_.y, pos_.x, text_.size()); //undo } - if (!selected_text_.empty()) - { - editor_.points_.caret = (std::min)(sel_a_, sel_b_); - editor_._m_put(selected_text_, false); - editor_.points_.caret = sel_b_; - editor_.select_.a = sel_a_; //Reset the selected text - editor_.select_.b = sel_b_; - } - } + editor_.textbase().text_changed(); - editor_.textbase().text_changed(); - editor_.reset_caret(); - } - private: - std::wstring text_; - }; - - class text_editor::undo_move_text - : public basic_undoable - { - public: - undo_move_text(text_editor& editor) - : basic_undoable(editor, command::move_text) - {} - - void execute(bool redo) override - { - if (redo) - { - editor_.select_.a = sel_a_; - editor_.select_.b = sel_b_; - editor_.points_.caret = pos_; - editor_._m_move_select(false); - } - else - { - editor_.select_.a = dest_a_; - editor_.select_.b = dest_b_; - editor_.points_.caret = (sel_a_ < sel_b_ ? sel_a_ : sel_b_); - - const auto text = editor_._m_make_select_string(); - - editor_._m_erase_select(false); - editor_._m_put(text, false); - - editor_.select_.a = sel_a_; - editor_.select_.b = sel_b_; - - editor_.points_.caret = sel_b_; editor_.reset_caret(); } - editor_.textbase().text_changed(); - } + }; - void set_destination(const nana::upoint& dest_a, const nana::upoint& dest_b) + class text_editor::undo_input_text + : public basic_undoable { - dest_a_ = dest_a; - dest_b_ = dest_b; - } - private: - nana::upoint dest_a_, dest_b_; - }; - - struct text_editor::text_section - { - const wchar_t* begin{ nullptr }; - const wchar_t* end{ nullptr }; - unsigned pixels{ 0 }; - - text_section() = default; - text_section(const wchar_t* ptr, const wchar_t* endptr, unsigned px) - : begin(ptr), end(endptr), pixels(px) - {} - }; - - - struct keyword_scheme - { - ::nana::color fgcolor; - ::nana::color bgcolor; - }; - - struct keyword_desc - { - std::wstring text; - std::string scheme; - bool case_sensitive; - bool whole_word_matched; - - keyword_desc(const std::wstring& txt, const std::string& schm, bool cs, bool wwm) - : text(txt), scheme(schm), case_sensitive(cs), whole_word_matched(wwm) - {} - }; - - struct entity - { - const wchar_t* begin; - const wchar_t* end; - const keyword_scheme * scheme; - }; - - enum class sync_graph - { - none, - refresh, - lazy_refresh - }; - - colored_area_access_interface::~colored_area_access_interface(){} - - class colored_area_access - : public colored_area_access_interface - { - public: - void set_window(window handle) - { - window_handle_ = handle; - } - - std::shared_ptr find(std::size_t pos) const - { - for (auto & sp : colored_areas_) + public: + undo_input_text(text_editor & editor, const std::wstring& text) + : basic_undoable(editor, command::input_text), + text_(text) { - if (sp->begin <= pos && pos < sp->begin + sp->count) - return sp; - else if (sp->begin > pos) - break; - } - return{}; - } - public: - //Overrides methods of colored_area_access_interface - std::shared_ptr get(std::size_t line_pos) override - { -#ifdef _MSC_VER - auto i = colored_areas_.cbegin(); - for (; i != colored_areas_.cend(); ++i) -#else - auto i = colored_areas_.begin(); - for (; i != colored_areas_.end(); ++i) -#endif - { - auto & area = *(i->get()); - if (area.begin <= line_pos && line_pos < area.begin + area.count) - return *i; - - if (area.begin > line_pos) - break; } - return *colored_areas_.emplace(i, - std::make_shared(colored_area_type{line_pos, 1, color{}, color{}}) - ); - } - - bool clear() override - { - if (colored_areas_.empty()) - return false; - - colored_areas_.clear(); - API::refresh_window(window_handle_); - return true; - } - - bool remove(std::size_t pos) override - { - bool changed = false; -#ifdef _MSC_VER - for (auto i = colored_areas_.cbegin(); i != colored_areas_.cend();) -#else - for (auto i = colored_areas_.begin(); i != colored_areas_.end();) -#endif + void execute(bool redo) override { - if (i->get()->begin <= pos && pos < i->get()->begin + i->get()->count) + bool is_enter = (text_.size() == 1 && '\n' == text_[0]); + editor_._m_cancel_select(0); + editor_.points_.caret = pos_; //The pos_ specifies the caret position before input + + if (redo) { - i = colored_areas_.erase(i); - changed = true; + if (is_enter) + { + editor_.enter(false, false); + } + else + { + if (!selected_text_.empty()) + { + editor_.select_.a = sel_a_; + editor_.select_.b = sel_b_; + editor_._m_erase_select(false); + } + editor_.points_.caret = editor_._m_put(text_, false); //redo + } } - else if (i->get()->begin > pos) - break; + else + { + if (is_enter) + { + editor_.points_.caret.x = 0; + ++editor_.points_.caret.y; + editor_.backspace(false, false); + } + else + { + std::vector> lines; + if (editor_._m_resolve_text(text_, lines)) + { + editor_.select_.a = pos_; + editor_.select_.b = upoint(static_cast(lines.back().second - lines.back().first), static_cast(pos_.y + lines.size() - 1)); + editor_.backspace(false, false); + editor_.select_.a = editor_.select_.b; + } + else + editor_.textbase().erase(pos_.y, pos_.x, text_.size()); //undo + } + + if (!selected_text_.empty()) + { + editor_.points_.caret = (std::min)(sel_a_, sel_b_); + editor_._m_put(selected_text_, false); + editor_.points_.caret = sel_b_; + editor_.select_.a = sel_a_; //Reset the selected text + editor_.select_.b = sel_b_; + } + } + + editor_.textbase().text_changed(); + editor_.reset_caret(); } - if (changed) - API::refresh_window(window_handle_); + private: + std::wstring text_; + }; - return changed; - } - - std::size_t size() const override + class text_editor::undo_move_text + : public basic_undoable { - return colored_areas_.size(); - } + public: + undo_move_text(text_editor& editor) + : basic_undoable(editor, command::move_text) + {} - std::shared_ptr at(std::size_t index) override + void execute(bool redo) override + { + if (redo) + { + editor_.select_.a = sel_a_; + editor_.select_.b = sel_b_; + editor_.points_.caret = pos_; + editor_._m_move_select(false); + } + else + { + editor_.select_.a = dest_a_; + editor_.select_.b = dest_b_; + editor_.points_.caret = (sel_a_ < sel_b_ ? sel_a_ : sel_b_); + + const auto text = editor_._m_make_select_string(); + + editor_._m_erase_select(false); + editor_._m_put(text, false); + + editor_.select_.a = sel_a_; + editor_.select_.b = sel_b_; + + editor_.points_.caret = sel_b_; + editor_.reset_caret(); + } + editor_.textbase().text_changed(); + } + + void set_destination(const nana::upoint& dest_a, const nana::upoint& dest_b) + { + dest_a_ = dest_a; + dest_b_ = dest_b; + } + private: + nana::upoint dest_a_, dest_b_; + }; + + struct text_editor::text_section { - return colored_areas_.at(index); - } - private: - window window_handle_; - std::vector> colored_areas_; - }; + const wchar_t* begin{ nullptr }; + const wchar_t* end{ nullptr }; + unsigned pixels{ 0 }; - struct text_editor::implementation - { - undoable undo; //undo command - renderers customized_renderers; - std::vector text_position; //positions of text since last rendering. - int text_position_origin{ -1 }; //origin when last text_exposed + text_section() = default; + text_section(const wchar_t* ptr, const wchar_t* endptr, unsigned px) + : begin(ptr), end(endptr), pixels(px) + {} + }; - skeletons::textbase textbase; - sync_graph try_refresh{ sync_graph::none }; - - colored_area_access colored_area; - - struct inner_capacities + struct keyword_scheme { - editor_behavior_interface * behavior; + ::nana::color fgcolor; + ::nana::color bgcolor; + }; - accepts acceptive{ accepts::no_restrict }; - std::function pred_acceptive; - }capacities; - - struct inner_counterpart + struct keyword_desc { - bool enabled{ false }; - paint::graphics buffer; //A offscreen buffer which keeps the background that painted by external part. - }counterpart; + std::wstring text; + std::string scheme; + bool case_sensitive; + bool whole_word_matched; - struct indent_rep + keyword_desc(const std::wstring& txt, const std::string& schm, bool cs, bool wwm) + : text(txt), scheme(schm), case_sensitive(cs), whole_word_matched(wwm) + {} + }; + + struct entity { - bool enabled{ false }; - std::function generator; - }indent; + const wchar_t* begin; + const wchar_t* end; + const keyword_scheme * scheme; + }; - struct inner_keywords + enum class sync_graph { - std::map> schemes; - std::deque base; - }keywords; + none, + refresh, + lazy_refresh + }; - std::unique_ptr cview; - }; + colored_area_access_interface::~colored_area_access_interface() {} - - class text_editor::editor_behavior_interface - { - public: - using row_coordinate = std::pair; ///< A coordinate type for line position. first: the absolute line position of text. second: the secondary line position of a part of line. - - virtual ~editor_behavior_interface() = default; - - /// Returns the text sections of a specified line - /** - * @param pos The absolute line number. - * @return The text sections of this line. - */ - virtual std::vector line(std::size_t pos) const = 0; - virtual row_coordinate text_position_from_screen(int top) const = 0; - - virtual unsigned max_pixels() const = 0; - - /// Deletes lines between first and second, and then, second line will be merged into first line. - virtual void merge_lines(std::size_t first, std::size_t second) = 0; - //Calculates how many lines the specified line of text takes with a specified pixels of width. - virtual void add_lines(std::size_t pos, std::size_t lines) = 0; - virtual void prepare() = 0; - virtual void pre_calc_line(std::size_t line, unsigned pixels) = 0; - virtual void pre_calc_lines(unsigned pixels) = 0; - 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; - }; - - inline bool is_right_text(const unicode_bidi::entity& e) - { - return ((e.bidi_char_type != unicode_bidi::bidi_char::L) && (e.level & 1)); - } - - - class text_editor::behavior_normal - : public editor_behavior_interface - { - public: - behavior_normal(text_editor& editor) - : editor_(editor) - {} - - std::vector line(std::size_t pos) const override + class colored_area_access + : public colored_area_access_interface { - //Every line of normal behavior only has one text_section - std::vector sections; - sections.emplace_back(this->sections_[pos]); - return sections; - } + public: + void set_window(window handle) + { + window_handle_ = handle; + } - row_coordinate text_position_from_screen(int top) const override - { - const std::size_t textlines = editor_.textbase().lines(); - const auto line_px = static_cast(editor_.line_height()); - if ((0 == textlines) || (0 == line_px)) + std::shared_ptr find(std::size_t pos) const + { + for (auto & sp : colored_areas_) + { + if (sp->begin <= pos && pos < sp->begin + sp->count) + return sp; + else if (sp->begin > pos) + break; + } return{}; - - if (top < editor_.text_area_.area.y) - top = (std::max)(editor_._m_text_topline() - 1, 0); - else - top = (top - editor_.text_area_.area.y + editor_.impl_->cview->origin().y) / line_px; - - return{ (textlines <= static_cast(top) ? textlines - 1 : static_cast(top)), - 0 }; - } - - unsigned max_pixels() const override - { - unsigned px = editor_.width_pixels(); - for (auto & sct : sections_) - { - if (sct.pixels > px) - px = sct.pixels; } - return px; - } - - void merge_lines(std::size_t first, std::size_t second) override - { - if (first > second) - std::swap(first, second); - - if (second < this->sections_.size()) -#ifdef _MSC_VER - this->sections_.erase(this->sections_.cbegin() + (first + 1), this->sections_.cbegin() + second); -#else - this->sections_.erase(this->sections_.begin() + (first + 1), this->sections_.begin() + second); -#endif - pre_calc_line(first, 0); - - //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; - - auto const & const_sections = sections_; - for (auto & sct : const_sections) + public: + //Overrides methods of colored_area_access_interface + std::shared_ptr get(std::size_t line_pos) override { - auto const& text = editor_.textbase().getline(line); - if (sct.begin < text.c_str() || (text.c_str() + text.size() < sct.begin)) - pre_calc_line(line, 0); +#ifdef _MSC_VER + auto i = colored_areas_.cbegin(); + for (; i != colored_areas_.cend(); ++i) +#else + auto i = colored_areas_.begin(); + for (; i != colored_areas_.end(); ++i) +#endif + { + auto & area = *(i->get()); + if (area.begin <= line_pos && line_pos < area.begin + area.count) + return *i; - ++line; + if (area.begin > line_pos) + break; + } + + return *colored_areas_.emplace(i, + std::make_shared(colored_area_type{ line_pos, 1, color{}, color{} }) + ); } + + bool clear() override + { + if (colored_areas_.empty()) + return false; + + colored_areas_.clear(); + API::refresh_window(window_handle_); + return true; + } + + bool remove(std::size_t pos) override + { + bool changed = false; +#ifdef _MSC_VER + for (auto i = colored_areas_.cbegin(); i != colored_areas_.cend();) +#else + for (auto i = colored_areas_.begin(); i != colored_areas_.end();) +#endif + { + if (i->get()->begin <= pos && pos < i->get()->begin + i->get()->count) + { + i = colored_areas_.erase(i); + changed = true; + } + else if (i->get()->begin > pos) + break; + } + if (changed) + API::refresh_window(window_handle_); + + return changed; + } + + std::size_t size() const override + { + return colored_areas_.size(); + } + + std::shared_ptr at(std::size_t index) override + { + return colored_areas_.at(index); + } + private: + window window_handle_; + std::vector> colored_areas_; + }; + + struct text_editor::implementation + { + undoable undo; //undo command + renderers customized_renderers; + std::vector text_position; //positions of text since last rendering. + int text_position_origin{ -1 }; //origin when last text_exposed + + skeletons::textbase textbase; + + sync_graph try_refresh{ sync_graph::none }; + + colored_area_access colored_area; + + struct inner_capacities + { + editor_behavior_interface * behavior; + + accepts acceptive{ accepts::no_restrict }; + std::function pred_acceptive; + }capacities; + + struct inner_counterpart + { + bool enabled{ false }; + paint::graphics buffer; //A offscreen buffer which keeps the background that painted by external part. + }counterpart; + + struct indent_rep + { + bool enabled{ false }; + std::function generator; + }indent; + + struct inner_keywords + { + std::map> schemes; + std::deque base; + }keywords; + + std::unique_ptr cview; + }; + + + class text_editor::editor_behavior_interface + { + public: + using row_coordinate = std::pair; ///< A coordinate type for line position. first: the absolute line position of text. second: the secondary line position of a part of line. + + virtual ~editor_behavior_interface() = default; + + /// Returns the text sections of a specified line + /** + * @param pos The absolute line number. + * @return The text sections of this line. + */ + virtual std::vector line(std::size_t pos) const = 0; + virtual row_coordinate text_position_from_screen(int top) const = 0; + + virtual unsigned max_pixels() const = 0; + + /// Deletes lines between first and second, and then, second line will be merged into first line. + virtual void merge_lines(std::size_t first, std::size_t second) = 0; + //Calculates how many lines the specified line of text takes with a specified pixels of width. + virtual void add_lines(std::size_t pos, std::size_t lines) = 0; + virtual void prepare() = 0; + virtual void pre_calc_line(std::size_t line, unsigned pixels) = 0; + virtual void pre_calc_lines(unsigned pixels) = 0; + 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; + }; + + inline bool is_right_text(const unicode_bidi::entity& e) + { + return ((e.bidi_char_type != unicode_bidi::bidi_char::L) && (e.level & 1)); } - void add_lines(std::size_t pos, std::size_t line_size) override + + class text_editor::behavior_normal + : public editor_behavior_interface { - if (pos < this->sections_.size()) + public: + behavior_normal(text_editor& editor) + : editor_(editor) + {} + + std::vector line(std::size_t pos) const override { - for (std::size_t i = 0; i < line_size; ++i) + //Every line of normal behavior only has one text_section + std::vector sections; + sections.emplace_back(this->sections_[pos]); + return sections; + } + + row_coordinate text_position_from_screen(int top) const override + { + const std::size_t textlines = editor_.textbase().lines(); + const auto line_px = static_cast(editor_.line_height()); + if ((0 == textlines) || (0 == line_px)) + return{}; + + if (top < editor_.text_area_.area.y) + top = (std::max)(editor_._m_text_topline() - 1, 0); + else + top = (top - editor_.text_area_.area.y + editor_.impl_->cview->origin().y) / line_px; + + return{ (textlines <= static_cast(top) ? textlines - 1 : static_cast(top)), + 0 }; + } + + unsigned max_pixels() const override + { + unsigned px = editor_.width_pixels(); + for (auto & sct : sections_) + { + if (sct.pixels > px) + px = sct.pixels; + } + return px; + } + + void merge_lines(std::size_t first, std::size_t second) override + { + if (first > second) + std::swap(first, second); + + if (second < this->sections_.size()) #ifdef _MSC_VER - this->sections_.emplace(this->sections_.cbegin() + (pos + i)); + this->sections_.erase(this->sections_.cbegin() + (first + 1), this->sections_.cbegin() + second); #else - this->sections_.emplace(this->sections_.begin() + (pos + i)); + this->sections_.erase(this->sections_.begin() + (first + 1), this->sections_.begin() + second); #endif + pre_calc_line(first, 0); + //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; @@ -663,3142 +639,3173 @@ namespace nana{ namespace widgets auto const & const_sections = sections_; for (auto & sct : const_sections) { - if (line < pos || (pos + line_size) <= line) - { - auto const & text = editor_.textbase().getline(line); - if (sct.begin < text.c_str() || (text.c_str() + text.size() < sct.begin)) - pre_calc_line(line, 0); - } + auto const& text = editor_.textbase().getline(line); + if (sct.begin < text.c_str() || (text.c_str() + text.size() < sct.begin)) + pre_calc_line(line, 0); + ++line; } } - } - void prepare() override + void add_lines(std::size_t pos, std::size_t line_size) override + { + if (pos < this->sections_.size()) + { + for (std::size_t i = 0; i < line_size; ++i) +#ifdef _MSC_VER + this->sections_.emplace(this->sections_.cbegin() + (pos + i)); +#else + this->sections_.emplace(this->sections_.begin() + (pos + i)); +#endif + //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; + + auto const & const_sections = sections_; + for (auto & sct : const_sections) + { + if (line < pos || (pos + line_size) <= line) + { + auto const & text = editor_.textbase().getline(line); + if (sct.begin < text.c_str() || (text.c_str() + text.size() < sct.begin)) + pre_calc_line(line, 0); + } + ++line; + } + } + } + + void prepare() override + { + auto const line_count = editor_.textbase().lines(); + this->sections_.resize(line_count); + } + + void pre_calc_line(std::size_t pos, unsigned) override + { + auto const & text = editor_.textbase().getline(pos); + auto& txt_section = this->sections_[pos]; + txt_section.begin = text.c_str(); + txt_section.end = txt_section.begin + text.size(); + txt_section.pixels = editor_._m_text_extent_size(txt_section.begin, text.size()).width; + } + + void pre_calc_lines(unsigned) override + { + auto const line_count = editor_.textbase().lines(); + this->sections_.resize(line_count); + for (std::size_t i = 0; i < line_count; ++i) + pre_calc_line(i, 0); + } + + std::size_t take_lines() const override + { + return editor_.textbase().lines(); + } + + std::size_t take_lines(std::size_t) const override + { + return 1; + } + private: + text_editor& editor_; + std::vector sections_; + }; //end class behavior_normal + + + class text_editor::behavior_linewrapped + : public text_editor::editor_behavior_interface { - auto const line_count = editor_.textbase().lines(); - this->sections_.resize(line_count); - } + struct line_metrics + { + std::size_t take_lines; //The number of lines that text of this line takes. + std::vector line_sections; + }; + public: + behavior_linewrapped(text_editor& editor) + : editor_(editor) + {} - void pre_calc_line(std::size_t pos, unsigned) override - { - auto const & text = editor_.textbase().getline(pos); - auto& txt_section = this->sections_[pos]; - txt_section.begin = text.c_str(); - txt_section.end = txt_section.begin + text.size(); - txt_section.pixels = editor_._m_text_extent_size(txt_section.begin, text.size()).width; - } + std::vector line(std::size_t pos) const override + { + return linemtr_[pos].line_sections; + } - void pre_calc_lines(unsigned) override - { - auto const line_count = editor_.textbase().lines(); - this->sections_.resize(line_count); - for (std::size_t i = 0; i < line_count; ++i) - pre_calc_line(i, 0); - } + row_coordinate text_position_from_screen(int top) const override + { + row_coordinate coord; + const auto line_px = static_cast(editor_.line_height()); - std::size_t take_lines() const override - { - return editor_.textbase().lines(); - } + if ((0 == editor_.textbase().lines()) || (0 == line_px)) + return coord; - std::size_t take_lines(std::size_t) const override - { - return 1; - } - private: - text_editor& editor_; - std::vector sections_; - }; //end class behavior_normal + auto text_row = (std::max)(0, (top - editor_.text_area_.area.y + editor_.impl_->cview->origin().y) / line_px); - - class text_editor::behavior_linewrapped - : public text_editor::editor_behavior_interface - { - struct line_metrics - { - std::size_t take_lines; //The number of lines that text of this line takes. - std::vector line_sections; - }; - public: - behavior_linewrapped(text_editor& editor) - : editor_(editor) - {} - - std::vector line(std::size_t pos) const override - { - return linemtr_[pos].line_sections; - } - - row_coordinate text_position_from_screen(int top) const override - { - row_coordinate coord; - const auto line_px = static_cast(editor_.line_height()); - - if ((0 == editor_.textbase().lines()) || (0 == line_px)) + coord = _m_textline(static_cast(text_row)); + if (linemtr_.size() <= coord.first) + { + coord.first = linemtr_.size() - 1; + coord.second = linemtr_.back().line_sections.size() - 1; + } return coord; - - auto text_row = (std::max)(0, (top - editor_.text_area_.area.y + editor_.impl_->cview->origin().y) / line_px); - - coord = _m_textline(static_cast(text_row)); - if (linemtr_.size() <= coord.first) - { - coord.first = linemtr_.size() - 1; - coord.second = linemtr_.back().line_sections.size() - 1; } - return coord; - } - unsigned max_pixels() const override - { - return editor_.width_pixels(); - } - - void merge_lines(std::size_t first, std::size_t second) override - { - if (first > second) - std::swap(first, second); - - if (second < linemtr_.size()) - linemtr_.erase(linemtr_.begin() + first + 1, linemtr_.begin() + second + 1); - - auto const width_px = editor_.width_pixels(); - - pre_calc_line(first, width_px); - } - - void add_lines(std::size_t pos, std::size_t lines) override - { - if (pos < linemtr_.size()) + unsigned max_pixels() const override { - for (std::size_t i = 0; i < lines; ++i) - linemtr_.emplace(linemtr_.begin() + pos + i); + return editor_.width_pixels(); + } - //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; + void merge_lines(std::size_t first, std::size_t second) override + { + if (first > second) + std::swap(first, second); - auto const & const_linemtr = linemtr_; - for (auto & mtr : const_linemtr) + if (second < linemtr_.size()) + linemtr_.erase(linemtr_.begin() + first + 1, linemtr_.begin() + second + 1); + + auto const width_px = editor_.width_pixels(); + + pre_calc_line(first, width_px); + } + + void add_lines(std::size_t pos, std::size_t lines) override + { + if (pos < linemtr_.size()) { - if (line < pos || (pos + lines) <= line) + for (std::size_t i = 0; i < lines; ++i) + linemtr_.emplace(linemtr_.begin() + pos + i); + + //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; + + auto const & const_linemtr = linemtr_; + for (auto & mtr : const_linemtr) { - 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()); + if (line < pos || (pos + lines) <= line) + { + 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()); + } + ++line; } - ++line; } } - } - void prepare() override - { - auto const lines = editor_.textbase().lines(); - linemtr_.resize(lines); - } - - void pre_calc_line(std::size_t line, unsigned pixels) override - { - const string_type& lnstr = editor_.textbase().getline(line); - if (lnstr.empty()) + void prepare() override { - auto & mtr = linemtr_[line]; - mtr.line_sections.clear(); - - mtr.line_sections.emplace_back(lnstr.c_str(), lnstr.c_str(), unsigned{}); - mtr.take_lines = 1; - return; + auto const lines = editor_.textbase().lines(); + linemtr_.resize(lines); } - std::vector sections; - _m_text_section(lnstr, sections); - - std::vector line_sections; - - unsigned text_px = 0; - const wchar_t * secondary_begin = nullptr; - for (auto & ts : sections) + void pre_calc_line(std::size_t line, unsigned pixels) override { - if (!secondary_begin) - secondary_begin = ts.begin; - - const unsigned str_w = editor_._m_text_extent_size(ts.begin, ts.end - ts.begin).width; - - text_px += str_w; - if (text_px >= pixels) + const string_type& lnstr = editor_.textbase().getline(line); + if (lnstr.empty()) { - if (text_px != str_w) + auto & mtr = linemtr_[line]; + mtr.line_sections.clear(); + + mtr.line_sections.emplace_back(lnstr.c_str(), lnstr.c_str(), unsigned{}); + mtr.take_lines = 1; + return; + } + + std::vector sections; + _m_text_section(lnstr, sections); + + std::vector line_sections; + + unsigned text_px = 0; + const wchar_t * secondary_begin = nullptr; + for (auto & ts : sections) + { + if (!secondary_begin) + secondary_begin = ts.begin; + + const unsigned str_w = editor_._m_text_extent_size(ts.begin, ts.end - ts.begin).width; + + text_px += str_w; + if (text_px >= pixels) + { + if (text_px != str_w) + { + line_sections.emplace_back(secondary_begin, ts.begin, unsigned{ text_px - str_w }); + text_px = str_w; + secondary_begin = ts.begin; + } + + if (str_w > pixels) //Indicates the splitting of ts string + { + std::size_t len = ts.end - ts.begin; +#ifdef _nana_std_has_string_view + auto pxbuf = editor_.graph_.glyph_pixels({ ts.begin, len }); +#else + std::unique_ptr pxbuf(new unsigned[len]); + editor_.graph_.glyph_pixels(ts.begin, len, pxbuf.get()); +#endif + + auto pxptr = pxbuf.get(); + auto pxend = pxptr + len; + + secondary_begin = ts.begin; + text_px = 0; + for (auto pxi = pxptr; pxi != pxend; ++pxi) + { + text_px += *pxi; + if (text_px < pixels) + continue; + + const wchar_t * endptr = ts.begin + (pxi - pxptr) + (text_px == pixels ? 1 : 0); + line_sections.emplace_back(secondary_begin, endptr, unsigned{ text_px - (text_px == pixels ? 0 : *pxi) }); + secondary_begin = endptr; + + text_px = (text_px == pixels ? 0 : *pxi); + } + } + continue; + } + else if (text_px == pixels) { line_sections.emplace_back(secondary_begin, ts.begin, unsigned{ text_px - str_w }); - text_px = str_w; secondary_begin = ts.begin; + text_px = str_w; + } + } + + auto & mtr = linemtr_[line]; + + mtr.take_lines = line_sections.size(); + mtr.line_sections.swap(line_sections); + + if (secondary_begin) + { + mtr.line_sections.emplace_back(secondary_begin, sections.back().end, unsigned{ text_px }); + ++mtr.take_lines; + } + } + + void pre_calc_lines(unsigned pixels) override + { + auto const lines = editor_.textbase().lines(); + linemtr_.resize(lines); + + for (std::size_t i = 0; i < lines; ++i) + pre_calc_line(i, pixels); + } + + std::size_t take_lines() const override + { + std::size_t lines = 0; + for (auto & mtr : linemtr_) + lines += mtr.take_lines; + + return lines; + } + + std::size_t take_lines(std::size_t pos) const override + { + return (pos < linemtr_.size() ? linemtr_[pos].take_lines : 0); + } + private: + /// Split a text into multiple sections, a section indicates an english word or a CKJ character + void _m_text_section(const std::wstring& str, std::vector& tsec) + { + if (str.empty()) + { + tsec.emplace_back(str.c_str(), str.c_str(), unsigned{}); + return; + } + const auto end = str.c_str() + str.size(); + + const wchar_t * word = nullptr; + for (auto i = str.c_str(); i != end; ++i) + { + wchar_t const ch = *i; + + //CKJ characters and whitespace + if (' ' == ch || '\t' == ch || (0x4E00 <= ch && ch <= 0x9FCF)) + { + if (word) //Record the word. + { + tsec.emplace_back(word, i, unsigned{}); + word = nullptr; + } + + tsec.emplace_back(i, i + 1, unsigned{}); + continue; } - if (str_w > pixels) //Indicates the splitting of ts string + if (nullptr == word) + word = i; + } + + if (word) + tsec.emplace_back(word, end, unsigned{}); + } + + row_coordinate _m_textline(std::size_t scrline) const + { + row_coordinate coord; + for (auto & mtr : linemtr_) + { + if (mtr.take_lines > scrline) { - std::size_t len = ts.end - ts.begin; -#ifdef _nana_std_has_string_view - auto pxbuf = editor_.graph_.glyph_pixels({ts.begin, len}); -#else - std::unique_ptr pxbuf(new unsigned[len]); - editor_.graph_.glyph_pixels(ts.begin, len, pxbuf.get()); -#endif + coord.second = scrline; + return coord; + } + else + scrline -= mtr.take_lines; - auto pxptr = pxbuf.get(); - auto pxend = pxptr + len; + ++coord.first; + } + return coord; + } + private: + text_editor& editor_; + std::vector linemtr_; + }; //end class behavior_linewrapped - secondary_begin = ts.begin; - text_px = 0; - for (auto pxi = pxptr; pxi != pxend; ++pxi) + class text_editor::keyword_parser + { + public: + 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)) + return; + + std::wstring text{ c_str, len }; + + using index = std::wstring::size_type; + + std::vector entities; + + ::nana::ciwstring cistr; + for (auto & ds : keywords.base) + { + index pos{ 0 }; + for (index rest{ text.size() }; rest >= ds.text.size(); ++pos, rest = text.size() - pos) + { + if (ds.case_sensitive) { - text_px += *pxi; - if (text_px < pixels) - continue; + pos = text.find(ds.text, pos); + if (pos == text.npos) + break; + } + else + { + if (cistr.empty()) + cistr.append(text.c_str(), text.size()); - const wchar_t * endptr = ts.begin + (pxi - pxptr) + (text_px == pixels ? 1 : 0); - line_sections.emplace_back(secondary_begin, endptr, unsigned{ text_px - (text_px == pixels ? 0 : *pxi) }); - secondary_begin = endptr; + pos = cistr.find(ds.text.c_str(), pos); + if (pos == cistr.npos) + break; + } - text_px = (text_px == pixels ? 0 : *pxi); + if (ds.whole_word_matched && (!_m_whole_word(text, pos, ds.text.size()))) + continue; + + auto ki = keywords.schemes.find(ds.scheme); + if ((ki != keywords.schemes.end()) && ki->second) + { +#ifdef _nana_std_has_emplace_return_type + auto & last = entities.emplace_back(); +#else + entities.emplace_back(); + auto & last = entities.back(); +#endif + last.begin = c_str + pos; + last.end = last.begin + ds.text.size(); + last.scheme = ki->second.get(); } } - continue; } - else if (text_px == pixels) + + if (!entities.empty()) { - line_sections.emplace_back(secondary_begin, ts.begin, unsigned{ text_px - str_w }); - secondary_begin = ts.begin; - text_px = str_w; - } - } - - auto & mtr = linemtr_[line]; - - mtr.take_lines = line_sections.size(); - mtr.line_sections.swap(line_sections); - - if (secondary_begin) - { - mtr.line_sections.emplace_back(secondary_begin, sections.back().end, unsigned{ text_px }); - ++mtr.take_lines; - } - } - - void pre_calc_lines(unsigned pixels) override - { - auto const lines = editor_.textbase().lines(); - linemtr_.resize(lines); - - for (std::size_t i = 0; i < lines; ++i) - pre_calc_line(i, pixels); - } - - std::size_t take_lines() const override - { - std::size_t lines = 0; - for (auto & mtr : linemtr_) - lines += mtr.take_lines; - - return lines; - } - - std::size_t take_lines(std::size_t pos) const override - { - return (pos < linemtr_.size() ? linemtr_[pos].take_lines : 0); - } - private: - /// Split a text into multiple sections, a section indicates an english word or a CKJ character - void _m_text_section(const std::wstring& str, std::vector& tsec) - { - if (str.empty()) - { - tsec.emplace_back(str.c_str(), str.c_str(), unsigned{}); - return; - } - const auto end = str.c_str() + str.size(); - - const wchar_t * word = nullptr; - for (auto i = str.c_str(); i != end; ++i) - { - wchar_t const ch = *i; - - //CKJ characters and whitespace - if (' ' == ch || '\t' == ch || (0x4E00 <= ch && ch <= 0x9FCF)) - { - if (word) //Record the word. + std::sort(entities.begin(), entities.end(), [](const entity& a, const entity& b) { - tsec.emplace_back(word, i, unsigned{}); - word = nullptr; + return (a.begin < b.begin); + }); + + auto i = entities.begin(); + auto bound = i->end; + + for (++i; i != entities.end(); ) + { + if (bound > i->begin) + i = entities.erase(i); // erase overlaping. Left only the first. + else + ++i; } - - tsec.emplace_back(i, i + 1, unsigned{}); - continue; } - if (nullptr == word) - word = i; + entities_.swap(entities); } - if(word) - tsec.emplace_back(word, end, unsigned{}); - } - - row_coordinate _m_textline(std::size_t scrline) const - { - row_coordinate coord; - for (auto & mtr : linemtr_) + const std::vector& entities() const { - if (mtr.take_lines > scrline) + return entities_; + } + private: + static bool _m_whole_word(const std::wstring& text, std::wstring::size_type pos, std::size_t len) + { + if (pos) { - coord.second = scrline; - return coord; + auto chr = text[pos - 1]; + if ((std::iswalpha(chr) && !std::iswspace(chr)) || chr == '_') + return false; } - else - scrline -= mtr.take_lines; - ++coord.first; - } - return coord; - } - private: - text_editor& editor_; - std::vector linemtr_; - }; //end class behavior_linewrapped - - class text_editor::keyword_parser - { - public: - 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) ) - return; - - std::wstring text{ c_str, len }; - - using index = std::wstring::size_type; - - std::vector entities; - - ::nana::ciwstring cistr; - for (auto & ds : keywords.base) - { - index pos{0} ; - for (index rest{text.size()}; rest >= ds.text.size() ; ++pos, rest = text.size() - pos) - { - if (ds.case_sensitive) - { - pos = text.find(ds.text, pos); - if (pos == text.npos) - break; - } - else - { - if (cistr.empty()) - cistr.append(text.c_str(), text.size()); - - pos = cistr.find(ds.text.c_str(), pos); - if (pos == cistr.npos) - break; - } - - if (ds.whole_word_matched && (!_m_whole_word(text, pos, ds.text.size()))) - continue; - - auto ki = keywords.schemes.find(ds.scheme); - if ((ki != keywords.schemes.end()) && ki->second) - { -#ifdef _nana_std_has_emplace_return_type - auto & last = entities.emplace_back(); -#else - entities.emplace_back(); - auto & last = entities.back(); -#endif - last.begin = c_str + pos; - last.end = last.begin + ds.text.size(); - last.scheme = ki->second.get(); - } - } - } - - if (!entities.empty()) - { - std::sort(entities.begin(), entities.end(), [](const entity& a, const entity& b) + if (pos + len < text.size()) { - return (a.begin < b.begin); - }); - - auto i = entities.begin(); - auto bound = i->end; - - for (++i; i != entities.end(); ) - { - if (bound > i->begin) - i = entities.erase(i); // erase overlaping. Left only the first. - else - ++i; + auto chr = text[pos + len]; + if ((std::iswalpha(chr) && !std::iswspace(chr)) || chr == '_') + return false; } + + return true; } + private: + std::vector entities_; + }; - entities_.swap(entities); - } + //class text_editor - const std::vector& entities() const - { - return entities_; - } - private: - static bool _m_whole_word(const std::wstring& text, std::wstring::size_type pos, std::size_t len) - { - if (pos) - { - auto chr = text[pos - 1]; - if ((std::iswalpha(chr) && !std::iswspace(chr)) || chr == '_') - return false; - } - - if (pos + len < text.size()) - { - auto chr = text[pos + len]; - if ((std::iswalpha(chr) && !std::iswspace(chr)) || chr == '_') - return false; - } - - return true; - } - private: - std::vector entities_; - }; - - //class text_editor - - text_editor::text_editor(window wd, graph_reference graph, const text_editor_scheme* schm) - : impl_(new implementation), + text_editor::text_editor(window wd, graph_reference graph, const text_editor_scheme* schm) + : impl_(new implementation), window_(wd), graph_(graph), scheme_(schm) - { - impl_->capacities.behavior = new behavior_normal(*this); - - text_area_.area.dimension(graph.size()); - - impl_->cview.reset(new content_view{ wd }); - impl_->cview->disp_area(text_area_.area, {}, {}, {}); - impl_->cview->events().scrolled = [this] { - 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); - } - - text_editor::~text_editor() - { - //For instance of unique_ptr pimpl idiom. - - delete impl_->capacities.behavior; - delete impl_; - } - - size text_editor::caret_size() const - { - return { 1, line_height() }; - } - - const point& text_editor::content_origin() const - { - return impl_->cview->origin(); - } - - void text_editor::set_highlight(const std::string& name, const ::nana::color& fgcolor, const ::nana::color& bgcolor) - { - if (fgcolor.invisible() && bgcolor.invisible()) { - impl_->keywords.schemes.erase(name); - return; + impl_->capacities.behavior = new behavior_normal(*this); + + text_area_.area.dimension(graph.size()); + + impl_->cview.reset(new content_view{ wd }); + impl_->cview->disp_area(text_area_.area, {}, {}, {}); + impl_->cview->events().scrolled = [this] { + 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); } - auto sp = std::make_shared(); - sp->fgcolor = fgcolor; - sp->bgcolor = bgcolor; - impl_->keywords.schemes[name].swap(sp); - } - - void text_editor::erase_highlight(const std::string& name) - { - impl_->keywords.schemes.erase(name); - } - - 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) + text_editor::~text_editor() { - if (ds.text == kw) - { - ds.scheme = name; - ds.case_sensitive = case_sensitive; - ds.whole_word_matched = whole_word_matched; - return; - } - } - - impl_->keywords.base.emplace_back(kw, name, case_sensitive, whole_word_matched); - } - - void text_editor::erase_keyword(const ::std::wstring& kw) - { - for (auto i = impl_->keywords.base.begin(); i != impl_->keywords.base.end(); ++i) - { - if (kw == i->text) - { - impl_->keywords.base.erase(i); - return; - } - } - } - - colored_area_access_interface& text_editor::colored_area() - { - return impl_->colored_area; - } - - void text_editor::set_accept(std::function pred) - { - impl_->capacities.pred_acceptive = std::move(pred); - } - - void text_editor::set_accept(accepts acceptive) - { - impl_->capacities.acceptive = acceptive; - } - - bool text_editor::respond_char(const arg_keyboard& arg) //key is a character of ASCII code - { - if (!API::window_enabled(window_)) - return false; - - char_type key = arg.key; - switch (key) - { - case keyboard::end_of_text: - copy(); - return false; - case keyboard::select_all: - select(true); - return true; - } - - if (attributes_.editable && (!impl_->capacities.pred_acceptive || impl_->capacities.pred_acceptive(key))) - { - switch (key) - { - case '\b': - backspace(true, true); break; - case '\n': case '\r': - enter(true, true); break; - case keyboard::sync_idel: - paste(); break; - case keyboard::tab: - put(static_cast(keyboard::tab)); break; - case keyboard::cancel: - cut(); - break; - case keyboard::end_of_medium: - undo(true); - break; - case keyboard::substitute: - undo(false); - break; - default: - if (!_m_accepts(key)) - return false; - - if (key > 0x7F || (32 <= key && key <= 126)) - put(key); - } - reset_caret(); - impl_->try_refresh = sync_graph::refresh; - return true; - } - return false; - } - - bool text_editor::respond_key(const arg_keyboard& arg) - { - char_type key = arg.key; - switch (key) - { - case keyboard::os_arrow_left: - case keyboard::os_arrow_right: - case keyboard::os_arrow_up: - case keyboard::os_arrow_down: - case keyboard::os_home: - case keyboard::os_end: - case keyboard::os_pageup: - case keyboard::os_pagedown: - _m_handle_move_key(arg); - break; - case keyboard::del: - // send delete to set_accept function - if (this->attr().editable && (!impl_->capacities.pred_acceptive || impl_->capacities.pred_acceptive(key))) - del(); - break; - default: - return false; - } - impl_->try_refresh = sync_graph::refresh; - return true; - } - - void text_editor::typeface_changed() - { - _m_reset_content_size(true); - } - - void text_editor::indent(bool enb, std::function generator) - { - impl_->indent.enabled = enb; - impl_->indent.generator = std::move(generator); - } - - void text_editor::set_event(event_interface* ptr) - { - event_handler_ = ptr; - } - - bool text_editor::load(const char* fs) - { - if (!impl_->textbase.load(fs)) - return false; - - _m_reset(); - - impl_->try_refresh = sync_graph::refresh; - _m_reset_content_size(true); - return true; - } - - void text_editor::text_align(::nana::align alignment) - { - this->attributes_.alignment = alignment; - this->reset_caret(); - impl_->try_refresh = sync_graph::refresh; - _m_reset_content_size(); - } - - bool text_editor::text_area(const nana::rectangle& r) - { - if(text_area_.area == r) - return false; - - text_area_.area = r; - - if (impl_->counterpart.enabled) - impl_->counterpart.buffer.make(r.dimension()); - - impl_->cview->disp_area(r, { -1, 1 }, { 1, -1 }, { 2, 2 }); - if (impl_->cview->content_size().empty() || this->attributes_.line_wrapped) - _m_reset_content_size(true); - - reset_caret(); - return true; - } - - rectangle text_editor::text_area(bool including_scroll) const - { - return (including_scroll ? impl_->cview->view_area() : text_area_.area); - } - - bool text_editor::tip_string(::std::string&& str) - { - if(attributes_.tip_string == str) - return false; - - attributes_.tip_string = std::move(str); - return true; - } - - const text_editor::attributes& text_editor::attr() const noexcept - { - return attributes_; - } - - bool text_editor::line_wrapped(bool autl) - { - if (autl != attributes_.line_wrapped) - { - attributes_.line_wrapped = autl; + //For instance of unique_ptr pimpl idiom. delete impl_->capacities.behavior; - if (autl) - impl_->capacities.behavior = new behavior_linewrapped(*this); - else - impl_->capacities.behavior = new behavior_normal(*this); - - _m_reset_content_size(true); - - impl_->cview->move_origin(point{} -impl_->cview->origin()); - move_caret(upoint{}); - - impl_->try_refresh = sync_graph::refresh; - return true; - } - return false; - } - - bool text_editor::multi_lines(bool ml) - { - if((ml == false) && attributes_.multi_lines) - { - //retain the first line and remove the extra lines - if (impl_->textbase.erase(1, impl_->textbase.lines() - 1)) - _m_reset(); + delete impl_; } - if (attributes_.multi_lines == ml) - return false; - - attributes_.multi_lines = ml; - - if (!ml) - line_wrapped(false); - - _m_reset_content_size(); - impl_->cview->enable_scrolls(ml ? content_view::scrolls::both : content_view::scrolls::none); - impl_->cview->move_origin(point{} -impl_->cview->origin()); - - impl_->try_refresh = sync_graph::refresh; - return true; - } - - void text_editor::editable(bool enable, bool enable_caret) - { - attributes_.editable = enable; - attributes_.enable_caret = (enable || enable_caret); - } - - void text_editor::enable_background(bool enb) - { - attributes_.enable_background = enb; - } - - void text_editor::enable_background_counterpart(bool enb) - { - impl_->counterpart.enabled = enb; - if (enb) - impl_->counterpart.buffer.make(text_area_.area.dimension()); - else - impl_->counterpart.buffer.release(); - } - - void text_editor::undo_enabled(bool enb) - { - impl_->undo.enable(enb); - } - - bool text_editor::undo_enabled() const - { - return impl_->undo.enabled(); - } - - void text_editor::undo_max_steps(std::size_t maxs) - { - impl_->undo.max_steps(maxs); - } - - std::size_t text_editor::undo_max_steps() const - { - return impl_->undo.max_steps(); - } - - void text_editor::clear_undo() - { - auto size = impl_->undo.max_steps(); - impl_->undo.max_steps(0); - impl_->undo.max_steps(size); - } - - auto text_editor::customized_renderers() -> renderers& - { - return impl_->customized_renderers; - } - - unsigned text_editor::line_height() const - { - unsigned ascent, descent, internal_leading; - unsigned px = 0; - if (graph_.text_metrics(ascent, descent, internal_leading)) - px = ascent + descent; - - impl_->cview->step(px, false); - return px; - } - - unsigned text_editor::screen_lines(bool completed_line) const - { - auto const line_px = line_height(); - if (line_px) + size text_editor::caret_size() const { - auto h = impl_->cview->view_area().height; - if (graph_ && h) + return { 1, line_height() }; + } + + const point& text_editor::content_origin() const + { + return impl_->cview->origin(); + } + + void text_editor::set_highlight(const std::string& name, const ::nana::color& fgcolor, const ::nana::color& bgcolor) + { + if (fgcolor.invisible() && bgcolor.invisible()) { - if (completed_line) - return (h / line_px); - - return (h / line_px + (h % line_px ? 1 : 0)); - } - } - return 0; - } - - bool text_editor::focus_changed(const arg_focus& arg) - { - bool renderred = false; - - if (arg.getting && (select_.a == select_.b)) //Do not change the selected text - { - bool select_all = false; - switch (select_.behavior) - { - case text_focus_behavior::select: - select_all = true; - break; - case text_focus_behavior::select_if_click: - select_all = (arg_focus::reason::mouse_press == arg.focus_reason); - break; - case text_focus_behavior::select_if_tabstop: - select_all = (arg_focus::reason::tabstop == arg.focus_reason); - break; - case text_focus_behavior::select_if_tabstop_or_click: - select_all = (arg_focus::reason::tabstop == arg.focus_reason || arg_focus::reason::mouse_press == arg.focus_reason); - default: - break; - } - - if (select_all) - { - select(true); - move_caret_end(false); - renderred = true; - - //If the text widget is focused by clicking mouse button, the selected text will be cancelled - //by the subsequent mouse down event. In this situation, the subsequent mouse down event should - //be ignored. - select_.ignore_press = (arg_focus::reason::mouse_press == arg.focus_reason); - } - } - show_caret(arg.getting); - reset_caret(); - return renderred; - } - - bool text_editor::mouse_enter(bool entering) - { - if ((false == entering) && (false == text_area_.captured)) - API::window_cursor(window_, nana::cursor::arrow); - - return false; - } - - bool text_editor::mouse_move(bool left_button, const point& scrpos) - { - cursor cur = cursor::iterm; - if(((!hit_text_area(scrpos)) && (!text_area_.captured)) || !attributes_.enable_caret || !API::window_enabled(window_)) - cur = cursor::arrow; - - API::window_cursor(window_, cur); - - if(!attributes_.enable_caret) - return false; - - if(left_button) - { - mouse_caret(scrpos, false); - - if (selection::mode::mouse_selected == select_.mode_selection || selection::mode::method_selected == select_.mode_selection) - set_end_caret(false); - else if (selection::mode::move_selected == select_.mode_selection) - select_.mode_selection = selection::mode::move_selected_take_effect; - - impl_->try_refresh = sync_graph::refresh; - return true; - } - return false; - } - - void text_editor::mouse_pressed(const arg_mouse& arg) - { - if(!attributes_.enable_caret) - return; - - if (event_code::mouse_down == arg.evt_code) - { - if (select_.ignore_press || (!hit_text_area(arg.pos))) - { - select_.ignore_press = false; + impl_->keywords.schemes.erase(name); return; } - if (::nana::mouse::left_button == arg.button) - { - API::set_capture(window_, true); - text_area_.captured = true; + auto sp = std::make_shared(); + sp->fgcolor = fgcolor; + sp->bgcolor = bgcolor; + impl_->keywords.schemes[name].swap(sp); + } - if (this->hit_select_area(_m_coordinate_to_caret(arg.pos), true)) + void text_editor::erase_highlight(const std::string& name) + { + impl_->keywords.schemes.erase(name); + } + + 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) + { + if (ds.text == kw) { - //The selected of text can be moved only if it is editable - if (attributes_.editable) - select_.mode_selection = selection::mode::move_selected; + ds.scheme = name; + ds.case_sensitive = case_sensitive; + ds.whole_word_matched = whole_word_matched; + return; } - else + } + + impl_->keywords.base.emplace_back(kw, name, case_sensitive, whole_word_matched); + } + + void text_editor::erase_keyword(const ::std::wstring& kw) + { + for (auto i = impl_->keywords.base.begin(); i != impl_->keywords.base.end(); ++i) + { + if (kw == i->text) { - //Set caret pos by screen point and get the caret pos. - mouse_caret(arg.pos, true); - if (arg.shift) + impl_->keywords.base.erase(i); + return; + } + } + } + + colored_area_access_interface& text_editor::colored_area() + { + return impl_->colored_area; + } + + void text_editor::set_accept(std::function pred) + { + impl_->capacities.pred_acceptive = std::move(pred); + } + + void text_editor::set_accept(accepts acceptive) + { + impl_->capacities.acceptive = acceptive; + } + + bool text_editor::respond_char(const arg_keyboard& arg) //key is a character of ASCII code + { + if (!API::window_enabled(window_)) + return false; + + char_type key = arg.key; + switch (key) + { + case keyboard::end_of_text: + copy(); + return false; + case keyboard::select_all: + select(true); + return true; + } + + if (attributes_.editable && (!impl_->capacities.pred_acceptive || impl_->capacities.pred_acceptive(key))) + { + switch (key) + { + case '\b': + backspace(true, true); break; + case '\n': case '\r': + enter(true, true); break; + case keyboard::sync_idel: + paste(); break; + case keyboard::tab: + put(static_cast(keyboard::tab)); break; + case keyboard::cancel: + cut(); + break; + case keyboard::end_of_medium: + undo(true); + break; + case keyboard::substitute: + undo(false); + break; + default: + if (!_m_accepts(key)) + return false; + + if (key > 0x7F || (32 <= key && key <= 126)) + put(key); + } + reset_caret(); + impl_->try_refresh = sync_graph::refresh; + return true; + } + return false; + } + + bool text_editor::respond_key(const arg_keyboard& arg) + { + char_type key = arg.key; + switch (key) + { + case keyboard::os_arrow_left: + case keyboard::os_arrow_right: + case keyboard::os_arrow_up: + case keyboard::os_arrow_down: + case keyboard::os_home: + case keyboard::os_end: + case keyboard::os_pageup: + case keyboard::os_pagedown: + _m_handle_move_key(arg); + break; + case keyboard::del: + // send delete to set_accept function + if (this->attr().editable && (!impl_->capacities.pred_acceptive || impl_->capacities.pred_acceptive(key))) + del(); + break; + default: + return false; + } + impl_->try_refresh = sync_graph::refresh; + return true; + } + + void text_editor::typeface_changed() + { + _m_reset_content_size(true); + } + + void text_editor::indent(bool enb, std::function generator) + { + impl_->indent.enabled = enb; + impl_->indent.generator = std::move(generator); + } + + void text_editor::set_event(event_interface* ptr) + { + event_handler_ = ptr; + } + + bool text_editor::load(const char* fs) + { + if (!impl_->textbase.load(fs)) + return false; + + _m_reset(); + + impl_->try_refresh = sync_graph::refresh; + _m_reset_content_size(true); + return true; + } + + void text_editor::text_align(::nana::align alignment) + { + this->attributes_.alignment = alignment; + this->reset_caret(); + impl_->try_refresh = sync_graph::refresh; + _m_reset_content_size(); + } + + bool text_editor::text_area(const nana::rectangle& r) + { + if (text_area_.area == r) + return false; + + text_area_.area = r; + + if (impl_->counterpart.enabled) + impl_->counterpart.buffer.make(r.dimension()); + + impl_->cview->disp_area(r, { -1, 1 }, { 1, -1 }, { 2, 2 }); + if (impl_->cview->content_size().empty() || this->attributes_.line_wrapped) + _m_reset_content_size(true); + + reset_caret(); + return true; + } + + rectangle text_editor::text_area(bool including_scroll) const + { + return (including_scroll ? impl_->cview->view_area() : text_area_.area); + } + + bool text_editor::tip_string(::std::string&& str) + { + if (attributes_.tip_string == str) + return false; + + attributes_.tip_string = std::move(str); + return true; + } + + const text_editor::attributes& text_editor::attr() const noexcept + { + return attributes_; + } + + bool text_editor::line_wrapped(bool autl) + { + if (autl != attributes_.line_wrapped) + { + attributes_.line_wrapped = autl; + + delete impl_->capacities.behavior; + if (autl) + impl_->capacities.behavior = new behavior_linewrapped(*this); + else + impl_->capacities.behavior = new behavior_normal(*this); + + _m_reset_content_size(true); + + impl_->cview->move_origin(point{} -impl_->cview->origin()); + move_caret(upoint{}); + + impl_->try_refresh = sync_graph::refresh; + return true; + } + return false; + } + + bool text_editor::multi_lines(bool ml) + { + if ((ml == false) && attributes_.multi_lines) + { + //retain the first line and remove the extra lines + if (impl_->textbase.erase(1, impl_->textbase.lines() - 1)) + _m_reset(); + } + + if (attributes_.multi_lines == ml) + return false; + + attributes_.multi_lines = ml; + + if (!ml) + line_wrapped(false); + + _m_reset_content_size(); + impl_->cview->enable_scrolls(ml ? content_view::scrolls::both : content_view::scrolls::none); + impl_->cview->move_origin(point{} -impl_->cview->origin()); + + impl_->try_refresh = sync_graph::refresh; + return true; + } + + void text_editor::editable(bool enable, bool enable_caret) + { + attributes_.editable = enable; + attributes_.enable_caret = (enable || enable_caret); + } + + void text_editor::enable_background(bool enb) + { + attributes_.enable_background = enb; + } + + void text_editor::enable_background_counterpart(bool enb) + { + impl_->counterpart.enabled = enb; + if (enb) + impl_->counterpart.buffer.make(text_area_.area.dimension()); + else + impl_->counterpart.buffer.release(); + } + + void text_editor::undo_enabled(bool enb) + { + impl_->undo.enable(enb); + } + + bool text_editor::undo_enabled() const + { + return impl_->undo.enabled(); + } + + void text_editor::undo_max_steps(std::size_t maxs) + { + impl_->undo.max_steps(maxs); + } + + std::size_t text_editor::undo_max_steps() const + { + return impl_->undo.max_steps(); + } + + void text_editor::clear_undo() + { + auto size = impl_->undo.max_steps(); + impl_->undo.max_steps(0); + impl_->undo.max_steps(size); + } + + auto text_editor::customized_renderers() -> renderers& + { + return impl_->customized_renderers; + } + + unsigned text_editor::line_height() const + { + unsigned ascent, descent, internal_leading; + unsigned px = 0; + if (graph_.text_metrics(ascent, descent, internal_leading)) + px = ascent + descent; + + impl_->cview->step(px, false); + return px; + } + + unsigned text_editor::screen_lines(bool completed_line) const + { + auto const line_px = line_height(); + if (line_px) + { + auto h = impl_->cview->view_area().height; + if (graph_ && h) + { + if (completed_line) + return (h / line_px); + + return (h / line_px + (h % line_px ? 1 : 0)); + } + } + return 0; + } + + bool text_editor::focus_changed(const arg_focus& arg) + { + bool renderred = false; + + if (arg.getting && (select_.a == select_.b)) //Do not change the selected text + { + bool select_all = false; + switch (select_.behavior) + { + case text_focus_behavior::select: + select_all = true; + break; + case text_focus_behavior::select_if_click: + select_all = (arg_focus::reason::mouse_press == arg.focus_reason); + break; + case text_focus_behavior::select_if_tabstop: + select_all = (arg_focus::reason::tabstop == arg.focus_reason); + break; + case text_focus_behavior::select_if_tabstop_or_click: + select_all = (arg_focus::reason::tabstop == arg.focus_reason || arg_focus::reason::mouse_press == arg.focus_reason); + default: + break; + } + + if (select_all) + { + select(true); + move_caret_end(false); + renderred = true; + + //If the text widget is focused by clicking mouse button, the selected text will be cancelled + //by the subsequent mouse down event. In this situation, the subsequent mouse down event should + //be ignored. + select_.ignore_press = (arg_focus::reason::mouse_press == arg.focus_reason); + } + } + show_caret(arg.getting); + reset_caret(); + return renderred; + } + + bool text_editor::mouse_enter(bool entering) + { + if ((false == entering) && (false == text_area_.captured)) + API::window_cursor(window_, nana::cursor::arrow); + + return false; + } + + bool text_editor::mouse_move(bool left_button, const point& scrpos) + { + cursor cur = cursor::iterm; + if (((!hit_text_area(scrpos)) && (!text_area_.captured)) || !attributes_.enable_caret || !API::window_enabled(window_)) + cur = cursor::arrow; + + API::window_cursor(window_, cur); + + if (!attributes_.enable_caret) + return false; + + if (left_button) + { + mouse_caret(scrpos, false); + + if (selection::mode::mouse_selected == select_.mode_selection || selection::mode::method_selected == select_.mode_selection) + set_end_caret(false); + else if (selection::mode::move_selected == select_.mode_selection) + select_.mode_selection = selection::mode::move_selected_take_effect; + + impl_->try_refresh = sync_graph::refresh; + return true; + } + return false; + } + + void text_editor::mouse_pressed(const arg_mouse& arg) + { + if (!attributes_.enable_caret) + return; + + if (event_code::mouse_down == arg.evt_code) + { + if (select_.ignore_press || (!hit_text_area(arg.pos))) + { + select_.ignore_press = false; + return; + } + + if (::nana::mouse::left_button == arg.button) + { + API::set_capture(window_, true); + text_area_.captured = true; + + if (this->hit_select_area(_m_coordinate_to_caret(arg.pos), true)) { - if (points_.shift_begin_caret != points_.caret) - { - select_.a = points_.shift_begin_caret; - select_.b = points_.caret; - } + //The selected of text can be moved only if it is editable + if (attributes_.editable) + select_.mode_selection = selection::mode::move_selected; } else { - if (!select(false)) + //Set caret pos by screen point and get the caret pos. + mouse_caret(arg.pos, true); + if (arg.shift) { - select_.a = points_.caret; //Set begin caret - set_end_caret(true); + if (points_.shift_begin_caret != points_.caret) + { + select_.a = points_.shift_begin_caret; + select_.b = points_.caret; + } } - points_.shift_begin_caret = points_.caret; + else + { + if (!select(false)) + { + select_.a = points_.caret; //Set begin caret + set_end_caret(true); + } + points_.shift_begin_caret = points_.caret; + } + select_.mode_selection = selection::mode::mouse_selected; } - select_.mode_selection = selection::mode::mouse_selected; } + + impl_->try_refresh = sync_graph::refresh; } - - impl_->try_refresh = sync_graph::refresh; - } - else if (event_code::mouse_up == arg.evt_code) - { - select_.ignore_press = false; - - if (select_.mode_selection == selection::mode::mouse_selected) + else if (event_code::mouse_up == arg.evt_code) { - select_.mode_selection = selection::mode::no_selected; - set_end_caret(true); - } - else if (selection::mode::move_selected == select_.mode_selection || selection::mode::move_selected_take_effect == select_.mode_selection) - { - //move_selected indicates the mouse is pressed on the selected text, but the mouse has not moved. So the text_editor should cancel the selection. - //move_selected_take_effect indicates the text_editor should try to move the selection. + select_.ignore_press = false; - if ((selection::mode::move_selected == select_.mode_selection) || !move_select()) + if (select_.mode_selection == selection::mode::mouse_selected) { - //no move occurs - select(false); - move_caret(_m_coordinate_to_caret(arg.pos)); + select_.mode_selection = selection::mode::no_selected; + set_end_caret(true); + } + else if (selection::mode::move_selected == select_.mode_selection || selection::mode::move_selected_take_effect == select_.mode_selection) + { + //move_selected indicates the mouse is pressed on the selected text, but the mouse has not moved. So the text_editor should cancel the selection. + //move_selected_take_effect indicates the text_editor should try to move the selection. + + if ((selection::mode::move_selected == select_.mode_selection) || !move_select()) + { + //no move occurs + select(false); + move_caret(_m_coordinate_to_caret(arg.pos)); + } + + select_.mode_selection = selection::mode::no_selected; + impl_->try_refresh = sync_graph::refresh; } - select_.mode_selection = selection::mode::no_selected; - impl_->try_refresh = sync_graph::refresh; - } + API::release_capture(window_); - API::release_capture(window_); - - text_area_.captured = false; - if (hit_text_area(arg.pos) == false) - API::window_cursor(window_, nana::cursor::arrow); - } - } - - //Added Windows-style mouse double-click to the textbox(https://github.com/cnjinhao/nana/pull/229) - //Oleg Smolsky - bool text_editor::select_word(const arg_mouse& arg) - { - if(!attributes_.enable_caret) - return false; - - // Set caret pos by screen point and get the caret pos. - mouse_caret(arg.pos, true); - - // Set the initial selection: it is an empty range. - select_.a = select_.b = points_.caret; - const auto& line = impl_->textbase.getline(select_.b.y); - - // Expand the selection forward to the word's end. - while (select_.b.x < line.size() && !std::iswspace(line[select_.b.x])) - ++select_.b.x; - - // Expand the selection backward to the word's start. - while (select_.a.x > 0 && !std::iswspace(line[select_.a.x - 1])) - --select_.a.x; - - select_.mode_selection = selection::mode::method_selected; - impl_->try_refresh = sync_graph::refresh; - return true; - } - - textbase & text_editor::textbase() - { - return impl_->textbase; - } - - const textbase & text_editor::textbase() const - { - return impl_->textbase; - } - - bool text_editor::try_refresh() - { - if (sync_graph::none != impl_->try_refresh) - { - if (sync_graph::refresh == impl_->try_refresh) - render(API::is_focus_ready(window_)); - - impl_->try_refresh = sync_graph::none; - return true; - } - return false; - } - - bool text_editor::getline(std::size_t pos, std::wstring& text) const - { - if (impl_->textbase.lines() <= pos) - return false; - - text = impl_->textbase.getline(pos); - return true; - } - - void text_editor::text(std::wstring str, bool end_caret) - { - impl_->undo.clear(); - - impl_->textbase.erase_all(); - _m_reset(); - _m_reset_content_size(true); - - if (!end_caret) - { - auto undo_ptr = std::unique_ptr{ new undo_input_text(*this, str) }; - undo_ptr->set_caret_pos(); - - _m_put(std::move(str), false); - - impl_->undo.push(std::move(undo_ptr)); - - if (graph_) - { - this->_m_adjust_view(); - - reset_caret(); - impl_->try_refresh = sync_graph::refresh; - - //_m_put calcs the lines - _m_reset_content_size(true); - impl_->cview->sync(false); + text_area_.captured = false; + if (hit_text_area(arg.pos) == false) + API::window_cursor(window_, nana::cursor::arrow); } } - else - put(std::move(str), false); - textbase().text_changed(); - } - - std::wstring text_editor::text() const - { - std::wstring str; - std::size_t lines = impl_->textbase.lines(); - if(lines > 0) + //Added Windows-style mouse double-click to the textbox(https://github.com/cnjinhao/nana/pull/229) + //Oleg Smolsky + bool text_editor::select_word(const arg_mouse& arg) { - str = impl_->textbase.getline(0); - for(std::size_t i = 1; i < lines; ++i) - { - str += L"\n\r"; - str += impl_->textbase.getline(i); + if (!attributes_.enable_caret) + return false; + + // Set caret pos by screen point and get the caret pos. + mouse_caret(arg.pos, true); + + // Set the initial selection: it is an empty range. + select_.a = select_.b = points_.caret; + 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; } - } - return str; - } + else { + // Expand the selection forward to the word's end. + 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; - bool text_editor::move_caret(upoint crtpos, bool stay_in_view) - { - const unsigned line_pixels = line_height(); - - if (crtpos != points_.caret) - { - //Check and make the crtpos available - if (crtpos.y < impl_->textbase.lines()) - { - crtpos.x = (std::min)(static_cast(impl_->textbase.getline(crtpos.y).size()), crtpos.x); + // Expand the selection backward to the word's start. + 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; } - else - { - crtpos.y = static_cast(impl_->textbase.lines()); - if (crtpos.y > 0) - --crtpos.y; - - crtpos.x = static_cast(impl_->textbase.getline(crtpos.y).size()); - } - - points_.caret = crtpos; - } - - //The coordinate of caret - auto coord = _m_caret_to_coordinate(crtpos); - - const int line_bottom = coord.y + static_cast(line_pixels); - - if (!API::is_focus_ready(window_)) - return false; - - auto caret = API::open_caret(window_, true); - - bool visible = false; - auto text_area = impl_->cview->view_area(); - - if (text_area.is_hit(coord) && (line_bottom > text_area.y)) - { - visible = true; - if (line_bottom > text_area.bottom()) - caret->dimension(nana::size(1, line_pixels - (line_bottom - text_area.bottom()))); - else if (caret->dimension().height != line_pixels) - reset_caret_pixels(); - } - - if(!attributes_.enable_caret) - visible = false; - - caret->visible(visible); - if(visible) - caret->position(coord); - - //Adjust the caret into screen when the caret position is modified by this function - if (stay_in_view && (!hit_text_area(coord))) - { - if (_m_adjust_view()) - impl_->cview->sync(false); - - impl_->try_refresh = sync_graph::refresh; - caret->visible(true); - return true; - } - return false; - } - - void text_editor::move_caret_end(bool update) - { - points_.caret.y = static_cast(impl_->textbase.lines()); - if(points_.caret.y) --points_.caret.y; - points_.caret.x = static_cast(impl_->textbase.getline(points_.caret.y).size()); - - if (update) - this->reset_caret(); - } - - void text_editor::reset_caret_pixels() const - { - API::open_caret(window_, true).get()->dimension({ 1, line_height() }); - } - - void text_editor::reset_caret(bool stay_in_view) - { - move_caret(points_.caret, stay_in_view); - } - - void text_editor::show_caret(bool isshow) - { - if(isshow == false || API::is_focus_ready(window_)) - API::open_caret(window_, true).get()->visible(isshow); - } - - bool text_editor::selected() const - { - return (select_.a != select_.b); - } - - bool text_editor::get_selected_points(nana::upoint &a, nana::upoint &b) const - { - if (select_.a == select_.b) - return false; - - if (select_.a < select_.b) - { - a = select_.a; - b = select_.b; - } - else - { - a = select_.b; - b = select_.a; - } - - return true; - } - - bool text_editor::select(bool yes) - { - if (yes) - { - select_.a.x = select_.a.y = 0; - select_.b.y = static_cast(impl_->textbase.lines()); - if (select_.b.y) --select_.b.y; - select_.b.x = static_cast(impl_->textbase.getline(select_.b.y).size()); select_.mode_selection = selection::mode::method_selected; impl_->try_refresh = sync_graph::refresh; return true; } - select_.mode_selection = selection::mode::no_selected; - if (_m_cancel_select(0)) + textbase & text_editor::textbase() { + return impl_->textbase; + } + + const textbase & text_editor::textbase() const + { + return impl_->textbase; + } + + bool text_editor::try_refresh() + { + if (sync_graph::none != impl_->try_refresh) + { + if (sync_graph::refresh == impl_->try_refresh) + render(API::is_focus_ready(window_)); + + impl_->try_refresh = sync_graph::none; + return true; + } + return false; + } + + bool text_editor::getline(std::size_t pos, std::wstring& text) const + { + if (impl_->textbase.lines() <= pos) + return false; + + text = impl_->textbase.getline(pos); + return true; + } + + void text_editor::text(std::wstring str, bool end_caret) + { + impl_->undo.clear(); + + impl_->textbase.erase_all(); + _m_reset(); + _m_reset_content_size(true); + + if (!end_caret) + { + auto undo_ptr = std::unique_ptr{ new undo_input_text(*this, str) }; + undo_ptr->set_caret_pos(); + + _m_put(std::move(str), false); + + impl_->undo.push(std::move(undo_ptr)); + + if (graph_) + { + this->_m_adjust_view(); + + reset_caret(); + impl_->try_refresh = sync_graph::refresh; + + //_m_put calcs the lines + _m_reset_content_size(true); + impl_->cview->sync(false); + } + } + else + put(std::move(str), false); + + textbase().text_changed(); + } + + std::wstring text_editor::text() const + { + std::wstring str; + std::size_t lines = impl_->textbase.lines(); + if (lines > 0) + { + str = impl_->textbase.getline(0); + for (std::size_t i = 1; i < lines; ++i) + { + str += L"\n\r"; + str += impl_->textbase.getline(i); + } + } + return str; + } + + bool text_editor::move_caret(upoint crtpos, bool stay_in_view) + { + const unsigned line_pixels = line_height(); + + if (crtpos != points_.caret) + { + //Check and make the crtpos available + if (crtpos.y < impl_->textbase.lines()) + { + crtpos.x = (std::min)(static_cast(impl_->textbase.getline(crtpos.y).size()), crtpos.x); + } + else + { + crtpos.y = static_cast(impl_->textbase.lines()); + if (crtpos.y > 0) + --crtpos.y; + + crtpos.x = static_cast(impl_->textbase.getline(crtpos.y).size()); + } + + points_.caret = crtpos; + } + + //The coordinate of caret + auto coord = _m_caret_to_coordinate(crtpos); + + const int line_bottom = coord.y + static_cast(line_pixels); + + if (!API::is_focus_ready(window_)) + return false; + + auto caret = API::open_caret(window_, true); + + bool visible = false; + auto text_area = impl_->cview->view_area(); + + if (text_area.is_hit(coord) && (line_bottom > text_area.y)) + { + visible = true; + if (line_bottom > text_area.bottom()) + caret->dimension(nana::size(1, line_pixels - (line_bottom - text_area.bottom()))); + else if (caret->dimension().height != line_pixels) + reset_caret_pixels(); + } + + if (!attributes_.enable_caret) + visible = false; + + caret->visible(visible); + if (visible) + caret->position(coord); + + //Adjust the caret into screen when the caret position is modified by this function + if (stay_in_view && (!hit_text_area(coord))) + { + if (_m_adjust_view()) + impl_->cview->sync(false); + + impl_->try_refresh = sync_graph::refresh; + caret->visible(true); + return true; + } + return false; + } + + void text_editor::move_caret_end(bool update) + { + points_.caret.y = static_cast(impl_->textbase.lines()); + if (points_.caret.y) --points_.caret.y; + points_.caret.x = static_cast(impl_->textbase.getline(points_.caret.y).size()); + + if (update) + this->reset_caret(); + } + + void text_editor::reset_caret_pixels() const + { + API::open_caret(window_, true).get()->dimension({ 1, line_height() }); + } + + void text_editor::reset_caret(bool stay_in_view) + { + move_caret(points_.caret, stay_in_view); + } + + void text_editor::show_caret(bool isshow) + { + if (isshow == false || API::is_focus_ready(window_)) + API::open_caret(window_, true).get()->visible(isshow); + } + + bool text_editor::selected() const + { + return (select_.a != select_.b); + } + + bool text_editor::get_selected_points(nana::upoint &a, nana::upoint &b) const + { + if (select_.a == select_.b) + return false; + + if (select_.a < select_.b) + { + a = select_.a; + b = select_.b; + } + else + { + a = select_.b; + b = select_.a; + } + + return true; + } + + bool text_editor::select(bool yes) + { + if (yes) + { + select_.a.x = select_.a.y = 0; + select_.b.y = static_cast(impl_->textbase.lines()); + if (select_.b.y) --select_.b.y; + select_.b.x = static_cast(impl_->textbase.getline(select_.b.y).size()); + select_.mode_selection = selection::mode::method_selected; + impl_->try_refresh = sync_graph::refresh; + return true; + } + + select_.mode_selection = selection::mode::no_selected; + if (_m_cancel_select(0)) + { + impl_->try_refresh = sync_graph::refresh; + return true; + } + return false; + } + + bool text_editor::select_points(nana::upoint arg_a, nana::upoint arg_b) + { + select_.a = arg_a; + select_.b = arg_b; + select_.mode_selection = selection::mode::method_selected; impl_->try_refresh = sync_graph::refresh; return true; } - return false; - } - bool text_editor::select_points(nana::upoint arg_a, nana::upoint arg_b) - { - select_.a = arg_a; - select_.b = arg_b; - select_.mode_selection = selection::mode::method_selected; - impl_->try_refresh = sync_graph::refresh; - return true; - } - - void text_editor::set_end_caret(bool stay_in_view) - { - bool new_sel_end = (select_.b != points_.caret); - select_.b = points_.caret; - - if (new_sel_end || (stay_in_view && this->_m_adjust_view())) - impl_->try_refresh = sync_graph::refresh; - } - - bool text_editor::hit_text_area(const point& pos) const - { - return impl_->cview->view_area().is_hit(pos); - } - - bool text_editor::hit_select_area(nana::upoint pos, bool ignore_when_select_all) const - { - nana::upoint a, b; - if (get_selected_points(a, b)) + void text_editor::set_end_caret(bool stay_in_view) { - if (ignore_when_select_all) + bool new_sel_end = (select_.b != points_.caret); + select_.b = points_.caret; + + if (new_sel_end || (stay_in_view && this->_m_adjust_view())) + impl_->try_refresh = sync_graph::refresh; + } + + bool text_editor::hit_text_area(const point& pos) const + { + return impl_->cview->view_area().is_hit(pos); + } + + bool text_editor::hit_select_area(nana::upoint pos, bool ignore_when_select_all) const + { + nana::upoint a, b; + if (get_selected_points(a, b)) { - if (a.x == 0 && a.y == 0 && (b.y + 1) == static_cast(textbase().lines())) + if (ignore_when_select_all) { - //is select all - if (b.x == static_cast(textbase().getline(b.y).size())) - return false; + if (a.x == 0 && a.y == 0 && (b.y + 1) == static_cast(textbase().lines())) + { + //is select all + if (b.x == static_cast(textbase().getline(b.y).size())) + return false; + } + } + + 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 false; + } + + bool text_editor::move_select() + { + if (hit_select_area(points_.caret, true) || (select_.b == points_.caret)) + { + points_.caret = select_.b; + + if (this->_m_adjust_view()) + impl_->try_refresh = sync_graph::refresh; + + reset_caret(); + return true; + } + + if (_m_move_select(true)) + { + textbase().text_changed(); + this->_m_adjust_view(); + impl_->try_refresh = sync_graph::refresh; + return true; + } + return false; + } + + bool text_editor::mask(wchar_t ch) + { + if (mask_char_ == ch) + return false; + + mask_char_ = ch; + return true; + } + + unsigned text_editor::width_pixels() const + { + unsigned exclude_px = API::open_caret(window_, true).get()->dimension().width; + + if (attributes_.line_wrapped) + exclude_px += impl_->cview->extra_space(false); + + return (text_area_.area.width > exclude_px ? text_area_.area.width - exclude_px : 0); + } + + window text_editor::window_handle() const + { + return window_; + } + + const std::vector& text_editor::text_position() const + { + return impl_->text_position; + } + + void text_editor::focus_behavior(text_focus_behavior behavior) + { + select_.behavior = behavior; + } + + void text_editor::select_behavior(bool move_to_end) + { + select_.move_to_end = move_to_end; + } + + std::size_t text_editor::line_count(bool text_lines) const + { + if (text_lines) + return textbase().lines(); + + return impl_->capacities.behavior->take_lines(); + } + + std::shared_ptr text_editor::scroll_operation() const + { + return impl_->cview->scroll_operation(); + } + + void text_editor::draw_corner() + { + impl_->cview->draw_corner(graph_); + } + + void text_editor::render(bool has_focus) + { + const auto bgcolor = _m_bgcolor(); + + auto fgcolor = scheme_->foreground.get_color(); + if (!API::window_enabled(window_)) + fgcolor.blend(bgcolor, 0.5); + + if (API::widget_borderless(window_)) + graph_.rectangle(false, bgcolor); + + //Draw background + if (!API::dev::copy_transparent_background(window_, graph_)) + { + if (attributes_.enable_background) + graph_.rectangle(text_area_.area, true, bgcolor); + } + + if (impl_->customized_renderers.background) + impl_->customized_renderers.background(graph_, text_area_.area, bgcolor); + + if (impl_->counterpart.buffer && !text_area_.area.empty()) + 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, + //otherwise draw the tip string. + if ((false == textbase().empty()) || has_focus) + { + auto text_pos = _m_render_text(fgcolor); + + if (text_pos.empty()) + text_pos.emplace_back(upoint{}); + + if ((impl_->text_position_origin != impl_->cview->origin().y) || (text_pos != impl_->text_position)) + { + impl_->text_position_origin = impl_->cview->origin().y; + impl_->text_position.swap(text_pos); + if (event_handler_) + event_handler_->text_exposed(impl_->text_position); + } + } + else //Draw tip string + { + graph_.string({ text_area_.area.x - impl_->cview->origin().x, text_area_.area.y }, attributes_.tip_string, static_cast(0x787878)); + } + + if (impl_->text_position.empty()) + impl_->text_position.emplace_back(upoint{}); + + _m_draw_border(); + impl_->try_refresh = sync_graph::none; + } + //public: + void text_editor::put(std::wstring text, bool perform_event) + { + if (text.empty()) + return; + + auto undo_ptr = std::unique_ptr{ new undo_input_text(*this, text) }; + + undo_ptr->set_selected_text(); + + //Do not forget to assign the _m_erase_select() to caret + //because _m_put() will insert the text at the position where the caret is. + points_.caret = _m_erase_select(false); + + undo_ptr->set_caret_pos(); + points_.caret = _m_put(std::move(text), false); + + impl_->undo.push(std::move(undo_ptr)); + + _m_reset_content_size(true); + if (perform_event) + textbase().text_changed(); + + if (graph_) + { + if (this->_m_adjust_view()) + impl_->cview->sync(false); + + reset_caret(); + impl_->try_refresh = sync_graph::refresh; + } + } + + void text_editor::put(wchar_t ch) + { + std::wstring ch_str(1, ch); + + auto undo_ptr = std::unique_ptr{ new undo_input_text(*this, ch_str) }; + bool refresh = (select_.a != select_.b); + + undo_ptr->set_selected_text(); + if (refresh) + points_.caret = _m_erase_select(false); + + undo_ptr->set_caret_pos(); + + impl_->undo.push(std::move(undo_ptr)); + + auto secondary_before = impl_->capacities.behavior->take_lines(points_.caret.y); + textbase().insert(points_.caret, std::move(ch_str)); + _m_pre_calc_lines(points_.caret.y, 1); + + textbase().text_changed(); + + points_.caret.x++; + + _m_reset_content_size(); + + if (!refresh) + { + if (!_m_update_caret_line(secondary_before)) + draw_corner(); + } + else + impl_->try_refresh = sync_graph::refresh; + + } + + void text_editor::copy() const + { + //Disallows copying text if the text_editor is masked. + if (mask_char_) + return; + + auto text = _m_make_select_string(); + if (!text.empty()) + nana::system::dataexch().set(text, API::root(this->window_)); + } + + void text_editor::cut() + { + copy(); + del(); + } + + void text_editor::paste() + { + auto text = system::dataexch{}.wget(); + + if ((accepts::no_restrict == impl_->capacities.acceptive) || !impl_->capacities.pred_acceptive) + { + put(move(text), true); + return; + } + + //Check if the input is acceptable + for (auto i = text.begin(); i != text.end(); ++i) + { + if (_m_accepts(*i)) + { + if (accepts::no_restrict == impl_->capacities.acceptive) + put(*i); + + continue; + } + + if (accepts::no_restrict != impl_->capacities.acceptive) + { + text.erase(i, text.end()); + put(move(text), true); + } + break; + } + } + + void text_editor::enter(bool record_undo, bool perform_event) + { + if (false == attributes_.multi_lines) + return; + + auto undo_ptr = std::unique_ptr(new undo_input_text(*this, std::wstring(1, '\n'))); + + undo_ptr->set_selected_text(); + points_.caret = _m_erase_select(false); + + undo_ptr->set_caret_pos(); + + auto & textbase = this->textbase(); + + const string_type& lnstr = textbase.getline(points_.caret.y); + ++points_.caret.y; + + if (lnstr.size() > points_.caret.x) + { + //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 + textbase.insertln(points_.caret.y, lnstr.substr(points_.caret.x)); + textbase.erase(points_.caret.y - 1, points_.caret.x, rest_part_len); + } + else + { + if (textbase.lines() == 0) + textbase.insertln(0, std::wstring{}); + textbase.insertln(points_.caret.y, std::wstring{}); + } + + if (record_undo) + impl_->undo.push(std::move(undo_ptr)); + + impl_->capacities.behavior->add_lines(points_.caret.y - 1, 1); + _m_pre_calc_lines(points_.caret.y - 1, 2); + + points_.caret.x = 0; + + auto origin = impl_->cview->origin(); + origin.x = 0; + + if (impl_->indent.enabled) + { + if (impl_->indent.generator) + { + put(nana::to_wstring(impl_->indent.generator()), false); + } + 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) + put(text.substr(0, indent_pos), false); + else + put(text, false); + } + } + else + _m_reset_content_size(); + + if (perform_event) + textbase.text_changed(); + + auto origin_moved = impl_->cview->move_origin(origin - impl_->cview->origin()); + + if (this->_m_adjust_view() || origin_moved) + impl_->cview->sync(true); + } + + void text_editor::del() + { + if (select_.a == select_.b) + { + if (textbase().getline(points_.caret.y).size() > points_.caret.x) + { + ++points_.caret.x; + } + else if (points_.caret.y + 1 < textbase().lines()) + { //Move to next line + points_.caret.x = 0; + ++points_.caret.y; + } + else + return; //No characters behind the caret + } + + backspace(true, true); + } + + void text_editor::backspace(bool record_undo, bool perform_event) + { + auto undo_ptr = std::unique_ptr(new undo_backspace(*this)); + bool has_to_redraw = true; + if (select_.a == select_.b) + { + auto & textbase = this->textbase(); + if (points_.caret.x) + { + unsigned erase_number = 1; + --points_.caret.x; + + auto& lnstr = textbase.getline(points_.caret.y); + + undo_ptr->set_caret_pos(); + undo_ptr->set_removed(lnstr.substr(points_.caret.x, erase_number)); + auto secondary = impl_->capacities.behavior->take_lines(points_.caret.y); + textbase.erase(points_.caret.y, points_.caret.x, erase_number); + _m_pre_calc_lines(points_.caret.y, 1); + + if (!this->_m_adjust_view()) + { + _m_update_line(points_.caret.y, secondary); + has_to_redraw = false; + } + } + else if (points_.caret.y) + { + points_.caret.x = static_cast(textbase.getline(--points_.caret.y).size()); + textbase.merge(points_.caret.y); + impl_->capacities.behavior->merge_lines(points_.caret.y, points_.caret.y + 1); + undo_ptr->set_caret_pos(); + undo_ptr->set_removed(std::wstring(1, '\n')); + } + else + undo_ptr.reset(); + } + else + { + undo_ptr->set_selected_text(); + points_.caret = _m_erase_select(false); + undo_ptr->set_caret_pos(); + } + + if (record_undo) + impl_->undo.push(std::move(undo_ptr)); + + _m_reset_content_size(false); + + if (perform_event) + textbase().text_changed(); + + textbase().text_changed(); + + if (has_to_redraw) + { + this->_m_adjust_view(); + impl_->try_refresh = sync_graph::refresh; + } + } + + void text_editor::undo(bool reverse) + { + if (reverse) + impl_->undo.redo(); + else + impl_->undo.undo(); + + _m_reset_content_size(true); + + this->_m_adjust_view(); + impl_->try_refresh = sync_graph::refresh; + } + + void text_editor::set_undo_queue_length(std::size_t len) + { + impl_->undo.max_steps(len); + } + + void text_editor::move_ns(bool to_north) + { + const bool redraw_required = _m_cancel_select(0); + if (_m_move_caret_ns(to_north) || redraw_required) + impl_->try_refresh = sync_graph::refresh; + } + + void text_editor::move_left() + { + bool pending = true; + if (_m_cancel_select(1) == false) + { + if (points_.caret.x) + { + --points_.caret.x; + pending = false; + if (this->_m_adjust_view()) + impl_->try_refresh = sync_graph::refresh; + } + else if (points_.caret.y) //Move to previous line + points_.caret.x = static_cast(textbase().getline(--points_.caret.y).size()); + else + pending = false; + } + + if (pending && this->_m_adjust_view()) + impl_->try_refresh = sync_graph::refresh; + } + + void text_editor::move_right() + { + bool do_render = false; + if (_m_cancel_select(2) == false) + { + auto lnstr = textbase().getline(points_.caret.y); + if (lnstr.size() > points_.caret.x) + { + ++points_.caret.x; + 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 = this->_m_adjust_view(); + } + } + else + do_render = this->_m_adjust_view(); + + if (do_render) + impl_->try_refresh = sync_graph::refresh; + } + + void text_editor::_m_handle_move_key(const arg_keyboard& arg) + { + if (arg.shift && (select_.a == select_.b)) + select_.a = select_.b = points_.caret; + + auto origin = impl_->cview->origin(); + auto pos = points_.caret; + auto coord = _m_caret_to_coordinate(points_.caret, false); + + wchar_t key = arg.key; + + 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)) + { + pos = select_.a; + } + else if (pos.x != 0) + { + --pos.x; + } + else if (pos.y != 0) { + --pos.y; + pos.x = static_cast(textbase().getline(pos.y).size()); + } + break; + case keyboard::os_arrow_right: + if (select_.move_to_end && (select_.a != select_.b) && (!arg.shift)) + { + pos = select_.b; + } + else if (pos.x < text_length) + { + ++pos.x; + } + else if (pos.y != line_count - 1) + { + ++pos.y; + pos.x = 0; + } + break; + case keyboard::os_arrow_up: + coord.y -= static_cast(line_px); + break; + case keyboard::os_arrow_down: + coord.y += static_cast(line_px); + break; + case keyboard::os_home: + //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: + //move the caret to the end of the line + pos.x = static_cast(text_length); + + //move the caret to the end of the text if Ctrl is pressed + if (arg.ctrl) + pos.y = static_cast((line_count - 1) * line_px); + break; + case keyboard::os_pageup: + if (origin.y > 0) + { + auto off = coord - origin; + origin.y -= (std::min)(origin.y, static_cast(impl_->cview->view_area().height)); + coord = off + origin; + } + break; + case keyboard::os_pagedown: + if (impl_->cview->content_size().height > impl_->cview->view_area().height) + { + auto off = coord - origin; + origin.y = (std::min)(origin.y + static_cast(impl_->cview->view_area().height), static_cast(impl_->cview->content_size().height - impl_->cview->view_area().height)); + coord = off + origin; + } + break; + } + + if (pos == points_.caret) + { + impl_->cview->move_origin(origin - impl_->cview->origin()); + pos = _m_coordinate_to_caret(coord, false); + } + + 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 = pos; + break; + case keyboard::os_arrow_right: + case keyboard::os_arrow_down: + case keyboard::os_end: + case keyboard::os_pagedown: + select_.b = pos; + break; + } + } + else { + select_.b = pos; + select_.a = pos; + } + points_.caret = pos; + impl_->try_refresh = sync_graph::refresh; + this->_m_adjust_view(); + impl_->cview->sync(true); + this->reset_caret(); + } + } + + unsigned text_editor::_m_width_px(bool include_vs) const + { + unsigned exclude_px = API::open_caret(window_, true).get()->dimension().width; + + if (!include_vs) + exclude_px += impl_->cview->space(); + + return (text_area_.area.width > exclude_px ? text_area_.area.width - exclude_px : 0); + } + + void text_editor::_m_draw_border() + { + if (!API::widget_borderless(this->window_)) + { + if (impl_->customized_renderers.border) + { + impl_->customized_renderers.border(graph_, _m_bgcolor()); + } + else + { + ::nana::facade facade; + facade.draw(graph_, _m_bgcolor(), API::fgcolor(this->window_), ::nana::rectangle{ API::window_size(this->window_) }, API::element_state(this->window_)); + } + + if (!attributes_.line_wrapped) + { + auto exclude_px = API::open_caret(window_, true).get()->dimension().width; + int x = this->text_area_.area.x + static_cast(width_pixels()); + graph_.rectangle(rectangle{ x, this->text_area_.area.y, exclude_px, text_area_.area.height }, true, _m_bgcolor()); } } - 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; + draw_corner(); } - return false; - } - bool text_editor::move_select() - { - if(hit_select_area(points_.caret, true) || (select_.b == points_.caret)) + const upoint& text_editor::mouse_caret(const point& scrpos, bool stay_in_view) //From screen position { - points_.caret = select_.b; + points_.caret = _m_coordinate_to_caret(scrpos); - if (this->_m_adjust_view()) + if (stay_in_view && this->_m_adjust_view()) impl_->try_refresh = sync_graph::refresh; reset_caret(); - return true; + return points_.caret; } - if (_m_move_select(true)) + const upoint& text_editor::caret() const { - textbase().text_changed(); - this->_m_adjust_view(); - impl_->try_refresh = sync_graph::refresh; - return true; + return points_.caret; } - return false; - } - bool text_editor::mask(wchar_t ch) - { - if (mask_char_ == ch) + point text_editor::caret_screen_pos() const + { + return _m_caret_to_coordinate(points_.caret); + } + + bool text_editor::scroll(bool upwards, bool vert) + { + impl_->cview->scroll(!upwards, !vert); return false; - - mask_char_ = ch; - return true; - } - - unsigned text_editor::width_pixels() const - { - unsigned exclude_px = API::open_caret(window_, true).get()->dimension().width; - - if (attributes_.line_wrapped) - exclude_px += impl_->cview->extra_space(false); - - return (text_area_.area.width > exclude_px ? text_area_.area.width - exclude_px : 0); - } - - window text_editor::window_handle() const - { - return window_; - } - - const std::vector& text_editor::text_position() const - { - return impl_->text_position; - } - - void text_editor::focus_behavior(text_focus_behavior behavior) - { - select_.behavior = behavior; - } - - void text_editor::select_behavior(bool move_to_end) - { - select_.move_to_end = move_to_end; - } - - std::size_t text_editor::line_count(bool text_lines) const - { - if (text_lines) - return textbase().lines(); - - return impl_->capacities.behavior->take_lines(); - } - - std::shared_ptr text_editor::scroll_operation() const - { - return impl_->cview->scroll_operation(); - } - - void text_editor::draw_corner() - { - impl_->cview->draw_corner(graph_); - } - - void text_editor::render(bool has_focus) - { - const auto bgcolor = _m_bgcolor(); - - auto fgcolor = scheme_->foreground.get_color(); - if (!API::window_enabled(window_)) - fgcolor.blend(bgcolor, 0.5); - - if (API::widget_borderless(window_)) - graph_.rectangle(false, bgcolor); - - //Draw background - if (!API::dev::copy_transparent_background(window_, graph_)) - { - if (attributes_.enable_background) - graph_.rectangle(text_area_.area, true, bgcolor); } - if (impl_->customized_renderers.background) - impl_->customized_renderers.background(graph_, text_area_.area, bgcolor); - - if(impl_->counterpart.buffer && !text_area_.area.empty()) - 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, - //otherwise draw the tip string. - if ((false == textbase().empty()) || has_focus) + color text_editor::_m_draw_colored_area(paint::graphics& graph, const std::pair& row, bool whole_line) { - auto text_pos = _m_render_text(fgcolor); - - if (text_pos.empty()) - text_pos.emplace_back(upoint{}); - - if ((impl_->text_position_origin != impl_->cview->origin().y) || (text_pos != impl_->text_position)) + auto area = impl_->colored_area.find(row.first); + if (area) { - impl_->text_position_origin = impl_->cview->origin().y; - impl_->text_position.swap(text_pos); - if (event_handler_) - event_handler_->text_exposed(impl_->text_position); - } - } - else //Draw tip string - { - graph_.string({ text_area_.area.x - impl_->cview->origin().x, text_area_.area.y }, attributes_.tip_string, static_cast(0x787878)); - } - - if (impl_->text_position.empty()) - impl_->text_position.emplace_back(upoint{}); - - _m_draw_border(); - impl_->try_refresh = sync_graph::none; - } - //public: - void text_editor::put(std::wstring text, bool perform_event) - { - if (text.empty()) - return; - - auto undo_ptr = std::unique_ptr{ new undo_input_text(*this, text) }; - - undo_ptr->set_selected_text(); - - //Do not forget to assign the _m_erase_select() to caret - //because _m_put() will insert the text at the position where the caret is. - points_.caret = _m_erase_select(false); - - undo_ptr->set_caret_pos(); - points_.caret = _m_put(std::move(text), false); - - impl_->undo.push(std::move(undo_ptr)); - - _m_reset_content_size(true); - if (perform_event) - textbase().text_changed(); - - if(graph_) - { - if(this->_m_adjust_view()) - impl_->cview->sync(false); - - reset_caret(); - impl_->try_refresh = sync_graph::refresh; - } - } - - void text_editor::put(wchar_t ch) - { - std::wstring ch_str(1, ch); - - auto undo_ptr = std::unique_ptr{new undo_input_text(*this, ch_str)}; - bool refresh = (select_.a != select_.b); - - undo_ptr->set_selected_text(); - if(refresh) - points_.caret = _m_erase_select(false); - - undo_ptr->set_caret_pos(); - - impl_->undo.push(std::move(undo_ptr)); - - auto secondary_before = impl_->capacities.behavior->take_lines(points_.caret.y); - textbase().insert(points_.caret, std::move(ch_str)); - _m_pre_calc_lines(points_.caret.y, 1); - - textbase().text_changed(); - - points_.caret.x ++; - - _m_reset_content_size(); - - if (!refresh) - { - if (!_m_update_caret_line(secondary_before)) - draw_corner(); - } - else - impl_->try_refresh = sync_graph::refresh; - - } - - void text_editor::copy() const - { - //Disallows copying text if the text_editor is masked. - if (mask_char_) - return; - - auto text = _m_make_select_string(); - if (!text.empty()) - nana::system::dataexch().set(text, API::root(this->window_)); - } - - void text_editor::cut() - { - copy(); - del(); - } - - void text_editor::paste() - { - auto text = system::dataexch{}.wget(); - - if ((accepts::no_restrict == impl_->capacities.acceptive) || !impl_->capacities.pred_acceptive) - { - put(move(text), true); - return; - } - - //Check if the input is acceptable - for (auto i = text.begin(); i != text.end(); ++i) - { - if (_m_accepts(*i)) - { - if (accepts::no_restrict == impl_->capacities.acceptive) - put(*i); - - continue; - } - - if (accepts::no_restrict != impl_->capacities.acceptive) - { - text.erase(i, text.end()); - put(move(text), true); - } - break; - } - } - - void text_editor::enter(bool record_undo, bool perform_event) - { - if(false == attributes_.multi_lines) - return; - - auto undo_ptr = std::unique_ptr(new undo_input_text(*this, std::wstring(1, '\n'))); - - undo_ptr->set_selected_text(); - points_.caret = _m_erase_select(false); - - undo_ptr->set_caret_pos(); - - auto & textbase = this->textbase(); - - const string_type& lnstr = textbase.getline(points_.caret.y); - ++points_.caret.y; - - if(lnstr.size() > points_.caret.x) - { - //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 - textbase.insertln(points_.caret.y, lnstr.substr(points_.caret.x)); - textbase.erase(points_.caret.y - 1, points_.caret.x, rest_part_len); - } - else - { - if (textbase.lines() == 0) - textbase.insertln(0, std::wstring{}); - textbase.insertln(points_.caret.y, std::wstring{}); - } - - if (record_undo) - impl_->undo.push(std::move(undo_ptr)); - - impl_->capacities.behavior->add_lines(points_.caret.y - 1, 1); - _m_pre_calc_lines(points_.caret.y - 1, 2); - - points_.caret.x = 0; - - auto origin = impl_->cview->origin(); - origin.x = 0; - - if (impl_->indent.enabled) - { - if (impl_->indent.generator) - { - put(nana::to_wstring(impl_->indent.generator()), false); - } - 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) - put(text.substr(0, indent_pos), false); - else - put(text, false); - } - } - else - _m_reset_content_size(); - - if (perform_event) - textbase.text_changed(); - - auto origin_moved = impl_->cview->move_origin(origin - impl_->cview->origin()); - - if (this->_m_adjust_view() || origin_moved) - impl_->cview->sync(true); - } - - void text_editor::del() - { - if(select_.a == select_.b) - { - if(textbase().getline(points_.caret.y).size() > points_.caret.x) - { - ++points_.caret.x; - } - else if (points_.caret.y + 1 < textbase().lines()) - { //Move to next line - points_.caret.x = 0; - ++points_.caret.y; - } - else - return; //No characters behind the caret - } - - backspace(true, true); - } - - void text_editor::backspace(bool record_undo, bool perform_event) - { - auto undo_ptr = std::unique_ptr(new undo_backspace(*this)); - bool has_to_redraw = true; - if(select_.a == select_.b) - { - auto & textbase = this->textbase(); - if(points_.caret.x) - { - unsigned erase_number = 1; - --points_.caret.x; - - auto& lnstr = textbase.getline(points_.caret.y); - - undo_ptr->set_caret_pos(); - undo_ptr->set_removed(lnstr.substr(points_.caret.x, erase_number)); - auto secondary = impl_->capacities.behavior->take_lines(points_.caret.y); - textbase.erase(points_.caret.y, points_.caret.x, erase_number); - _m_pre_calc_lines(points_.caret.y, 1); - - if (!this->_m_adjust_view()) + if (!area->bgcolor.invisible()) { - _m_update_line(points_.caret.y, secondary); - has_to_redraw = false; + auto const height = line_height(); + + auto top = _m_caret_to_coordinate(upoint{ 0, static_cast(row.first) }).y; + std::size_t lines = 1; + + if (whole_line) + lines = impl_->capacities.behavior->take_lines(row.first); + else + top += static_cast(height * row.second); + + const rectangle area_r = { text_area_.area.x, top, width_pixels(), static_cast(height * lines) }; + + if (API::is_transparent_background(this->window_)) + graph.blend(area_r, area->bgcolor, 1); + else + graph.rectangle(area_r, true, area->bgcolor); } + + return area->fgcolor; } - else if (points_.caret.y) - { - points_.caret.x = static_cast(textbase.getline(--points_.caret.y).size()); - textbase.merge(points_.caret.y); - impl_->capacities.behavior->merge_lines(points_.caret.y, points_.caret.y + 1); - undo_ptr->set_caret_pos(); - undo_ptr->set_removed(std::wstring(1, '\n')); - } - else - undo_ptr.reset(); + return{}; } - else + + std::vector text_editor::_m_render_text(const color& text_color) { - undo_ptr->set_selected_text(); - points_.caret = _m_erase_select(false); - undo_ptr->set_caret_pos(); - } + std::vector line_indexes; + auto const behavior = this->impl_->capacities.behavior; + auto const line_count = textbase().lines(); - if (record_undo) - impl_->undo.push(std::move(undo_ptr)); + auto row = behavior->text_position_from_screen(impl_->cview->view_area().y); - _m_reset_content_size(false); + if (row.first >= line_count || graph_.empty()) + return line_indexes; - if (perform_event) - textbase().text_changed(); - - textbase().text_changed(); - - if(has_to_redraw) - { - this->_m_adjust_view(); - impl_->try_refresh = sync_graph::refresh; - } - } - - void text_editor::undo(bool reverse) - { - if (reverse) - impl_->undo.redo(); - else - impl_->undo.undo(); - - _m_reset_content_size(true); - - this->_m_adjust_view(); - impl_->try_refresh = sync_graph::refresh; - } - - void text_editor::set_undo_queue_length(std::size_t len) - { - impl_->undo.max_steps(len); - } - - void text_editor::move_ns(bool to_north) - { - const bool redraw_required = _m_cancel_select(0); - if (_m_move_caret_ns(to_north) || redraw_required) - impl_->try_refresh = sync_graph::refresh; - } - - void text_editor::move_left() - { - bool pending = true; - if(_m_cancel_select(1) == false) - { - if(points_.caret.x) + auto sections = behavior->line(row.first); + if (row.second < sections.size()) { - --points_.caret.x; - pending = false; - if (this->_m_adjust_view()) - impl_->try_refresh = sync_graph::refresh; - } - else if (points_.caret.y) //Move to previous line - points_.caret.x = static_cast(textbase().getline(--points_.caret.y).size()); - else - pending = false; - } + nana::upoint str_pos(0, static_cast(row.first)); + str_pos.x = static_cast(sections[row.second].begin - textbase().getline(row.first).c_str()); - if (pending && this->_m_adjust_view()) - impl_->try_refresh = sync_graph::refresh; - } + int top = _m_text_top_base() - (impl_->cview->origin().y % line_height()); + const unsigned pixels = line_height(); - void text_editor::move_right() - { - bool do_render = false; - if (_m_cancel_select(2) == false) - { - auto lnstr = textbase().getline(points_.caret.y); - if (lnstr.size() > points_.caret.x) - { - ++points_.caret.x; - 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 = this->_m_adjust_view(); - } - } - else - do_render = this->_m_adjust_view(); - - if (do_render) - impl_->try_refresh = sync_graph::refresh; - } - - void text_editor::_m_handle_move_key(const arg_keyboard& arg) - { - if (arg.shift && (select_.a == select_.b)) - select_.a = select_.b = points_.caret; - - auto origin = impl_->cview->origin(); - auto pos = points_.caret; - auto coord = _m_caret_to_coordinate(points_.caret, false); - - wchar_t key = arg.key; - - 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)) - { - pos = select_.a; - } - else if (pos.x != 0) - { - --pos.x; - } - else if (pos.y != 0) { - --pos.y; - pos.x = static_cast(textbase().getline(pos.y).size()); - } - break; - case keyboard::os_arrow_right: - if (select_.move_to_end && (select_.a != select_.b) && (!arg.shift)) - { - pos = select_.b; - } - else if (pos.x < text_length) - { - ++pos.x; - } - else if (pos.y != line_count - 1) - { - ++pos.y; - pos.x = 0; - } - break; - case keyboard::os_arrow_up: - coord.y -= static_cast(line_px); - break; - case keyboard::os_arrow_down: - coord.y += static_cast(line_px); - break; - case keyboard::os_home: - //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: - //move the caret to the end of the line - pos.x = static_cast(text_length); - - //move the caret to the end of the text if Ctrl is pressed - if(arg.ctrl) - pos.y = static_cast((line_count - 1) * line_px); - break; - case keyboard::os_pageup: - if(origin.y > 0) - { - auto off = coord - origin; - origin.y -= (std::min)(origin.y, static_cast(impl_->cview->view_area().height)); - coord = off + origin; - } - break; - case keyboard::os_pagedown: - if (impl_->cview->content_size().height > impl_->cview->view_area().height) - { - auto off = coord - origin; - origin.y = (std::min)(origin.y + static_cast(impl_->cview->view_area().height), static_cast(impl_->cview->content_size().height - impl_->cview->view_area().height)); - coord = off + origin; - } - break; - } - - if (pos == points_.caret) - { - impl_->cview->move_origin(origin - impl_->cview->origin()); - pos = _m_coordinate_to_caret(coord, false); - } - - 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 = pos; - break; - case keyboard::os_arrow_right: - case keyboard::os_arrow_down: - case keyboard::os_end: - case keyboard::os_pagedown: - select_.b = pos; - break; - } - } - else { - select_.b = pos; - select_.a = pos; - } - points_.caret = pos; - impl_->try_refresh = sync_graph::refresh; - this->_m_adjust_view(); - impl_->cview->sync(true); - this->reset_caret(); - } - } - - unsigned text_editor::_m_width_px(bool include_vs) const - { - unsigned exclude_px = API::open_caret(window_, true).get()->dimension().width; - - if (!include_vs) - exclude_px += impl_->cview->space(); - - return (text_area_.area.width > exclude_px ? text_area_.area.width - exclude_px : 0); - } - - void text_editor::_m_draw_border() - { - if (!API::widget_borderless(this->window_)) - { - if (impl_->customized_renderers.border) - { - impl_->customized_renderers.border(graph_, _m_bgcolor()); - } - else - { - ::nana::facade facade; - facade.draw(graph_, _m_bgcolor(), API::fgcolor(this->window_), ::nana::rectangle{ API::window_size(this->window_) }, API::element_state(this->window_)); - } - - if (!attributes_.line_wrapped) - { - auto exclude_px = API::open_caret(window_, true).get()->dimension().width; - int x = this->text_area_.area.x + static_cast(width_pixels()); - graph_.rectangle(rectangle{ x, this->text_area_.area.y, exclude_px, text_area_.area.height }, true, _m_bgcolor()); - } - } - - draw_corner(); - } - - const upoint& text_editor::mouse_caret(const point& scrpos, bool stay_in_view) //From screen position - { - points_.caret = _m_coordinate_to_caret(scrpos); - - if (stay_in_view && this->_m_adjust_view()) - impl_->try_refresh = sync_graph::refresh; - - reset_caret(); - return points_.caret; - } - - const upoint& text_editor::caret() const - { - return points_.caret; - } - - point text_editor::caret_screen_pos() const - { - return _m_caret_to_coordinate(points_.caret); - } - - bool text_editor::scroll(bool upwards, bool vert) - { - impl_->cview->scroll(!upwards, !vert); - return false; - } - - color text_editor::_m_draw_colored_area(paint::graphics& graph, const std::pair& row, bool whole_line) - { - auto area = impl_->colored_area.find(row.first); - if (area) - { - if (!area->bgcolor.invisible()) - { - auto const height = line_height(); - - auto top = _m_caret_to_coordinate(upoint{ 0, static_cast(row.first) }).y; - std::size_t lines = 1; - - if (whole_line) - lines = impl_->capacities.behavior->take_lines(row.first); - else - top += static_cast(height * row.second); - - const rectangle area_r = { text_area_.area.x, top, width_pixels(), static_cast(height * lines) }; - - if (API::is_transparent_background(this->window_)) - graph.blend(area_r, area->bgcolor, 1); - else - graph.rectangle(area_r, true, area->bgcolor); - } - - return area->fgcolor; - } - return{}; - } - - std::vector text_editor::_m_render_text(const color& text_color) - { - std::vector line_indexes; - auto const behavior = this->impl_->capacities.behavior; - auto const line_count = textbase().lines(); - - auto row = behavior->text_position_from_screen(impl_->cview->view_area().y); - - if (row.first >= line_count || graph_.empty()) - return line_indexes; - - auto sections = behavior->line(row.first); - if (row.second < sections.size()) - { - nana::upoint str_pos(0, static_cast(row.first)); - str_pos.x = static_cast(sections[row.second].begin - textbase().getline(row.first).c_str()); - - int top = _m_text_top_base() - (impl_->cview->origin().y % line_height()); - const unsigned pixels = line_height(); - - const std::size_t scrlines = screen_lines() + 1; - for (std::size_t pos = 0; pos < scrlines; ++pos, top += pixels) - { - if (row.first >= line_count) - break; - - auto fgcolor = _m_draw_colored_area(graph_, row, false); - if (fgcolor.invisible()) - fgcolor = text_color; - - sections = behavior->line(row.first); - if (row.second < sections.size()) + const std::size_t scrlines = screen_lines() + 1; + for (std::size_t pos = 0; pos < scrlines; ++pos, top += pixels) { - auto const & sct = sections[row.second]; + if (row.first >= line_count) + break; - _m_draw_string(top, fgcolor, str_pos, sct, true); - line_indexes.emplace_back(str_pos); - ++row.second; - if (row.second >= sections.size()) + auto fgcolor = _m_draw_colored_area(graph_, row, false); + if (fgcolor.invisible()) + fgcolor = text_color; + + sections = behavior->line(row.first); + if (row.second < sections.size()) { - ++row.first; - row.second = 0; - str_pos.x = 0; - ++str_pos.y; + auto const & sct = sections[row.second]; + + _m_draw_string(top, fgcolor, str_pos, sct, true); + line_indexes.emplace_back(str_pos); + ++row.second; + if (row.second >= sections.size()) + { + ++row.first; + row.second = 0; + str_pos.x = 0; + ++str_pos.y; + } + else + str_pos.x += static_cast(sct.end - sct.begin); } else - str_pos.x += static_cast(sct.end - sct.begin); + break; } - else - break; } + + return line_indexes; } - return line_indexes; - } - - void text_editor::_m_pre_calc_lines(std::size_t line_off, std::size_t lines) - { - unsigned width_px = width_pixels(); - for (auto pos = line_off, end = line_off + lines; pos != end; ++pos) - this->impl_->capacities.behavior->pre_calc_line(pos, width_px); - } - - 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); - - std::size_t lines = 0; //lines before the caret line; - for (std::size_t i = 0; i < pos.y; ++i) + void text_editor::_m_pre_calc_lines(std::size_t line_off, std::size_t lines) { - lines += behavior->take_lines(i); + unsigned width_px = width_pixels(); + for (auto pos = line_off, end = line_off + lines; pos != end; ++pos) + this->impl_->capacities.behavior->pre_calc_line(pos, width_px); } - const text_section * sct_ptr = nullptr; - nana::point scrpos; - if (0 != pos.x) + nana::point text_editor::_m_caret_to_coordinate(nana::upoint pos, bool to_screen_coordinate) const { - std::wstring str; + auto const behavior = impl_->capacities.behavior; + auto const sections = behavior->line(pos.y); - std::size_t sct_pos = 0; - for (auto & sct : sections) + std::size_t lines = 0; //lines before the caret line; + for (std::size_t i = 0; i < pos.y; ++i) { - std::size_t chsize = sct.end - sct.begin; - str.clear(); - if (mask_char_) - str.append(chsize, mask_char_); - else - str.append(sct.begin, sct.end); + lines += behavior->take_lines(i); + } - //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)) + const text_section * sct_ptr = nullptr; + nana::point scrpos; + if (0 != pos.x) + { + std::wstring str; + + std::size_t sct_pos = 0; + for (auto & sct : sections) { - sct_ptr = &sct; - if (pos.x == chsize) - scrpos.x = _m_text_extent_size(str.c_str(), sct.end - sct.begin).width; + std::size_t chsize = sct.end - sct.begin; + str.clear(); + if (mask_char_) + str.append(chsize, mask_char_); else - scrpos.x = _m_pixels_by_char(str, pos.x); - break; + str.append(sct.begin, sct.end); + + //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) + scrpos.x = _m_text_extent_size(str.c_str(), sct.end - sct.begin).width; + else + scrpos.x = _m_pixels_by_char(str, pos.x); + break; + } + else + { + pos.x -= static_cast(chsize); + ++lines; + } + + ++sct_pos; } + } + + if (!sct_ptr) + { + if (sections.empty()) + scrpos.x += _m_text_x({}); else - { - pos.x -= static_cast(chsize); - ++lines; - } - - ++sct_pos; + scrpos.x += _m_text_x(sections.front()); } + else + scrpos.x += _m_text_x(*sct_ptr); + + scrpos.y = static_cast(lines * line_height()); + + if (!to_screen_coordinate) + { + //_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 += this->_m_text_top_base() - impl_->cview->origin().y; + + return scrpos; } - if (!sct_ptr) + 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); if (sections.empty()) - scrpos.x += _m_text_x({}); - else - scrpos.x += _m_text_x(sections.front()); - } - else - scrpos.x += _m_text_x(*sct_ptr); + return{ 0, static_cast(row.first) }; - scrpos.y = static_cast(lines * line_height()); + //First of all, find the text of secondary. + auto real_str = sections[row.second]; - if (!to_screen_coordinate) - { - //_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 += this->_m_text_top_base() - impl_->cview->origin().y; + auto text_ptr = real_str.begin; + const auto text_size = real_str.end - real_str.begin; - return scrpos; - } - - 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); - if (sections.empty()) - return{ 0, static_cast(row.first) }; - - //First of all, find the text of secondary. - auto real_str = sections[row.second]; - - auto text_ptr = real_str.begin; - const auto text_size = real_str.end - real_str.begin; - - std::wstring mask_str; - if (mask_char_) - { - mask_str.resize(text_size, mask_char_); - text_ptr = mask_str.c_str(); - } - - auto const reordered = unicode_reorder(text_ptr, text_size); - - nana::upoint res(static_cast(real_str.begin - sections.front().begin), static_cast(row.first)); - - scrpos.x = (std::max)(0, (scrpos.x - _m_text_x(sections[row.second]))); - - for (auto & ent : reordered) - { - auto str_px = static_cast(_m_text_extent_size(ent.begin, ent.end - ent.begin).width); - if (scrpos.x <= str_px) + std::wstring mask_str; + if (mask_char_) { - res.x += _m_char_by_pixels(ent, scrpos.x) + static_cast(ent.begin - text_ptr); - return res; + mask_str.resize(text_size, mask_char_); + text_ptr = mask_str.c_str(); } - scrpos.x -= str_px; - } - //move the caret to the end of this section. - res.x = static_cast(text_size); - for (std::size_t i = 0; i < row.second; ++i) - res.x += static_cast(sections[i].end - sections[i].begin); + auto const reordered = unicode_reorder(text_ptr, text_size); - return res; - } + nana::upoint res(static_cast(real_str.begin - sections.front().begin), static_cast(row.first)); - bool text_editor::_m_pos_from_secondary(std::size_t textline, const nana::upoint& secondary, unsigned & pos) - { - if (textline >= textbase().lines()) - return false; + scrpos.x = (std::max)(0, (scrpos.x - _m_text_x(sections[row.second]))); - auto sections = impl_->capacities.behavior->line(textline); - - if (secondary.y >= sections.size()) - return false; - - auto const & sct = sections[secondary.y]; - - auto chptr = sct.begin + (std::min)(secondary.x, static_cast(sct.end - sct.begin)); - pos = static_cast(chptr - textbase().getline(textline).c_str()); - return true; - } - - - bool text_editor::_m_pos_secondary(const nana::upoint& charpos, nana::upoint& secondary_pos) const - { - if (charpos.y >= textbase().lines()) - return false; - - secondary_pos.x = charpos.x; - secondary_pos.y = 0; - - auto sections = impl_->capacities.behavior->line(charpos.y); - unsigned len = 0; - for (auto & sct : sections) - { - len = static_cast(sct.end - sct.begin); - if (len >= secondary_pos.x) - return true; - - ++secondary_pos.y; - secondary_pos.x -= len; - } - --secondary_pos.y; - secondary_pos.x = len; - return true; - } - - bool text_editor::_m_move_caret_ns(bool to_north) - { - auto const behavior = impl_->capacities.behavior; - - nana::upoint secondary_pos; - _m_pos_secondary(points_.caret, secondary_pos); - - if (to_north) //North - { - if (0 == secondary_pos.y) + for (auto & ent : reordered) { - if (0 == points_.caret.y) - return false; - - --points_.caret.y; - secondary_pos.y = static_cast(behavior->take_lines(points_.caret.y)) - 1; - } - else - { - //Test if out of screen - if (static_cast(points_.caret.y) < _m_text_topline()) + auto str_px = static_cast(_m_text_extent_size(ent.begin, ent.end - ent.begin).width); + if (scrpos.x <= str_px) { - auto origin = impl_->cview->origin(); - origin.y = static_cast(points_.caret.y) * line_height(); - impl_->cview->move_origin(origin - impl_->cview->origin()); + res.x += _m_char_by_pixels(ent, scrpos.x) + static_cast(ent.begin - text_ptr); + return res; } - - --secondary_pos.y; + scrpos.x -= str_px; } - } - else //South - { - ++secondary_pos.y; - if (secondary_pos.y >= behavior->take_lines(points_.caret.y)) - { - secondary_pos.y = 0; - if (points_.caret.y + 1 >= textbase().lines()) - return false; - ++points_.caret.y; - } + //move the caret to the end of this section. + res.x = static_cast(text_size); + for (std::size_t i = 0; i < row.second; ++i) + res.x += static_cast(sections[i].end - sections[i].begin); + + return res; } - _m_pos_from_secondary(points_.caret.y, secondary_pos, points_.caret.x); - return this->_m_adjust_view(); - } - - void text_editor::_m_update_line(std::size_t pos, std::size_t secondary_count_before) - { - auto behavior = impl_->capacities.behavior; - if (behavior->take_lines(pos) == secondary_count_before) + bool text_editor::_m_pos_from_secondary(std::size_t textline, const nana::upoint& secondary, unsigned & pos) { - auto top = _m_caret_to_coordinate(upoint{ 0, static_cast(pos) }).y; + if (textline >= textbase().lines()) + return false; - const unsigned pixels = line_height(); - const rectangle update_area = { text_area_.area.x, top, width_pixels(), static_cast(pixels * secondary_count_before) }; + auto sections = impl_->capacities.behavior->line(textline); - if (!API::dev::copy_transparent_background(window_, update_area, graph_, update_area.position())) - { - _m_draw_colored_area(graph_, { pos, 0 }, true); - graph_.rectangle(update_area, true, API::bgcolor(window_)); - } - else - _m_draw_colored_area(graph_, { pos, 0 }, true); + if (secondary.y >= sections.size()) + return false; - auto fgcolor = API::fgcolor(window_); - auto text_ptr = textbase().getline(pos).c_str(); + auto const & sct = sections[secondary.y]; - auto sections = behavior->line(pos); - for (auto & sct : sections) - { - _m_draw_string(top, fgcolor, nana::upoint(static_cast(sct.begin - text_ptr), points_.caret.y), sct, true); - top += pixels; - } - - _m_draw_border(); - impl_->try_refresh = sync_graph::lazy_refresh; - } - else - impl_->try_refresh = sync_graph::refresh; - } - - bool text_editor::_m_accepts(char_type ch) const - { - if (accepts::no_restrict == impl_->capacities.acceptive) - { - if (impl_->capacities.pred_acceptive) - return impl_->capacities.pred_acceptive(ch); + auto chptr = sct.begin + (std::min)(secondary.x, static_cast(sct.end - sct.begin)); + pos = static_cast(chptr - textbase().getline(textline).c_str()); return true; } - //Checks the input whether it meets the requirement for a numeric. - auto str = text(); - if ('+' == ch || '-' == ch) - return str.empty(); - - if((accepts::real == impl_->capacities.acceptive) && ('.' == ch)) - return (str.find(L'.') == str.npos); - - return ('0' <= ch && ch <= '9'); - } - - ::nana::color text_editor::_m_bgcolor() const - { - return (!API::window_enabled(window_) ? static_cast(0xE0E0E0) : API::bgcolor(window_)); - } - - void text_editor::_m_reset_content_size(bool calc_lines) - { - size csize; - - if (this->attributes_.line_wrapped) + bool text_editor::_m_pos_secondary(const nana::upoint& charpos, nana::upoint& secondary_pos) const { - //detect if vertical scrollbar is required - auto const max_lines = screen_lines(true); + if (charpos.y >= textbase().lines()) + return false; - if (calc_lines) + secondary_pos.x = charpos.x; + secondary_pos.y = 0; + + auto sections = impl_->capacities.behavior->line(charpos.y); + unsigned len = 0; + for (auto & sct : sections) { - auto text_lines = textbase().lines(); - if (text_lines <= max_lines) + len = static_cast(sct.end - sct.begin); + if (len >= secondary_pos.x) + return true; + + ++secondary_pos.y; + secondary_pos.x -= len; + } + --secondary_pos.y; + secondary_pos.x = len; + return true; + } + + bool text_editor::_m_move_caret_ns(bool to_north) + { + auto const behavior = impl_->capacities.behavior; + + nana::upoint secondary_pos; + _m_pos_secondary(points_.caret, secondary_pos); + + if (to_north) //North + { + if (0 == secondary_pos.y) { - impl_->capacities.behavior->prepare(); + if (0 == points_.caret.y) + return false; - auto const width_px = _m_width_px(true); - - std::size_t lines = 0; - for (std::size_t i = 0; i < text_lines; ++i) + --points_.caret.y; + secondary_pos.y = static_cast(behavior->take_lines(points_.caret.y)) - 1; + } + else + { + //Test if out of screen + if (static_cast(points_.caret.y) < _m_text_topline()) { - impl_->capacities.behavior->pre_calc_line(i, width_px); - lines += impl_->capacities.behavior->take_lines(i); + auto origin = impl_->cview->origin(); + origin.y = static_cast(points_.caret.y) * line_height(); + impl_->cview->move_origin(origin - impl_->cview->origin()); + } - if (lines > max_lines) + --secondary_pos.y; + } + } + else //South + { + ++secondary_pos.y; + if (secondary_pos.y >= behavior->take_lines(points_.caret.y)) + { + secondary_pos.y = 0; + if (points_.caret.y + 1 >= textbase().lines()) + return false; + + ++points_.caret.y; + } + } + + _m_pos_from_secondary(points_.caret.y, secondary_pos, points_.caret.x); + return this->_m_adjust_view(); + } + + void text_editor::_m_update_line(std::size_t pos, std::size_t secondary_count_before) + { + auto behavior = impl_->capacities.behavior; + if (behavior->take_lines(pos) == secondary_count_before) + { + auto top = _m_caret_to_coordinate(upoint{ 0, static_cast(pos) }).y; + + const unsigned pixels = line_height(); + const rectangle update_area = { text_area_.area.x, top, width_pixels(), static_cast(pixels * secondary_count_before) }; + + if (!API::dev::copy_transparent_background(window_, update_area, graph_, update_area.position())) + { + _m_draw_colored_area(graph_, { pos, 0 }, true); + graph_.rectangle(update_area, true, API::bgcolor(window_)); + } + else + _m_draw_colored_area(graph_, { pos, 0 }, true); + + auto fgcolor = API::fgcolor(window_); + auto text_ptr = textbase().getline(pos).c_str(); + + auto sections = behavior->line(pos); + for (auto & sct : sections) + { + _m_draw_string(top, fgcolor, nana::upoint(static_cast(sct.begin - text_ptr), points_.caret.y), sct, true); + top += pixels; + } + + _m_draw_border(); + impl_->try_refresh = sync_graph::lazy_refresh; + } + else + impl_->try_refresh = sync_graph::refresh; + } + + bool text_editor::_m_accepts(char_type ch) const + { + if (accepts::no_restrict == impl_->capacities.acceptive) + { + if (impl_->capacities.pred_acceptive) + return impl_->capacities.pred_acceptive(ch); + return true; + } + + //Checks the input whether it meets the requirement for a numeric. + auto str = text(); + + if ('+' == ch || '-' == ch) + return str.empty(); + + if ((accepts::real == impl_->capacities.acceptive) && ('.' == ch)) + return (str.find(L'.') == str.npos); + + return ('0' <= ch && ch <= '9'); + } + + ::nana::color text_editor::_m_bgcolor() const + { + return (!API::window_enabled(window_) ? static_cast(0xE0E0E0) : API::bgcolor(window_)); + } + + void text_editor::_m_reset_content_size(bool calc_lines) + { + size csize; + + if (this->attributes_.line_wrapped) + { + //detect if vertical scrollbar is required + auto const max_lines = screen_lines(true); + + if (calc_lines) + { + auto text_lines = textbase().lines(); + if (text_lines <= max_lines) + { + impl_->capacities.behavior->prepare(); + + auto const width_px = _m_width_px(true); + + std::size_t lines = 0; + for (std::size_t i = 0; i < text_lines; ++i) { - text_lines = lines; - break; + impl_->capacities.behavior->pre_calc_line(i, width_px); + lines += impl_->capacities.behavior->take_lines(i); + + if (lines > max_lines) + { + text_lines = lines; + break; + } } } + + //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; } - - //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(width_pixels()); + if (calc_lines) + impl_->capacities.behavior->pre_calc_lines(width_pixels()); - auto maxline = textbase().max_line(); - csize.width = _m_text_extent_size(textbase().getline(maxline.first).c_str(), maxline.second).width + caret_size().width; - } - - csize.height = static_cast(impl_->capacities.behavior->take_lines() * line_height()); - - impl_->cview->content_size(csize); - } - - void text_editor::_m_reset() - { - points_.caret.x = points_.caret.y = 0; - impl_->cview->move_origin(point{} -impl_->cview->origin()); - select_.a = select_.b; - } - - nana::upoint text_editor::_m_put(std::wstring text, bool perform_event) - { - auto & textbase = this->textbase(); - auto crtpos = points_.caret; - std::vector> lines; - if (_m_resolve_text(text, lines) && attributes_.multi_lines) - { - auto str_orig = textbase.getline(crtpos.y); - - auto const subpos = lines.front(); - auto substr = text.substr(subpos.first, subpos.second - subpos.first); - - if (str_orig.size() == crtpos.x) - textbase.insert(crtpos, std::move(substr)); - else - textbase.replace(crtpos.y, str_orig.substr(0, crtpos.x) + substr); - - //There are at least 2 elements in lines - for (auto i = lines.begin() + 1, end = lines.end() - 1; i != end; ++i) - { - textbase.insertln(++crtpos.y, text.substr(i->first, i->second - i->first)); + auto maxline = textbase().max_line(); + csize.width = _m_text_extent_size(textbase().getline(maxline.first).c_str(), maxline.second).width + caret_size().width; } - auto backpos = lines.back(); - textbase.insertln(++crtpos.y, text.substr(backpos.first, backpos.second - backpos.first) + str_orig.substr(crtpos.x)); - crtpos.x = static_cast(backpos.second - backpos.first); + csize.height = static_cast(impl_->capacities.behavior->take_lines() * line_height()); - impl_->capacities.behavior->add_lines(points_.caret.y, lines.size() - 1); - _m_pre_calc_lines(points_.caret.y, lines.size()); + impl_->cview->content_size(csize); } - else + + void text_editor::_m_reset() { - //Just insert the first line of text if the text is multilines. - if (lines.size() > 1) - text = text.substr(lines.front().first, lines.front().second - lines.front().first); - - crtpos.x += static_cast(text.size()); - textbase.insert(points_.caret, std::move(text)); - - _m_pre_calc_lines(crtpos.y, 1); + points_.caret.x = points_.caret.y = 0; + impl_->cview->move_origin(point{} -impl_->cview->origin()); + select_.a = select_.b; } - if (perform_event) - textbase.text_changed(); - - return crtpos; - } - - nana::upoint text_editor::_m_erase_select(bool perform_event) - { - nana::upoint a, b; - if (get_selected_points(a, b)) + nana::upoint text_editor::_m_put(std::wstring text, bool perform_event) { auto & textbase = this->textbase(); - if(a.y != b.y) + auto crtpos = points_.caret; + std::vector> lines; + if (_m_resolve_text(text, lines) && attributes_.multi_lines) { - textbase.erase(a.y, a.x, std::wstring::npos); - textbase.erase(a.y + 1, b.y - a.y - 1); + auto str_orig = textbase.getline(crtpos.y); - textbase.erase(a.y + 1, 0, b.x); - textbase.merge(a.y); + auto const subpos = lines.front(); + auto substr = text.substr(subpos.first, subpos.second - subpos.first); - impl_->capacities.behavior->merge_lines(a.y, b.y); + if (str_orig.size() == crtpos.x) + textbase.insert(crtpos, std::move(substr)); + else + textbase.replace(crtpos.y, str_orig.substr(0, crtpos.x) + substr); + + //There are at least 2 elements in lines + for (auto i = lines.begin() + 1, end = lines.end() - 1; i != end; ++i) + { + textbase.insertln(++crtpos.y, text.substr(i->first, i->second - i->first)); + } + + auto backpos = lines.back(); + textbase.insertln(++crtpos.y, text.substr(backpos.first, backpos.second - backpos.first) + str_orig.substr(crtpos.x)); + crtpos.x = static_cast(backpos.second - backpos.first); + + impl_->capacities.behavior->add_lines(points_.caret.y, lines.size() - 1); + _m_pre_calc_lines(points_.caret.y, lines.size()); } else { - textbase.erase(a.y, a.x, b.x - a.x); - _m_pre_calc_lines(a.y, 1); + //Just insert the first line of text if the text is multilines. + if (lines.size() > 1) + text = text.substr(lines.front().first, lines.front().second - lines.front().first); + + crtpos.x += static_cast(text.size()); + textbase.insert(points_.caret, std::move(text)); + + _m_pre_calc_lines(crtpos.y, 1); } if (perform_event) textbase.text_changed(); - select_.a = select_.b; - return a; + return crtpos; } - return points_.caret; - } - - std::wstring text_editor::_m_make_select_string() const - { - std::wstring text; - - nana::upoint a, b; - if (get_selected_points(a, b)) + nana::upoint text_editor::_m_erase_select(bool perform_event) { - auto & textbase = this->textbase(); - - if (a.y != b.y) + nana::upoint a, b; + if (get_selected_points(a, b)) { - text = textbase.getline(a.y).substr(a.x); - text += L"\r\n"; - for (unsigned i = a.y + 1; i < b.y; ++i) + auto & textbase = this->textbase(); + if (a.y != b.y) { - text += textbase.getline(i); - text += L"\r\n"; + textbase.erase(a.y, a.x, std::wstring::npos); + textbase.erase(a.y + 1, b.y - a.y - 1); + + textbase.erase(a.y + 1, 0, b.x); + textbase.merge(a.y); + + impl_->capacities.behavior->merge_lines(a.y, b.y); } - text += textbase.getline(b.y).substr(0, b.x); - } - else - return textbase.getline(a.y).substr(a.x, b.x - a.x); - } - - return text; - } - - std::size_t eat_endl(const wchar_t* str, std::size_t pos) - { - auto ch = str[pos]; - if (0 == ch) - return pos; - - const wchar_t * endlstr; - switch (ch) - { - case L'\n': - endlstr = L"\n\r"; - break; - case L'\r': - endlstr = L"\r\n"; - break; - default: - return pos; - } - - if (std::memcmp(str + pos, endlstr, sizeof(wchar_t) * 2) == 0) - return pos + 2; - - return pos + 1; - } - - bool text_editor::_m_resolve_text(const std::wstring& text, std::vector> & lines) - { - auto const text_str = text.c_str(); - std::size_t begin = 0; - while (true) - { - auto pos = text.find_first_of(L"\r\n", begin); - if (text.npos == pos) - { - if (!lines.empty()) - lines.emplace_back(begin, text.size()); - break; - } - - lines.emplace_back(begin, pos); - - pos = eat_endl(text_str, pos); - - begin = text.find_first_not_of(L"\r\n", pos); - - //The number of new lines minus one - const auto chp_end = text_str + (begin == text.npos ? text.size() : begin); - - for (auto chp = text_str + pos; chp != chp_end; ++chp) - { - auto eats = eat_endl(chp, 0); - if (eats) + else { - lines.emplace_back(); - chp += (eats - 1); + textbase.erase(a.y, a.x, b.x - a.x); + _m_pre_calc_lines(a.y, 1); } + + if (perform_event) + textbase.text_changed(); + + select_.a = select_.b; + return a; } - if (text.npos == begin) - { - lines.emplace_back(); - break; - } - } - return !lines.empty(); - } - - bool text_editor::_m_cancel_select(int align) - { - upoint a, b; - if (get_selected_points(a, b)) - { - //domain of algin = [0, 2] - if (align) - { - this->points_.caret = (1 == align ? a : b); - this->_m_adjust_view(); - } - - select_.a = select_.b = points_.caret; - reset_caret(); - return true; - } - return false; - } - - nana::size text_editor::_m_text_extent_size(const char_type* str, size_type n) const - { - if(mask_char_) - { - std::wstring maskstr; - maskstr.append(n, mask_char_); - return graph_.text_extent_size(maskstr); - } -#ifdef _nana_std_has_string_view - return graph_.text_extent_size(std::basic_string_view(str, n)); -#else - return graph_.text_extent_size(str, static_cast(n)); -#endif - } - - bool text_editor::_m_adjust_view() - { - auto const view_area = impl_->cview->view_area(); - - auto const line_px = static_cast(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; - - unsigned extra_count_horz = 4; - unsigned extra_count_vert = 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 extra = points_.caret; - - if (coord.x < origin.x) - { - 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(caret_size().width) >= origin.x + static_cast(view_area.width)) - { - extra.x = (std::min)(static_cast(textbase().getline(points_.caret.y).size()), points_.caret.x + extra_count_horz); - auto new_origin = _m_caret_to_coordinate(extra, false).x + static_cast(caret_size().width) - static_cast(view_area.width); - moved_origin.x = new_origin - origin.x; - } + return points_.caret; } - auto extra_px = static_cast(line_px * extra_count_vert); - - if (coord.y < origin.y) + std::wstring text_editor::_m_make_select_string() const { - //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(view_area.height)) - { - //Bottom of caret is greater than the bottom of view - - auto const bottom = static_cast(impl_->capacities.behavior->take_lines() * line_px); - auto new_origin = (std::min)(coord.y + line_px + extra_px, bottom) - static_cast(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) - { - if (!attributes_.editable) - return false; - - nana::upoint caret = points_.caret; - const auto text = _m_make_select_string(); - if (!text.empty()) - { - auto undo_ptr = std::unique_ptr(new undo_move_text(*this)); - undo_ptr->set_selected_text(); - - //Determines whether the caret is at left or at right. The select_.b indicates the caret position when finish selection - const bool at_left = (select_.b < select_.a); + std::wstring text; nana::upoint a, b; - get_selected_points(a, b); - if (caret.y < a.y || (caret.y == a.y && caret.x < a.x)) - {//forward - undo_ptr->set_caret_pos(); - - _m_erase_select(false); - _m_put(text, false); - - select_.a = caret; - select_.b.y = b.y + (caret.y - a.y); - } - else if (b.y < caret.y || (caret.y == b.y && b.x < caret.x)) + if (get_selected_points(a, b)) { - undo_ptr->set_caret_pos(); + auto & textbase = this->textbase(); - _m_put(text, false); - _m_erase_select(false); - - select_.b.y = caret.y; - select_.a.y = caret.y - (b.y - a.y); - select_.a.x = caret.x - (caret.y == b.y ? (b.x - a.x) : 0); + if (a.y != b.y) + { + text = textbase.getline(a.y).substr(a.x); + text += L"\r\n"; + for (unsigned i = a.y + 1; i < b.y; ++i) + { + text += textbase.getline(i); + text += L"\r\n"; + } + text += textbase.getline(b.y).substr(0, b.x); + } + else + return textbase.getline(a.y).substr(a.x, b.x - a.x); } - select_.b.x = b.x + (a.y == b.y ? (select_.a.x - a.x) : 0); - //restores the caret at the proper end. - if ((select_.b < select_.a) != at_left) - std::swap(select_.a, select_.b); + return text; + } - if (record_undo) + std::size_t eat_endl(const wchar_t* str, std::size_t pos) + { + auto ch = str[pos]; + if (0 == ch) + return pos; + + const wchar_t * endlstr; + switch (ch) { - undo_ptr->set_destination(select_.a, select_.b); - impl_->undo.push(std::move(undo_ptr)); + case L'\n': + endlstr = L"\n\r"; + break; + case L'\r': + endlstr = L"\r\n"; + break; + default: + return pos; } - points_.caret = select_.b; - reset_caret(); + if (std::memcmp(str + pos, endlstr, sizeof(wchar_t) * 2) == 0) + return pos + 2; + + return pos + 1; + } + + bool text_editor::_m_resolve_text(const std::wstring& text, std::vector> & lines) + { + auto const text_str = text.c_str(); + std::size_t begin = 0; + while (true) + { + auto pos = text.find_first_of(L"\r\n", begin); + if (text.npos == pos) + { + if (!lines.empty()) + lines.emplace_back(begin, text.size()); + break; + } + + lines.emplace_back(begin, pos); + + pos = eat_endl(text_str, pos); + + begin = text.find_first_not_of(L"\r\n", pos); + + //The number of new lines minus one + const auto chp_end = text_str + (begin == text.npos ? text.size() : begin); + + for (auto chp = text_str + pos; chp != chp_end; ++chp) + { + auto eats = eat_endl(chp, 0); + if (eats) + { + lines.emplace_back(); + chp += (eats - 1); + } + } + + if (text.npos == begin) + { + lines.emplace_back(); + break; + } + } + return !lines.empty(); + } + + bool text_editor::_m_cancel_select(int align) + { + upoint a, b; + if (get_selected_points(a, b)) + { + //domain of algin = [0, 2] + if (align) + { + this->points_.caret = (1 == align ? a : b); + this->_m_adjust_view(); + } + + select_.a = select_.b = points_.caret; + reset_caret(); + return true; + } + return false; + } + + nana::size text_editor::_m_text_extent_size(const char_type* str, size_type n) const + { + if (mask_char_) + { + std::wstring maskstr; + maskstr.append(n, mask_char_); + return graph_.text_extent_size(maskstr); + } +#ifdef _nana_std_has_string_view + return graph_.text_extent_size(std::basic_string_view(str, n)); +#else + return graph_.text_extent_size(str, static_cast(n)); +#endif + } + + bool text_editor::_m_adjust_view() + { + auto const view_area = impl_->cview->view_area(); + + auto const line_px = static_cast(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; + + unsigned extra_count_horz = 4; + unsigned extra_count_vert = 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 extra = points_.caret; + + if (coord.x < origin.x) + { + 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(caret_size().width) >= origin.x + static_cast(view_area.width)) + { + extra.x = (std::min)(static_cast(textbase().getline(points_.caret.y).size()), points_.caret.x + extra_count_horz); + auto new_origin = _m_caret_to_coordinate(extra, false).x + static_cast(caret_size().width) - static_cast(view_area.width); + moved_origin.x = new_origin - origin.x; + } + } + + auto extra_px = static_cast(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(view_area.height)) + { + //Bottom of caret is greater than the bottom of view + + auto const bottom = static_cast(impl_->capacities.behavior->take_lines() * line_px); + auto new_origin = (std::min)(coord.y + line_px + extra_px, bottom) - static_cast(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) + { + if (!attributes_.editable) + return false; + + nana::upoint caret = points_.caret; + const auto text = _m_make_select_string(); + if (!text.empty()) + { + auto undo_ptr = std::unique_ptr(new undo_move_text(*this)); + undo_ptr->set_selected_text(); + + //Determines whether the caret is at left or at right. The select_.b indicates the caret position when finish selection + const bool at_left = (select_.b < select_.a); + + nana::upoint a, b; + get_selected_points(a, b); + if (caret.y < a.y || (caret.y == a.y && caret.x < a.x)) + {//forward + undo_ptr->set_caret_pos(); + + _m_erase_select(false); + _m_put(text, false); + + select_.a = caret; + select_.b.y = b.y + (caret.y - a.y); + } + else if (b.y < caret.y || (caret.y == b.y && b.x < caret.x)) + { + undo_ptr->set_caret_pos(); + + _m_put(text, false); + _m_erase_select(false); + + select_.b.y = caret.y; + select_.a.y = caret.y - (b.y - a.y); + select_.a.x = caret.x - (caret.y == b.y ? (b.x - a.x) : 0); + } + select_.b.x = b.x + (a.y == b.y ? (select_.a.x - a.x) : 0); + + //restores the caret at the proper end. + if ((select_.b < select_.a) != at_left) + std::swap(select_.a, select_.b); + + if (record_undo) + { + undo_ptr->set_destination(select_.a, select_.b); + impl_->undo.push(std::move(undo_ptr)); + } + + points_.caret = select_.b; + reset_caret(); + return true; + } + return false; + } + + int text_editor::_m_text_top_base() const + { + if (false == attributes_.multi_lines) + { + unsigned px = line_height(); + if (text_area_.area.height > px) + return text_area_.area.y + static_cast((text_area_.area.height - px) >> 1); + } + return text_area_.area.y; + } + + int text_editor::_m_text_topline() const + { + auto px = static_cast(line_height()); + return (px ? (impl_->cview->origin().y / px) : px); + } + + int text_editor::_m_text_x(const text_section& sct) const + { + auto const behavior = impl_->capacities.behavior; + int left = this->text_area_.area.x; + + if (::nana::align::left != this->attributes_.alignment) + { + auto blank_px = behavior->max_pixels() - sct.pixels; + + if (::nana::align::center == this->attributes_.alignment) + left += static_cast(blank_px) / 2; + else + left += static_cast(blank_px); + } + + return left - impl_->cview->origin().x; + } + + + void text_editor::_m_draw_parse_string(const keyword_parser& parser, bool rtl, ::nana::point pos, const ::nana::color& fgcolor, const wchar_t* str, std::size_t len) const + { +#ifdef _nana_std_has_string_view + graph_.string(pos, { str, len }, fgcolor); +#else + graph_.palette(true, fgcolor); + graph_.string(pos, str, len); +#endif + if (parser.entities().empty()) + return; + +#ifdef _nana_std_has_string_view + auto glyph_px = graph_.glyph_pixels({ str, len }); +#else + std::unique_ptr glyph_px(new unsigned[len]); + graph_.glyph_pixels(str, len, glyph_px.get()); +#endif + 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 wchar_t* ent_begin = nullptr; + + int ent_off = 0; + if (str <= ent.begin && ent.begin < str_end) + { + ent_begin = ent.begin; + ent_off = static_cast(std::accumulate(glyphs, glyphs + (ent.begin - str), unsigned{})); + } + 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{}); + + canvas.palette(false, ent.scheme->bgcolor.invisible() ? _m_bgcolor() : ent.scheme->bgcolor); + canvas.palette(true, ent.scheme->fgcolor.invisible() ? fgcolor : ent.scheme->fgcolor); + + canvas.rectangle(true); + + ent_pos.x = pos.x + ent_off; + +#ifdef _nana_std_has_string_view + std::wstring_view ent_sv; + if (rtl) + { + //draw the whole text if it is a RTL text, because Arbic language is transformable. + ent_sv = { str, len }; + } + else + { + ent_sv = { ent_begin, static_cast(ent_end - ent_begin) }; + ent_off = 0; + } + canvas.string({}, ent_sv); +#else + if (rtl) + { + //draw the whole text if it is a RTL text, because Arbic language is transformable. + canvas.string({}, str, len); + } + else + { + canvas.string({}, ent_begin, ent_end - ent_begin); + ent_off = 0; + } +#endif + graph_.bitblt(rectangle{ ent_pos, size{ ent_pixels, canvas.height() } }, canvas, point{ ent_off, 0 }); + } + } + } + + class text_editor::helper_pencil + { + public: + helper_pencil(paint::graphics& graph, const text_editor& editor, keyword_parser& parser) : + graph_(graph), + editor_(editor), + parser_(parser), + line_px_(editor.line_height()) + {} + + color selection_color(bool fgcolor, bool focused) const + { + if (fgcolor) + return (focused ? editor_.scheme_->selection_text : editor_.scheme_->foreground).get_color(); + + return (focused ? editor_.scheme_->selection : editor_.scheme_->selection_unfocused).get_color(); + } + + void write_selection(const point& text_pos, unsigned text_px, const wchar_t* text, std::size_t len, bool has_focused) + { +#ifdef _nana_std_has_string_view + graph_.rectangle(::nana::rectangle{ text_pos,{ text_px, line_px_ } }, true, + selection_color(false, has_focused)); + + graph_.string(text_pos, { text, len }, selection_color(true, has_focused)); +#else + graph_.palette(true, selection_color(true, has_focused)); + + graph_.rectangle(::nana::rectangle{ text_pos,{ text_px, line_px_ } }, true, + selection_color(false, has_focused)); + + graph_.string(text_pos, text, len); +#endif + } + + void rtl_string(point strpos, const wchar_t* str, std::size_t len, std::size_t str_px, unsigned glyph_front, unsigned glyph_selected, bool has_focused) + { + editor_._m_draw_parse_string(parser_, true, strpos, selection_color(true, has_focused), str, len); + + //Draw selected part + paint::graphics graph({ glyph_selected, line_px_ }); + graph.typeface(this->graph_.typeface()); + graph.rectangle(true, selection_color(false, has_focused)); + + int sel_xpos = static_cast(str_px - (glyph_front + glyph_selected)); + +#ifdef _nana_std_has_string_view + graph.string({ -sel_xpos, 0 }, { str, len }, selection_color(true, has_focused)); +#else + graph.palette(true, selection_color(true, has_focused)); + graph.string({ -sel_xpos, 0 }, str, len); +#endif + graph_.bitblt(nana::rectangle(strpos.x + sel_xpos, strpos.y, glyph_selected, line_px_), graph); + }; + private: + paint::graphics& graph_; + const text_editor& editor_; + keyword_parser & parser_; + unsigned line_px_; + }; + + void text_editor::_m_draw_string(int top, const ::nana::color& clr, const nana::upoint& text_coord, const text_section& sct, bool if_mask) const + { + point text_draw_pos{ _m_text_x(sct), top }; + + const int text_right = text_area_.area.right(); + auto const text_len = static_cast(sct.end - sct.begin); + auto text_ptr = sct.begin; + + std::wstring mask_str; + if (if_mask && mask_char_) + { + mask_str.resize(text_len, mask_char_); + text_ptr = mask_str.c_str(); + } + + const auto focused = API::is_focus_ready(window_); + + auto const reordered = unicode_reorder(text_ptr, text_len); + + //Parse highlight keywords + keyword_parser parser; + parser.parse(text_ptr, text_len, impl_->keywords); + + const auto line_h_pixels = line_height(); + + helper_pencil pencil(graph_, *this, parser); + + graph_.palette(true, clr); + graph_.palette(false, scheme_->selection.get_color()); + + + //Get the selection begin and end position of the current text. + const wchar_t *sbegin = nullptr, *send = nullptr; + + nana::upoint a, b; + if (get_selected_points(a, b)) + { + if (a.y < text_coord.y && text_coord.y < b.y) + { + sbegin = sct.begin; + send = sct.end; + } + else if ((a.y == b.y) && a.y == text_coord.y) + { + auto sbegin_pos = (std::max)(a.x, text_coord.x); + auto send_pos = (std::min)(text_coord.x + text_len, b.x); + + if (sbegin_pos < send_pos) + { + sbegin = text_ptr + (sbegin_pos - text_coord.x); + send = text_ptr + (send_pos - text_coord.x); + } + } + else if (a.y == text_coord.y) + { + if (a.x < text_coord.x + text_len) + { + sbegin = text_ptr; + if (text_coord.x < a.x) + sbegin += (a.x - text_coord.x); + + send = text_ptr + text_len; + } + } + else if (b.y == text_coord.y) + { + if (text_coord.x < b.x) + { + sbegin = text_ptr; + send = text_ptr + (std::min)(b.x - text_coord.x, text_len); + } + } + } + + //A text editor feature, it draws an extra block at end of line if the end of line is in range of selection. + bool extra_space = false; + + //Create a flag for indicating whether the whole line is selected + const bool text_selected = (sbegin == text_ptr && send == text_ptr + text_len); + + //The text is not selected or the whole line text is selected + if ((!sbegin || !send) || text_selected) + { + for (auto & ent : reordered) + { + std::size_t len = ent.end - ent.begin; + +#ifdef _nana_std_has_string_view + unsigned str_w = graph_.text_extent_size(std::wstring_view{ ent.begin, len }).width; +#else + unsigned str_w = graph_.text_extent_size(ent.begin, len).width; +#endif + + if ((text_draw_pos.x + static_cast(str_w) > text_area_.area.x) && (text_draw_pos.x < text_right)) + { + if (text_selected) + pencil.write_selection(text_draw_pos, str_w, ent.begin, len, focused); + else + _m_draw_parse_string(parser, is_right_text(ent), text_draw_pos, clr, ent.begin, len); + } + text_draw_pos.x += static_cast(str_w); + } + + extra_space = text_selected; + } + else + { + for (auto & ent : reordered) + { + const auto len = ent.end - ent.begin; +#ifdef _nana_std_has_string_view + auto ent_px = graph_.text_extent_size(std::wstring_view(ent.begin, len)).width; +#else + auto ent_px = graph_.text_extent_size(ent.begin, len).width; +#endif + + extra_space = false; + + //Only draw the text which is in the visual rectangle. + if ((text_draw_pos.x + static_cast(ent_px) > text_area_.area.x) && (text_draw_pos.x < text_right)) + { + if (send <= ent.begin || ent.end <= sbegin) + { + //this string is not selected + _m_draw_parse_string(parser, false, text_draw_pos, clr, ent.begin, len); + } + else if (sbegin <= ent.begin && ent.end <= send) + { + //this string is completed selected + pencil.write_selection(text_draw_pos, ent_px, ent.begin, len, focused); + extra_space = true; + } + else + { + //a part of string is selected + + //get the selected range of this string. + auto ent_sbegin = (std::max)(sbegin, ent.begin); + auto ent_send = (std::min)(send, ent.end); + + unsigned select_pos = static_cast(ent_sbegin != ent.begin ? ent_sbegin - ent.begin : 0); + unsigned select_len = static_cast(ent_send - ent_sbegin); + +#ifdef _nana_std_has_string_view + auto pxbuf = graph_.glyph_pixels({ ent.begin, static_cast(len) }); +#else + std::unique_ptr pxbuf{ new unsigned[len] }; + graph_.glyph_pixels(ent.begin, len, pxbuf.get()); +#endif + + auto head_px = std::accumulate(pxbuf.get(), pxbuf.get() + select_pos, unsigned{}); + auto select_px = std::accumulate(pxbuf.get() + select_pos, pxbuf.get() + select_pos + select_len, unsigned{}); + + graph_.palette(true, clr); + if (is_right_text(ent)) + { //RTL + pencil.rtl_string(text_draw_pos, ent.begin, len, ent_px, head_px, select_px, focused); + } + else + { //LTR + _m_draw_parse_string(parser, false, text_draw_pos, clr, ent.begin, select_pos); + + auto part_pos = text_draw_pos; + part_pos.x += static_cast(head_px); + + pencil.write_selection(part_pos, select_px, ent.begin + select_pos, select_len, focused); + + if (ent_send < ent.end) + { + part_pos.x += static_cast(select_px); + _m_draw_parse_string(parser, false, part_pos, clr, ent_send, ent.end - ent_send); + } + } + + extra_space = (select_pos + select_len == text_len); + } + } + text_draw_pos.x += static_cast(ent_px); + }//end for + } + + //extra_space is true if the end of line is selected + if (extra_space) + { + //draw the extra space if end of line is not equal to the second selection position. + auto pos = text_coord.x + text_len; + if (b.x != pos || text_coord.y != b.y) + { +#ifdef _nana_std_has_string_view + auto whitespace_w = graph_.text_extent_size(std::wstring_view{ L" ", 1 }).width; +#else + auto whitespace_w = graph_.text_extent_size(L" ", 1).width; +#endif + graph_.rectangle(::nana::rectangle{ text_draw_pos,{ whitespace_w, line_h_pixels } }, true); + } + } + } + + bool text_editor::_m_update_caret_line(std::size_t secondary_before) + { + if (false == this->_m_adjust_view()) + { + if (_m_caret_to_coordinate(points_.caret).x < impl_->cview->view_area().right()) + { + _m_update_line(points_.caret.y, secondary_before); + return false; + } + } + else + { + //The content view is adjusted, now syncs it with active mode to avoid updating. + impl_->cview->sync(false); + } + impl_->try_refresh = sync_graph::refresh; return true; } - return false; - } - int text_editor::_m_text_top_base() const - { - if(false == attributes_.multi_lines) + unsigned text_editor::_m_char_by_pixels(const unicode_bidi::entity& ent, unsigned pos) const { - unsigned px = line_height(); - if(text_area_.area.height > px) - return text_area_.area.y + static_cast((text_area_.area.height - px) >> 1); - } - return text_area_.area.y; - } - - int text_editor::_m_text_topline() const - { - auto px = static_cast(line_height()); - return (px ? (impl_->cview->origin().y / px) : px); - } - - int text_editor::_m_text_x(const text_section& sct) const - { - auto const behavior = impl_->capacities.behavior; - int left = this->text_area_.area.x; - - if (::nana::align::left != this->attributes_.alignment) - { - auto blank_px = behavior->max_pixels() - sct.pixels; - - if (::nana::align::center == this->attributes_.alignment) - left += static_cast(blank_px) / 2; - else - left += static_cast(blank_px); - } - - return left - impl_->cview->origin().x; - } - - - void text_editor::_m_draw_parse_string(const keyword_parser& parser, bool rtl, ::nana::point pos, const ::nana::color& fgcolor, const wchar_t* str, std::size_t len) const - { + auto len = static_cast(ent.end - ent.begin); #ifdef _nana_std_has_string_view - graph_.string(pos, { str, len }, fgcolor); + auto pxbuf = graph_.glyph_pixels({ ent.begin, len }); + if (pxbuf) #else - graph_.palette(true, fgcolor); - graph_.string(pos, str, len); + std::unique_ptr pxbuf(new unsigned[len]); + if (graph_.glyph_pixels(ent.begin, len, pxbuf.get())) #endif - if (parser.entities().empty()) - return; - -#ifdef _nana_std_has_string_view - auto glyph_px = graph_.glyph_pixels({ str, len }); -#else - std::unique_ptr glyph_px(new unsigned[len]); - graph_.glyph_pixels(str, len, glyph_px.get()); -#endif - 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 wchar_t* ent_begin = nullptr; - - int ent_off = 0; - if (str <= ent.begin && ent.begin < str_end) { - ent_begin = ent.begin; - ent_off = static_cast(std::accumulate(glyphs, glyphs + (ent.begin - str), unsigned{})); - } - else if (ent.begin <= str && str < ent.end) - ent_begin = str; + const auto px_end = pxbuf.get() + len; - 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{}); - - canvas.palette(false, ent.scheme->bgcolor.invisible() ? _m_bgcolor() : ent.scheme->bgcolor); - canvas.palette(true, ent.scheme->fgcolor.invisible() ? fgcolor : ent.scheme->fgcolor); - - canvas.rectangle(true); - - ent_pos.x = pos.x + ent_off; - -#ifdef _nana_std_has_string_view - std::wstring_view ent_sv; - if (rtl) + if (is_right_text(ent)) { - //draw the whole text if it is a RTL text, because Arbic language is transformable. - ent_sv = { str, len }; + auto total_px = std::accumulate(pxbuf.get(), px_end, unsigned{}); + + for (auto p = pxbuf.get(); p != px_end; ++p) + { + auto chpos = total_px - *p; + if ((chpos <= pos) && (pos < total_px)) + { + if ((*p < 2) || (pos <= chpos + (*p >> 1))) + return static_cast(p - pxbuf.get()) + 1; + + return static_cast(p - pxbuf.get()); + } + total_px = chpos; + } } else { - ent_sv = { ent_begin, static_cast(ent_end - ent_begin) }; - ent_off = 0; - } - canvas.string({}, ent_sv); -#else - if (rtl) - { - //draw the whole text if it is a RTL text, because Arbic language is transformable. - canvas.string({}, str, len); - } - else - { - canvas.string({}, ent_begin, ent_end - ent_begin); - ent_off = 0; - } -#endif - graph_.bitblt(rectangle{ ent_pos, size{ ent_pixels, canvas.height() } }, canvas, point{ ent_off, 0 }); - } - } - } - - class text_editor::helper_pencil - { - public: - helper_pencil(paint::graphics& graph, const text_editor& editor, keyword_parser& parser): - graph_( graph ), - editor_( editor ), - parser_( parser ), - line_px_( editor.line_height() ) - {} - - color selection_color(bool fgcolor, bool focused) const - { - if (fgcolor) - return (focused ? editor_.scheme_->selection_text : editor_.scheme_->foreground).get_color(); - - return (focused ? editor_.scheme_->selection : editor_.scheme_->selection_unfocused).get_color(); - } - - void write_selection(const point& text_pos, unsigned text_px, const wchar_t* text, std::size_t len, bool has_focused) - { -#ifdef _nana_std_has_string_view - graph_.rectangle(::nana::rectangle{ text_pos,{ text_px, line_px_ } }, true, - selection_color(false, has_focused)); - - graph_.string(text_pos, { text, len }, selection_color(true, has_focused)); -#else - graph_.palette(true, selection_color(true, has_focused)); - - graph_.rectangle(::nana::rectangle{ text_pos, { text_px, line_px_ } }, true, - selection_color(false, has_focused)); - - graph_.string(text_pos, text, len); -#endif - } - - void rtl_string(point strpos, const wchar_t* str, std::size_t len, std::size_t str_px, unsigned glyph_front, unsigned glyph_selected, bool has_focused) - { - editor_._m_draw_parse_string(parser_, true, strpos, selection_color(true, has_focused), str, len); - - //Draw selected part - paint::graphics graph({ glyph_selected, line_px_ }); - graph.typeface(this->graph_.typeface()); - graph.rectangle(true, selection_color(false, has_focused)); - - int sel_xpos = static_cast(str_px - (glyph_front + glyph_selected)); - -#ifdef _nana_std_has_string_view - graph.string({ -sel_xpos, 0 }, { str, len }, selection_color(true, has_focused)); -#else - graph.palette(true, selection_color(true, has_focused)); - graph.string({ -sel_xpos, 0 }, str, len); -#endif - graph_.bitblt(nana::rectangle(strpos.x + sel_xpos, strpos.y, glyph_selected, line_px_), graph); - }; - private: - paint::graphics& graph_; - const text_editor& editor_; - keyword_parser & parser_; - unsigned line_px_; - }; - - void text_editor::_m_draw_string(int top, const ::nana::color& clr, const nana::upoint& text_coord, const text_section& sct, bool if_mask) const - { - point text_draw_pos{ _m_text_x(sct), top }; - - const int text_right = text_area_.area.right(); - auto const text_len = static_cast(sct.end - sct.begin); - auto text_ptr = sct.begin; - - std::wstring mask_str; - if (if_mask && mask_char_) - { - mask_str.resize(text_len, mask_char_); - text_ptr = mask_str.c_str(); - } - - const auto focused = API::is_focus_ready(window_); - - auto const reordered = unicode_reorder(text_ptr, text_len); - - //Parse highlight keywords - keyword_parser parser; - parser.parse(text_ptr, text_len, impl_->keywords); - - const auto line_h_pixels = line_height(); - - helper_pencil pencil(graph_, *this, parser); - - graph_.palette(true, clr); - graph_.palette(false, scheme_->selection.get_color()); - - - //Get the selection begin and end position of the current text. - const wchar_t *sbegin = nullptr, *send = nullptr; - - nana::upoint a, b; - if (get_selected_points(a, b)) - { - if (a.y < text_coord.y && text_coord.y < b.y) - { - sbegin = sct.begin; - send = sct.end; - } - else if ((a.y == b.y) && a.y == text_coord.y) - { - auto sbegin_pos = (std::max)(a.x, text_coord.x); - auto send_pos = (std::min)(text_coord.x + text_len, b.x); - - if (sbegin_pos < send_pos) - { - sbegin = text_ptr + (sbegin_pos - text_coord.x); - send = text_ptr + (send_pos - text_coord.x); + for (auto p = pxbuf.get(); p != px_end; ++p) + { + if (pos <= *p) + { + if ((*p > 1) && (pos >(*p >> 1))) + return static_cast(p - pxbuf.get()) + 1; + return static_cast(p - pxbuf.get()); + } + pos -= *p; + } } } - else if (a.y == text_coord.y) - { - if (a.x < text_coord.x + text_len) - { - sbegin = text_ptr; - if (text_coord.x < a.x) - sbegin += (a.x - text_coord.x); - send = text_ptr + text_len; - } - } - else if (b.y == text_coord.y) - { - if (text_coord.x < b.x) - { - sbegin = text_ptr; - send = text_ptr + (std::min)(b.x - text_coord.x, text_len); - } - } + return 0; } - //A text editor feature, it draws an extra block at end of line if the end of line is in range of selection. - bool extra_space = false; - - //Create a flag for indicating whether the whole line is selected - const bool text_selected = (sbegin == text_ptr && send == text_ptr+ text_len); - - //The text is not selected or the whole line text is selected - if ((!sbegin || !send) || text_selected) + unsigned text_editor::_m_pixels_by_char(const std::wstring& lnstr, std::size_t pos) const { + if (pos > lnstr.size()) + return 0; + + auto const reordered = unicode_reorder(lnstr.c_str(), lnstr.size()); + + auto target = lnstr.c_str() + pos; + + unsigned text_w = 0; for (auto & ent : reordered) { std::size_t len = ent.end - ent.begin; - -#ifdef _nana_std_has_string_view - unsigned str_w = graph_.text_extent_size(std::wstring_view{ ent.begin, len }).width; -#else - unsigned str_w = graph_.text_extent_size(ent.begin, len).width; -#endif - - if ((text_draw_pos.x + static_cast(str_w) > text_area_.area.x) && (text_draw_pos.x < text_right)) + if (ent.begin <= target && target <= ent.end) { - if (text_selected) - pencil.write_selection(text_draw_pos, str_w, ent.begin, len, focused); - else - _m_draw_parse_string(parser, is_right_text(ent), text_draw_pos, clr, ent.begin, len); - } - text_draw_pos.x += static_cast(str_w); - } - - extra_space = text_selected; - } - else - { - for (auto & ent : reordered) - { - const auto len = ent.end - ent.begin; + if (is_right_text(ent)) + { + //Characters of some bidi languages may transform in a word. + //RTL #ifdef _nana_std_has_string_view - auto ent_px = graph_.text_extent_size(std::wstring_view(ent.begin, len)).width; + auto pxbuf = graph_.glyph_pixels({ ent.begin, len }); #else - auto ent_px = graph_.text_extent_size(ent.begin, len).width; -#endif - - extra_space = false; - - //Only draw the text which is in the visual rectangle. - if ((text_draw_pos.x + static_cast(ent_px) > text_area_.area.x) && (text_draw_pos.x < text_right)) - { - if (send <= ent.begin || ent.end <= sbegin) - { - //this string is not selected - _m_draw_parse_string(parser, false, text_draw_pos, clr, ent.begin, len); - } - else if (sbegin <= ent.begin && ent.end <= send) - { - //this string is completed selected - pencil.write_selection(text_draw_pos, ent_px, ent.begin, len, focused); - extra_space = true; - } - else - { - //a part of string is selected - - //get the selected range of this string. - auto ent_sbegin = (std::max)(sbegin, ent.begin); - auto ent_send = (std::min)(send, ent.end); - - unsigned select_pos = static_cast(ent_sbegin != ent.begin ? ent_sbegin - ent.begin : 0); - unsigned select_len = static_cast(ent_send - ent_sbegin); - -#ifdef _nana_std_has_string_view - auto pxbuf = graph_.glyph_pixels({ ent.begin, static_cast(len) }); -#else - std::unique_ptr pxbuf{ new unsigned[len] }; + std::unique_ptr pxbuf(new unsigned[len]); graph_.glyph_pixels(ent.begin, len, pxbuf.get()); #endif - - auto head_px = std::accumulate(pxbuf.get(), pxbuf.get() + select_pos, unsigned{}); - auto select_px = std::accumulate(pxbuf.get() + select_pos, pxbuf.get() + select_pos + select_len, unsigned{}); - - graph_.palette(true, clr); - if (is_right_text(ent)) - { //RTL - pencil.rtl_string(text_draw_pos, ent.begin, len, ent_px, head_px, select_px, focused); - } - else - { //LTR - _m_draw_parse_string(parser, false, text_draw_pos, clr, ent.begin, select_pos); - - auto part_pos = text_draw_pos; - part_pos.x += static_cast(head_px); - - pencil.write_selection(part_pos, select_px, ent.begin + select_pos, select_len, focused); - - if (ent_send < ent.end) - { - part_pos.x += static_cast(select_px); - _m_draw_parse_string(parser, false, part_pos, clr, ent_send, ent.end - ent_send); - } - } - - extra_space = (select_pos + select_len == text_len); + return std::accumulate(pxbuf.get() + (target - ent.begin), pxbuf.get() + len, text_w); } + //LTR + return text_w + _m_text_extent_size(ent.begin, target - ent.begin).width; } - text_draw_pos.x += static_cast(ent_px); - }//end for - } - - //extra_space is true if the end of line is selected - if (extra_space) - { - //draw the extra space if end of line is not equal to the second selection position. - auto pos = text_coord.x + text_len; - if (b.x != pos || text_coord.y != b.y) - { -#ifdef _nana_std_has_string_view - auto whitespace_w = graph_.text_extent_size(std::wstring_view{ L" ", 1 }).width; -#else - auto whitespace_w = graph_.text_extent_size(L" ", 1).width; -#endif - graph_.rectangle(::nana::rectangle{ text_draw_pos, { whitespace_w, line_h_pixels } }, true); - } - } - } - - bool text_editor::_m_update_caret_line(std::size_t secondary_before) - { - if (false == this->_m_adjust_view()) - { - if (_m_caret_to_coordinate(points_.caret).x < impl_->cview->view_area().right()) - { - _m_update_line(points_.caret.y, secondary_before); - return false; - } - } - else - { - //The content view is adjusted, now syncs it with active mode to avoid updating. - impl_->cview->sync(false); - } - impl_->try_refresh = sync_graph::refresh; - return true; - } - - unsigned text_editor::_m_char_by_pixels(const unicode_bidi::entity& ent, unsigned pos) const - { - auto len = static_cast(ent.end - ent.begin); -#ifdef _nana_std_has_string_view - auto pxbuf = graph_.glyph_pixels({ ent.begin, len }); - if (pxbuf) -#else - std::unique_ptr pxbuf(new unsigned[len]); - if (graph_.glyph_pixels(ent.begin, len, pxbuf.get())) -#endif - { - const auto px_end = pxbuf.get() + len; - - if (is_right_text(ent)) - { - auto total_px = std::accumulate(pxbuf.get(), px_end, unsigned{}); - - for (auto p = pxbuf.get(); p != px_end; ++p) - { - auto chpos = total_px - *p; - if ((chpos <= pos) && (pos < total_px)) - { - if ((*p < 2) || (pos <= chpos + (*p >> 1))) - return static_cast(p - pxbuf.get()) + 1; - - return static_cast(p - pxbuf.get()); - } - total_px = chpos; - } - } - else - { - for (auto p = pxbuf.get(); p != px_end; ++p) - { - if (pos <= *p) - { - if ((*p > 1) && (pos >(*p >> 1))) - return static_cast(p - pxbuf.get()) + 1; - return static_cast(p - pxbuf.get()); - } - pos -= *p; - } + else + text_w += _m_text_extent_size(ent.begin, len).width; } + return text_w; } - return 0; - } - - unsigned text_editor::_m_pixels_by_char(const std::wstring& lnstr, std::size_t pos) const - { - if (pos > lnstr.size()) - return 0; - - auto const reordered = unicode_reorder(lnstr.c_str(), lnstr.size()); - - auto target = lnstr.c_str() + pos; - - unsigned text_w = 0; - for (auto & ent : reordered) - { - std::size_t len = ent.end - ent.begin; - if (ent.begin <= target && target <= ent.end) - { - if (is_right_text(ent)) - { - //Characters of some bidi languages may transform in a word. - //RTL -#ifdef _nana_std_has_string_view - auto pxbuf = graph_.glyph_pixels({ent.begin, len}); -#else - std::unique_ptr pxbuf(new unsigned[len]); - graph_.glyph_pixels(ent.begin, len, pxbuf.get()); -#endif - return std::accumulate(pxbuf.get() + (target - ent.begin), pxbuf.get() + len, text_w); - } - //LTR - return text_w + _m_text_extent_size(ent.begin, target - ent.begin).width; - } - else - text_w += _m_text_extent_size(ent.begin, len).width; - } - return text_w; - } - - //end class text_editor - }//end namespace skeletons -}//end namespace widgets + //end class text_editor + }//end namespace skeletons + }//end namespace widgets }//end namespace nana