2019-03-20 17:15:52 -04:00
|
|
|
// Copyright 2013 The Flutter Authors. All rights reserved.
|
|
|
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
|
|
|
// found in the LICENSE file.
|
|
|
|
|
|
|
|
|
|
#include "flutter/shell/platform/common/cpp/text_input_model.h"
|
|
|
|
|
|
2019-09-09 03:13:59 +08:00
|
|
|
#include <codecvt>
|
2019-03-20 17:15:52 -04:00
|
|
|
#include <iostream>
|
2019-09-09 03:13:59 +08:00
|
|
|
#include <locale>
|
2019-03-20 17:15:52 -04:00
|
|
|
|
|
|
|
|
// TODO(awdavies): Need to fix this regarding issue #47.
|
|
|
|
|
static constexpr char kComposingBaseKey[] = "composingBase";
|
|
|
|
|
|
|
|
|
|
static constexpr char kComposingExtentKey[] = "composingExtent";
|
|
|
|
|
|
|
|
|
|
static constexpr char kSelectionAffinityKey[] = "selectionAffinity";
|
|
|
|
|
static constexpr char kAffinityDownstream[] = "TextAffinity.downstream";
|
|
|
|
|
|
|
|
|
|
static constexpr char kSelectionBaseKey[] = "selectionBase";
|
|
|
|
|
static constexpr char kSelectionExtentKey[] = "selectionExtent";
|
|
|
|
|
|
|
|
|
|
static constexpr char kSelectionIsDirectionalKey[] = "selectionIsDirectional";
|
|
|
|
|
|
|
|
|
|
static constexpr char kTextKey[] = "text";
|
|
|
|
|
|
|
|
|
|
// Input client configuration keys.
|
|
|
|
|
static constexpr char kTextInputAction[] = "inputAction";
|
|
|
|
|
static constexpr char kTextInputType[] = "inputType";
|
|
|
|
|
static constexpr char kTextInputTypeName[] = "name";
|
|
|
|
|
|
2019-09-09 03:13:59 +08:00
|
|
|
#if defined(_MSC_VER)
|
|
|
|
|
// TODO(naifu): This temporary code is to solve link error.(VS2015/2017)
|
|
|
|
|
// https://social.msdn.microsoft.com/Forums/vstudio/en-US/8f40dcd8-c67f-4eba-9134-a19b9178e481/vs-2015-rc-linker-stdcodecvt-error
|
|
|
|
|
std::locale::id std::codecvt<char32_t, char, _Mbstatet>::id;
|
|
|
|
|
#endif // defined(_MSC_VER)
|
|
|
|
|
|
2019-04-09 17:10:46 -07:00
|
|
|
namespace flutter {
|
2019-03-20 17:15:52 -04:00
|
|
|
|
|
|
|
|
TextInputModel::TextInputModel(int client_id, const rapidjson::Value& config)
|
2019-09-09 03:13:59 +08:00
|
|
|
: client_id_(client_id),
|
2019-03-20 17:15:52 -04:00
|
|
|
selection_base_(text_.begin()),
|
|
|
|
|
selection_extent_(text_.begin()) {
|
|
|
|
|
// TODO: Improve error handling during refactoring; this is just minimal
|
|
|
|
|
// checking to avoid asserts since RapidJSON is stricter than jsoncpp.
|
|
|
|
|
if (config.IsObject()) {
|
|
|
|
|
auto input_action = config.FindMember(kTextInputAction);
|
|
|
|
|
if (input_action != config.MemberEnd() && input_action->value.IsString()) {
|
|
|
|
|
input_action_ = input_action->value.GetString();
|
|
|
|
|
}
|
|
|
|
|
auto input_type_info = config.FindMember(kTextInputType);
|
|
|
|
|
if (input_type_info != config.MemberEnd() &&
|
|
|
|
|
input_type_info->value.IsObject()) {
|
|
|
|
|
auto input_type = input_type_info->value.FindMember(kTextInputTypeName);
|
|
|
|
|
if (input_type != input_type_info->value.MemberEnd() &&
|
|
|
|
|
input_type->value.IsString()) {
|
|
|
|
|
input_type_ = input_type->value.GetString();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TextInputModel::~TextInputModel() = default;
|
|
|
|
|
|
|
|
|
|
bool TextInputModel::SetEditingState(size_t selection_base,
|
|
|
|
|
size_t selection_extent,
|
|
|
|
|
const std::string& text) {
|
|
|
|
|
if (selection_base > selection_extent) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
// Only checks extent since it is implicitly greater-than-or-equal-to base.
|
|
|
|
|
if (selection_extent > text.size()) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2019-09-09 03:13:59 +08:00
|
|
|
std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> utf32conv;
|
|
|
|
|
text_ = utf32conv.from_bytes(text);
|
2019-03-20 17:15:52 -04:00
|
|
|
selection_base_ = text_.begin() + selection_base;
|
|
|
|
|
selection_extent_ = text_.begin() + selection_extent;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TextInputModel::DeleteSelected() {
|
|
|
|
|
selection_base_ = text_.erase(selection_base_, selection_extent_);
|
|
|
|
|
// Moves extent back to base, so that it is a single cursor placement again.
|
|
|
|
|
selection_extent_ = selection_base_;
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-09 03:13:59 +08:00
|
|
|
void TextInputModel::AddCharacter(char32_t c) {
|
2019-03-20 17:15:52 -04:00
|
|
|
if (selection_base_ != selection_extent_) {
|
|
|
|
|
DeleteSelected();
|
|
|
|
|
}
|
|
|
|
|
selection_extent_ = text_.insert(selection_extent_, c);
|
|
|
|
|
selection_extent_++;
|
|
|
|
|
selection_base_ = selection_extent_;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool TextInputModel::Backspace() {
|
|
|
|
|
if (selection_base_ != selection_extent_) {
|
|
|
|
|
DeleteSelected();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
if (selection_base_ != text_.begin()) {
|
|
|
|
|
selection_base_ = text_.erase(selection_base_ - 1, selection_base_);
|
|
|
|
|
selection_extent_ = selection_base_;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false; // No edits happened.
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool TextInputModel::Delete() {
|
|
|
|
|
if (selection_base_ != selection_extent_) {
|
|
|
|
|
DeleteSelected();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
if (selection_base_ != text_.end()) {
|
|
|
|
|
selection_base_ = text_.erase(selection_base_, selection_base_ + 1);
|
|
|
|
|
selection_extent_ = selection_base_;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TextInputModel::MoveCursorToBeginning() {
|
|
|
|
|
selection_base_ = text_.begin();
|
|
|
|
|
selection_extent_ = text_.begin();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TextInputModel::MoveCursorToEnd() {
|
|
|
|
|
selection_base_ = text_.end();
|
|
|
|
|
selection_extent_ = text_.end();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool TextInputModel::MoveCursorForward() {
|
|
|
|
|
// If about to move set to the end of the highlight (when not selecting).
|
|
|
|
|
if (selection_base_ != selection_extent_) {
|
|
|
|
|
selection_base_ = selection_extent_;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
// If not at the end, move the extent forward.
|
|
|
|
|
if (selection_extent_ != text_.end()) {
|
|
|
|
|
selection_extent_++;
|
|
|
|
|
selection_base_++;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool TextInputModel::MoveCursorBack() {
|
|
|
|
|
// If about to move set to the beginning of the highlight
|
|
|
|
|
// (when not selecting).
|
|
|
|
|
if (selection_base_ != selection_extent_) {
|
|
|
|
|
selection_extent_ = selection_base_;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
// If not at the start, move the beginning backward.
|
|
|
|
|
if (selection_base_ != text_.begin()) {
|
|
|
|
|
selection_base_--;
|
|
|
|
|
selection_extent_--;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::unique_ptr<rapidjson::Document> TextInputModel::GetState() const {
|
|
|
|
|
// TODO(stuartmorgan): Move client_id out up to the plugin so that this
|
|
|
|
|
// function just returns the editing state.
|
|
|
|
|
auto args = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
|
|
|
|
|
auto& allocator = args->GetAllocator();
|
|
|
|
|
args->PushBack(client_id_, allocator);
|
|
|
|
|
|
|
|
|
|
rapidjson::Value editing_state(rapidjson::kObjectType);
|
|
|
|
|
// TODO(awdavies): Most of these are hard-coded for now.
|
|
|
|
|
editing_state.AddMember(kComposingBaseKey, -1, allocator);
|
|
|
|
|
editing_state.AddMember(kComposingExtentKey, -1, allocator);
|
|
|
|
|
editing_state.AddMember(kSelectionAffinityKey, kAffinityDownstream,
|
|
|
|
|
allocator);
|
|
|
|
|
editing_state.AddMember(kSelectionBaseKey,
|
|
|
|
|
static_cast<int>(selection_base_ - text_.begin()),
|
|
|
|
|
allocator);
|
|
|
|
|
editing_state.AddMember(kSelectionExtentKey,
|
|
|
|
|
static_cast<int>(selection_extent_ - text_.begin()),
|
|
|
|
|
allocator);
|
|
|
|
|
editing_state.AddMember(kSelectionIsDirectionalKey, false, allocator);
|
2019-09-09 03:13:59 +08:00
|
|
|
std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> utf8conv;
|
|
|
|
|
editing_state.AddMember(
|
|
|
|
|
kTextKey, rapidjson::Value(utf8conv.to_bytes(text_), allocator).Move(),
|
|
|
|
|
allocator);
|
2019-03-20 17:15:52 -04:00
|
|
|
args->PushBack(editing_state, allocator);
|
|
|
|
|
return args;
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-09 17:10:46 -07:00
|
|
|
} // namespace flutter
|