Preserve the insertion point (but not selection) when switching between lines
This commit is contained in:
parent
6ee1b8ca52
commit
d4fbe3040d
4 changed files with 56 additions and 29 deletions
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in a new issue