Preserve the insertion point (but not selection) when switching between lines

This commit is contained in:
Thomas Goyne 2014-04-25 08:00:03 -07:00
parent 6ee1b8ca52
commit d4fbe3040d
4 changed files with 56 additions and 29 deletions

View File

@ -31,35 +31,34 @@ struct utext_deleter {
}; };
using utext_ptr = std::unique_ptr<UText, utext_deleter>; using utext_ptr = std::unique_ptr<UText, utext_deleter>;
template<typename Iterator> icu::BreakIterator& get_break_iterator(const char *ptr, size_t len) {
utext_ptr to_utext(Iterator begin, Iterator end) { static std::unique_ptr<icu::BreakIterator> 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; 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); 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 <typename Iterator> template <typename Iterator>
size_t count_in_range(Iterator begin, Iterator end, int mask) { size_t count_in_range(Iterator begin, Iterator end, int mask) {
if (begin == end) return 0; if (begin == end) return 0;
static std::unique_ptr<icu::BreakIterator> character_bi; auto& character_bi = get_break_iterator(&*begin, end - begin);
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);
size_t count = 0; size_t count = 0;
auto pos = character_bi->first(); auto pos = character_bi.first();
for (auto end = character_bi->next(); end != BreakIterator::DONE; pos = end, end = character_bi->next()) { for (auto end = character_bi.next(); end != BreakIterator::DONE; pos = end, end = character_bi.next()) {
if (!mask) if (!mask)
++count; ++count;
else { else {
@ -85,25 +84,29 @@ int ignore_mask_to_icu_mask(int mask) {
} }
namespace agi { 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); mask = ignore_mask_to_icu_mask(mask);
size_t characters = 0; size_t characters = 0;
auto pos = begin(str); auto pos = begin;
do { do {
auto it = std::find(pos, end(str), '{'); auto it = std::find(pos, end, '{');
characters += count_in_range(pos, it, mask); characters += count_in_range(pos, it, mask);
if (it == end(str)) break; if (it == end) break;
pos = std::find(pos, end(str), '}'); pos = std::find(pos, end, '}');
if (pos == end(str)) { if (pos == end) {
characters += count_in_range(it, pos, mask); characters += count_in_range(it, pos, mask);
break; break;
} }
} while (++pos != end(str)); } while (++pos != end);
return characters; 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) { size_t MaxLineLength(std::string const& text, int mask) {
mask = ignore_mask_to_icu_mask(mask); mask = ignore_mask_to_icu_mask(mask);
auto tokens = agi::ass::TokenizeDialogueBody(text); 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); 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;
}
}
} }

View File

@ -25,5 +25,10 @@ namespace agi {
/// Get the length in characters of the longest line in the given text /// Get the length in characters of the longest line in the given text
size_t MaxLineLength(std::string const& text, int ignore_mask); 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& 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);
} }

View File

@ -342,7 +342,6 @@ void SubsEditBox::UpdateFields(int type, bool repopulate_lists) {
if (repopulate_lists) PopulateList(actor_box, &AssDialogue::Actor); if (repopulate_lists) PopulateList(actor_box, &AssDialogue::Actor);
actor_box->ChangeValue(to_wx(line->Actor)); actor_box->ChangeValue(to_wx(line->Actor));
actor_box->SetStringSelection(to_wx(line->Actor)); actor_box->SetStringSelection(to_wx(line->Actor));
edit_ctrl->SetTextTo(line->Text);
} }
} }

View File

@ -47,8 +47,9 @@
#include <libaegisub/ass/dialogue_parser.h> #include <libaegisub/ass/dialogue_parser.h>
#include <libaegisub/calltip_provider.h> #include <libaegisub/calltip_provider.h>
#include <libaegisub/spellchecker.h> #include <libaegisub/character_count.h>
#include <libaegisub/make_unique.h> #include <libaegisub/make_unique.h>
#include <libaegisub/spellchecker.h>
#include <boost/algorithm/string/predicate.hpp> #include <boost/algorithm/string/predicate.hpp>
#include <boost/algorithm/string/replace.hpp> #include <boost/algorithm/string/replace.hpp>
@ -281,9 +282,16 @@ void SubsTextEditCtrl::SetTextTo(std::string const& text) {
SetEvtHandlerEnabled(false); SetEvtHandlerEnabled(false);
Freeze(); 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(); line_text.clear();
SetTextRaw(text.c_str());
SetSelection(0, 0); SetSelection(0, 0);
SetTextRaw(text.c_str());
auto pos = agi::IndexOfCharacter(text, old_pos);
SetSelection(pos, pos);
SetEvtHandlerEnabled(true); SetEvtHandlerEnabled(true);
Thaw(); Thaw();