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>;
template<typename Iterator>
utext_ptr to_utext(Iterator begin, Iterator end) {
icu::BreakIterator& get_break_iterator(const char *ptr, size_t len) {
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;
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 <typename Iterator>
size_t count_in_range(Iterator begin, Iterator end, int mask) {
if (begin == end) return 0;
static std::unique_ptr<icu::BreakIterator> 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;
}
}
}

View file

@ -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);
}

View file

@ -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);
}
}

View file

@ -47,8 +47,9 @@
#include <libaegisub/ass/dialogue_parser.h>
#include <libaegisub/calltip_provider.h>
#include <libaegisub/spellchecker.h>
#include <libaegisub/character_count.h>
#include <libaegisub/make_unique.h>
#include <libaegisub/spellchecker.h>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/algorithm/string/replace.hpp>
@ -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();