From d4fbe3040d92230f6e47faeaf625ec470b851e28 Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Fri, 25 Apr 2014 08:00:03 -0700 Subject: [PATCH] Preserve the insertion point (but not selection) when switching between lines --- libaegisub/common/character_count.cpp | 67 ++++++++++++------- .../include/libaegisub/character_count.h | 5 ++ src/subs_edit_box.cpp | 1 - src/subs_edit_ctrl.cpp | 12 +++- 4 files changed, 56 insertions(+), 29 deletions(-) diff --git a/libaegisub/common/character_count.cpp b/libaegisub/common/character_count.cpp index 1862ec8c4..9a776e551 100644 --- a/libaegisub/common/character_count.cpp +++ b/libaegisub/common/character_count.cpp @@ -31,35 +31,34 @@ struct utext_deleter { }; using utext_ptr = std::unique_ptr; -template -utext_ptr to_utext(Iterator begin, Iterator end) { +icu::BreakIterator& get_break_iterator(const char *ptr, size_t len) { + static std::unique_ptr bi; + static std::once_flag token; + std::call_once(token, [&] { + UErrorCode status = U_ZERO_ERROR; + bi.reset(BreakIterator::createCharacterInstance(Locale::getDefault(), status)); + if (U_FAILURE(status)) throw agi::InternalError("Failed to create character iterator", nullptr); + }); + UErrorCode err = U_ZERO_ERROR; - utext_ptr ret(utext_openUTF8(nullptr, &*begin, end - begin, &err)); + utext_ptr ut(utext_openUTF8(nullptr, ptr, len, &err)); if (U_FAILURE(err)) throw agi::InternalError("Failed to open utext", nullptr); - return ret; + + bi->setText(ut.get(), err); + if (U_FAILURE(err)) throw agi::InternalError("Failed to set break iterator text", nullptr); + + return *bi; } template size_t count_in_range(Iterator begin, Iterator end, int mask) { if (begin == end) return 0; - static std::unique_ptr character_bi; - static std::once_flag token; - std::call_once(token, [&] { - UErrorCode status = U_ZERO_ERROR; - character_bi.reset(BreakIterator::createCharacterInstance(Locale::getDefault(), status)); - if (U_FAILURE(status)) throw agi::InternalError("Failed to create character iterator", nullptr); - }); - - UErrorCode err = U_ZERO_ERROR; - - utext_ptr ut = to_utext(begin, end); - character_bi->setText(ut.get(), err); - if (U_FAILURE(err)) throw agi::InternalError("Failed to set break iterator text", nullptr); + auto& character_bi = get_break_iterator(&*begin, end - begin); size_t count = 0; - auto pos = character_bi->first(); - for (auto end = character_bi->next(); end != BreakIterator::DONE; pos = end, end = character_bi->next()) { + auto pos = character_bi.first(); + for (auto end = character_bi.next(); end != BreakIterator::DONE; pos = end, end = character_bi.next()) { if (!mask) ++count; else { @@ -85,25 +84,29 @@ int ignore_mask_to_icu_mask(int mask) { } namespace agi { -size_t CharacterCount(std::string const& str, int mask) { +size_t CharacterCount(std::string::const_iterator begin, std::string::const_iterator end, int mask) { mask = ignore_mask_to_icu_mask(mask); size_t characters = 0; - auto pos = begin(str); + auto pos = begin; do { - auto it = std::find(pos, end(str), '{'); + auto it = std::find(pos, end, '{'); characters += count_in_range(pos, it, mask); - if (it == end(str)) break; + if (it == end) break; - pos = std::find(pos, end(str), '}'); - if (pos == end(str)) { + pos = std::find(pos, end, '}'); + if (pos == end) { characters += count_in_range(it, pos, mask); break; } - } while (++pos != end(str)); + } while (++pos != end); return characters; } +size_t CharacterCount(std::string const& str, int mask) { + return CharacterCount(begin(str), end(str), mask); +} + size_t MaxLineLength(std::string const& text, int mask) { mask = ignore_mask_to_icu_mask(mask); auto tokens = agi::ass::TokenizeDialogueBody(text); @@ -131,4 +134,16 @@ size_t MaxLineLength(std::string const& text, int mask) { return std::max(max_line_length, current_line_length); } + +size_t IndexOfCharacter(std::string const& str, size_t n) { + if (str.empty() || n == 0) return 0; + auto& bi = get_break_iterator(&str[0], str.size()); + + for (auto pos = bi.first(), end = bi.next(); ; --n, pos = end, end = bi.next()) { + if (end == BreakIterator::DONE) + return str.size(); + if (n == 0) + return pos; + } +} } diff --git a/libaegisub/include/libaegisub/character_count.h b/libaegisub/include/libaegisub/character_count.h index d0e66346a..c45ab81a3 100644 --- a/libaegisub/include/libaegisub/character_count.h +++ b/libaegisub/include/libaegisub/character_count.h @@ -25,5 +25,10 @@ namespace agi { /// Get the length in characters of the longest line in the given text size_t MaxLineLength(std::string const& text, int ignore_mask); + /// Get the total number of characters in the string size_t CharacterCount(std::string const& str, int ignore_mask); + size_t CharacterCount(std::string::const_iterator begin, std::string::const_iterator end, int ignore_mask); + /// Get index in bytes of the nth character in str, or str.size() if str + /// has less than n characters + size_t IndexOfCharacter(std::string const& str, size_t n); } \ No newline at end of file diff --git a/src/subs_edit_box.cpp b/src/subs_edit_box.cpp index 9fc8180ef..d854b0eb5 100644 --- a/src/subs_edit_box.cpp +++ b/src/subs_edit_box.cpp @@ -342,7 +342,6 @@ void SubsEditBox::UpdateFields(int type, bool repopulate_lists) { if (repopulate_lists) PopulateList(actor_box, &AssDialogue::Actor); actor_box->ChangeValue(to_wx(line->Actor)); actor_box->SetStringSelection(to_wx(line->Actor)); - edit_ctrl->SetTextTo(line->Text); } } diff --git a/src/subs_edit_ctrl.cpp b/src/subs_edit_ctrl.cpp index 6cd6ed5e9..04294dc3b 100644 --- a/src/subs_edit_ctrl.cpp +++ b/src/subs_edit_ctrl.cpp @@ -47,8 +47,9 @@ #include #include -#include +#include #include +#include #include #include @@ -281,9 +282,16 @@ void SubsTextEditCtrl::SetTextTo(std::string const& text) { SetEvtHandlerEnabled(false); Freeze(); + auto insertion_point = GetInsertionPoint(); + if (insertion_point > line_text.size()) + line_text = GetTextRaw().data(); + auto old_pos = agi::CharacterCount(line_text.begin(), line_text.begin() + insertion_point, 0); line_text.clear(); - SetTextRaw(text.c_str()); + SetSelection(0, 0); + SetTextRaw(text.c_str()); + auto pos = agi::IndexOfCharacter(text, old_pos); + SetSelection(pos, pos); SetEvtHandlerEnabled(true); Thaw();