• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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