| // 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" |
| |
| #include <codecvt> |
| #include <iostream> |
| #include <locale> |
| |
| // 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"; |
| |
| #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) |
| |
| namespace flutter { |
| |
| TextInputModel::TextInputModel(int client_id, const rapidjson::Value& config) |
| : client_id_(client_id), |
| 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; |
| } |
| std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> utf32conv; |
| text_ = utf32conv.from_bytes(text); |
| 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_; |
| } |
| |
| void TextInputModel::AddCharacter(char32_t c) { |
| 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); |
| std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> utf8conv; |
| editing_state.AddMember( |
| kTextKey, rapidjson::Value(utf8conv.to_bytes(text_), allocator).Move(), |
| allocator); |
| args->PushBack(editing_state, allocator); |
| return args; |
| } |
| |
| } // namespace flutter |