1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "flutter/shell/platform/common/cpp/text_input_model.h"
6
7 #include <iostream>
8
9 // TODO(awdavies): Need to fix this regarding issue #47.
10 static constexpr char kComposingBaseKey[] = "composingBase";
11
12 static constexpr char kComposingExtentKey[] = "composingExtent";
13
14 static constexpr char kSelectionAffinityKey[] = "selectionAffinity";
15 static constexpr char kAffinityDownstream[] = "TextAffinity.downstream";
16
17 static constexpr char kSelectionBaseKey[] = "selectionBase";
18 static constexpr char kSelectionExtentKey[] = "selectionExtent";
19
20 static constexpr char kSelectionIsDirectionalKey[] = "selectionIsDirectional";
21
22 static constexpr char kTextKey[] = "text";
23
24 // Input client configuration keys.
25 static constexpr char kTextInputAction[] = "inputAction";
26 static constexpr char kTextInputType[] = "inputType";
27 static constexpr char kTextInputTypeName[] = "name";
28
29 namespace flutter {
30
TextInputModel(int client_id,const rapidjson::Value & config)31 TextInputModel::TextInputModel(int client_id, const rapidjson::Value& config)
32 : text_(""),
33 client_id_(client_id),
34 selection_base_(text_.begin()),
35 selection_extent_(text_.begin()) {
36 // TODO: Improve error handling during refactoring; this is just minimal
37 // checking to avoid asserts since RapidJSON is stricter than jsoncpp.
38 if (config.IsObject()) {
39 auto input_action = config.FindMember(kTextInputAction);
40 if (input_action != config.MemberEnd() && input_action->value.IsString()) {
41 input_action_ = input_action->value.GetString();
42 }
43 auto input_type_info = config.FindMember(kTextInputType);
44 if (input_type_info != config.MemberEnd() &&
45 input_type_info->value.IsObject()) {
46 auto input_type = input_type_info->value.FindMember(kTextInputTypeName);
47 if (input_type != input_type_info->value.MemberEnd() &&
48 input_type->value.IsString()) {
49 input_type_ = input_type->value.GetString();
50 }
51 }
52 }
53 }
54
55 TextInputModel::~TextInputModel() = default;
56
SetEditingState(size_t selection_base,size_t selection_extent,const std::string & text)57 bool TextInputModel::SetEditingState(size_t selection_base,
58 size_t selection_extent,
59 const std::string& text) {
60 if (selection_base > selection_extent) {
61 return false;
62 }
63 // Only checks extent since it is implicitly greater-than-or-equal-to base.
64 if (selection_extent > text.size()) {
65 return false;
66 }
67 text_ = std::string(text);
68 selection_base_ = text_.begin() + selection_base;
69 selection_extent_ = text_.begin() + selection_extent;
70 return true;
71 }
72
DeleteSelected()73 void TextInputModel::DeleteSelected() {
74 selection_base_ = text_.erase(selection_base_, selection_extent_);
75 // Moves extent back to base, so that it is a single cursor placement again.
76 selection_extent_ = selection_base_;
77 }
78
AddCharacter(char c)79 void TextInputModel::AddCharacter(char c) {
80 if (selection_base_ != selection_extent_) {
81 DeleteSelected();
82 }
83 selection_extent_ = text_.insert(selection_extent_, c);
84 selection_extent_++;
85 selection_base_ = selection_extent_;
86 }
87
Backspace()88 bool TextInputModel::Backspace() {
89 if (selection_base_ != selection_extent_) {
90 DeleteSelected();
91 return true;
92 }
93 if (selection_base_ != text_.begin()) {
94 selection_base_ = text_.erase(selection_base_ - 1, selection_base_);
95 selection_extent_ = selection_base_;
96 return true;
97 }
98 return false; // No edits happened.
99 }
100
Delete()101 bool TextInputModel::Delete() {
102 if (selection_base_ != selection_extent_) {
103 DeleteSelected();
104 return true;
105 }
106 if (selection_base_ != text_.end()) {
107 selection_base_ = text_.erase(selection_base_, selection_base_ + 1);
108 selection_extent_ = selection_base_;
109 return true;
110 }
111 return false;
112 }
113
MoveCursorToBeginning()114 void TextInputModel::MoveCursorToBeginning() {
115 selection_base_ = text_.begin();
116 selection_extent_ = text_.begin();
117 }
118
MoveCursorToEnd()119 void TextInputModel::MoveCursorToEnd() {
120 selection_base_ = text_.end();
121 selection_extent_ = text_.end();
122 }
123
MoveCursorForward()124 bool TextInputModel::MoveCursorForward() {
125 // If about to move set to the end of the highlight (when not selecting).
126 if (selection_base_ != selection_extent_) {
127 selection_base_ = selection_extent_;
128 return true;
129 }
130 // If not at the end, move the extent forward.
131 if (selection_extent_ != text_.end()) {
132 selection_extent_++;
133 selection_base_++;
134 return true;
135 }
136 return false;
137 }
138
MoveCursorBack()139 bool TextInputModel::MoveCursorBack() {
140 // If about to move set to the beginning of the highlight
141 // (when not selecting).
142 if (selection_base_ != selection_extent_) {
143 selection_extent_ = selection_base_;
144 return true;
145 }
146 // If not at the start, move the beginning backward.
147 if (selection_base_ != text_.begin()) {
148 selection_base_--;
149 selection_extent_--;
150 return true;
151 }
152 return false;
153 }
154
GetState() const155 std::unique_ptr<rapidjson::Document> TextInputModel::GetState() const {
156 // TODO(stuartmorgan): Move client_id out up to the plugin so that this
157 // function just returns the editing state.
158 auto args = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
159 auto& allocator = args->GetAllocator();
160 args->PushBack(client_id_, allocator);
161
162 rapidjson::Value editing_state(rapidjson::kObjectType);
163 // TODO(awdavies): Most of these are hard-coded for now.
164 editing_state.AddMember(kComposingBaseKey, -1, allocator);
165 editing_state.AddMember(kComposingExtentKey, -1, allocator);
166 editing_state.AddMember(kSelectionAffinityKey, kAffinityDownstream,
167 allocator);
168 editing_state.AddMember(kSelectionBaseKey,
169 static_cast<int>(selection_base_ - text_.begin()),
170 allocator);
171 editing_state.AddMember(kSelectionExtentKey,
172 static_cast<int>(selection_extent_ - text_.begin()),
173 allocator);
174 editing_state.AddMember(kSelectionIsDirectionalKey, false, allocator);
175 editing_state.AddMember(kTextKey, rapidjson::Value(text_, allocator).Move(),
176 allocator);
177 args->PushBack(editing_state, allocator);
178 return args;
179 }
180
181 } // namespace flutter
182