@@ -22,6 +22,9 @@ static constexpr char kHideMethod[] = "TextInput.hide";
22
22
static constexpr char kUpdateEditingStateMethod [] =
23
23
" TextInputClient.updateEditingState" ;
24
24
static constexpr char kPerformActionMethod [] = " TextInputClient.performAction" ;
25
+ static constexpr char kSetEditableSizeAndTransform [] =
26
+ " TextInput.setEditableSizeAndTransform" ;
27
+ static constexpr char kSetMarkedTextRect [] = " TextInput.setMarkedTextRect" ;
25
28
26
29
static constexpr char kInputActionKey [] = " inputAction" ;
27
30
static constexpr char kTextInputTypeKey [] = " inputType" ;
@@ -34,6 +37,8 @@ static constexpr char kSelectionIsDirectionalKey[] = "selectionIsDirectional";
34
37
static constexpr char kComposingBaseKey [] = " composingBase" ;
35
38
static constexpr char kComposingExtentKey [] = " composingExtent" ;
36
39
40
+ static constexpr char kTransform [] = " transform" ;
41
+
37
42
static constexpr char kTextAffinityDownstream [] = " TextAffinity.downstream" ;
38
43
static constexpr char kMultilineInputType [] = " TextInputType.multiline" ;
39
44
@@ -57,6 +62,23 @@ struct _FlTextInputPlugin {
57
62
GtkIMContext* im_context;
58
63
59
64
flutter::TextInputModel* text_model;
65
+
66
+ // The owning Flutter view.
67
+ FlView* view;
68
+
69
+ // A 4x4 matrix that maps from `EditableText` local coordinates to the
70
+ // coordinate system of `PipelineOwner.rootNode`.
71
+ double editabletext_transform[4 ][4 ];
72
+
73
+ // The smallest rect, in local coordinates, of the text in the composing
74
+ // range, or of the caret in the case where there is no current composing
75
+ // range. This value is updated via `TextInput.setMarkedTextRect` messages
76
+ // over the text input channel.
77
+ GdkRectangle composing_rect;
78
+
79
+ // A cached copy of the composing (pre-edit in Gtk terminology) rect in
80
+ // window coordinates.
81
+ GdkRectangle preedit_rect;
60
82
};
61
83
62
84
G_DEFINE_TYPE (FlTextInputPlugin, fl_text_input_plugin, G_TYPE_OBJECT)
@@ -100,13 +122,20 @@ static void update_editing_state(FlTextInputPlugin* self) {
100
122
value, kSelectionExtentKey ,
101
123
fl_value_new_int (self->text_model ->selection_extent ()));
102
124
125
+ int composing_base =
126
+ self->text_model ->composing () ? self->text_model ->composing_base () : -1 ;
127
+ int composing_extent =
128
+ self->text_model ->composing () ? self->text_model ->composing_extent () : -1 ;
129
+ fl_value_set_string_take (value, kComposingBaseKey ,
130
+ fl_value_new_int (composing_base));
131
+ fl_value_set_string_take (value, kComposingExtentKey ,
132
+ fl_value_new_int (composing_extent));
133
+
103
134
// The following keys are not implemented and set to default values.
104
135
fl_value_set_string_take (value, kSelectionAffinityKey ,
105
136
fl_value_new_string (kTextAffinityDownstream ));
106
137
fl_value_set_string_take (value, kSelectionIsDirectionalKey ,
107
138
fl_value_new_bool (FALSE ));
108
- fl_value_set_string_take (value, kComposingBaseKey , fl_value_new_int (-1 ));
109
- fl_value_set_string_take (value, kComposingExtentKey , fl_value_new_int (-1 ));
110
139
111
140
fl_value_append (args, value);
112
141
@@ -139,9 +168,50 @@ static void perform_action(FlTextInputPlugin* self) {
139
168
nullptr , perform_action_response_cb, self);
140
169
}
141
170
171
+ // Signal handler for GtkIMContext::preedit-start
172
+ static void im_preedit_start_cb (FlTextInputPlugin* self) {
173
+ self->text_model ->BeginComposing ();
174
+
175
+ // Set the top-level window used for system input method windows.
176
+ GdkWindow* window =
177
+ gtk_widget_get_window (gtk_widget_get_toplevel (GTK_WIDGET (self->view )));
178
+ gtk_im_context_set_client_window (self->im_context , window);
179
+
180
+ // Set the cursor location in window co-ordinates so that Gtk can position
181
+ // any system input method windows.
182
+ gtk_im_context_set_cursor_location (self->im_context , &self->preedit_rect );
183
+ }
184
+
185
+ // Signal handler for GtkIMContext::preedit-changed
186
+ static void im_preedit_changed_cb (FlTextInputPlugin* self) {
187
+ // Set the cursor location in window co-ordinates so that Gtk can position
188
+ // any system input method windows.
189
+ gtk_im_context_set_cursor_location (self->im_context , &self->preedit_rect );
190
+
191
+ gchar* buf = NULL ;
192
+ gint cursor_offset = 0 ;
193
+ gtk_im_context_get_preedit_string (self->im_context , &buf, NULL ,
194
+ &cursor_offset);
195
+ cursor_offset += self->text_model ->composing_base ();
196
+ self->text_model ->UpdateComposingText (buf);
197
+ self->text_model ->SetSelection (cursor_offset, cursor_offset);
198
+
199
+ update_editing_state (self);
200
+ }
201
+
142
202
// Signal handler for GtkIMContext::commit
143
203
static void im_commit_cb (FlTextInputPlugin* self, const gchar* text) {
144
- self->text_model ->AddText (text);
204
+ if (self->text_model ->composing ()) {
205
+ self->text_model ->CommitComposing ();
206
+ } else {
207
+ self->text_model ->AddText (text);
208
+ }
209
+ update_editing_state (self);
210
+ }
211
+
212
+ // Signal handler for GtkIMContext::preedit-end
213
+ static void im_preedit_end_cb (FlTextInputPlugin* self) {
214
+ self->text_model ->EndComposing ();
145
215
update_editing_state (self);
146
216
}
147
217
@@ -209,6 +279,8 @@ static FlMethodResponse* set_editing_state(FlTextInputPlugin* self,
209
279
FlValue* args) {
210
280
const gchar* text =
211
281
fl_value_get_string (fl_value_lookup_string (args, kTextKey ));
282
+ self->text_model ->SetText (text);
283
+
212
284
int64_t selection_base =
213
285
fl_value_get_int (fl_value_lookup_string (args, kSelectionBaseKey ));
214
286
int64_t selection_extent =
@@ -217,10 +289,21 @@ static FlMethodResponse* set_editing_state(FlTextInputPlugin* self,
217
289
if (selection_base == -1 && selection_extent == -1 ) {
218
290
selection_base = selection_extent = 0 ;
219
291
}
220
-
221
- self->text_model ->SetText (text);
222
292
self->text_model ->SetSelection (selection_base, selection_extent);
223
293
294
+ int64_t composing_base =
295
+ fl_value_get_int (fl_value_lookup_string (args, kComposingBaseKey ));
296
+ int64_t composing_extent =
297
+ fl_value_get_int (fl_value_lookup_string (args, kComposingExtentKey ));
298
+ if (composing_base == -1 && composing_extent == -1 ) {
299
+ self->text_model ->EndComposing ();
300
+ } else {
301
+ size_t composing_start = std::min (composing_base, composing_extent);
302
+ size_t cursor_offset = selection_base - composing_start;
303
+ self->text_model ->SetComposingRange (composing_base, composing_extent,
304
+ cursor_offset);
305
+ }
306
+
224
307
return FL_METHOD_RESPONSE (fl_method_success_response_new (nullptr ));
225
308
}
226
309
@@ -238,6 +321,58 @@ static FlMethodResponse* hide(FlTextInputPlugin* self) {
238
321
return FL_METHOD_RESPONSE (fl_method_success_response_new (nullptr ));
239
322
}
240
323
324
+ static void update_preedit_rect (FlTextInputPlugin* self) {
325
+ // Skip update if not composing since composing base/extent are 0.
326
+ if (!self->text_model ->composing ()) {
327
+ return ;
328
+ }
329
+ gint fl_x = self->composing_rect .x * self->editabletext_transform [0 ][0 ] +
330
+ self->composing_rect .y * self->editabletext_transform [1 ][0 ] +
331
+ self->editabletext_transform [3 ][0 ] + self->composing_rect .width ;
332
+ gint fl_y = self->composing_rect .x * self->editabletext_transform [0 ][1 ] +
333
+ self->composing_rect .y * self->editabletext_transform [1 ][1 ] +
334
+ self->editabletext_transform [3 ][1 ] + self->composing_rect .height ;
335
+
336
+ gtk_widget_translate_coordinates (
337
+ GTK_WIDGET (self->view ), gtk_widget_get_toplevel (GTK_WIDGET (self->view )),
338
+ fl_x, fl_y, &self->preedit_rect .x , &self->preedit_rect .y );
339
+ }
340
+
341
+ // Handle updates from the framework to the cached coordinate transform of the
342
+ // `EditableText` relative to the root coordinate system.
343
+ static FlMethodResponse* set_editable_size_and_transform (
344
+ FlTextInputPlugin* self,
345
+ FlValue* args) {
346
+ FlValue* transform = fl_value_lookup_string (args, kTransform );
347
+ size_t transform_len = fl_value_get_length (transform);
348
+ g_warn_if_fail (transform_len == 16 );
349
+
350
+ for (size_t i = 0 ; i < transform_len; ++i) {
351
+ double val = fl_value_get_float (fl_value_get_list_value (transform, i));
352
+ self->editabletext_transform [i / 4 ][i % 4 ] = val;
353
+ }
354
+ update_preedit_rect (self);
355
+
356
+ return FL_METHOD_RESPONSE (fl_method_success_response_new (nullptr ));
357
+ }
358
+
359
+ // Handle updates from the framework to the cached composing rect in local
360
+ // coordinates.
361
+ static FlMethodResponse* set_marked_text_rect (FlTextInputPlugin* self,
362
+ FlValue* args) {
363
+ self->composing_rect .x =
364
+ fl_value_get_float (fl_value_lookup_string (args, " x" ));
365
+ self->composing_rect .y =
366
+ fl_value_get_float (fl_value_lookup_string (args, " y" ));
367
+ self->composing_rect .width =
368
+ fl_value_get_float (fl_value_lookup_string (args, " width" ));
369
+ self->composing_rect .height =
370
+ fl_value_get_float (fl_value_lookup_string (args, " height" ));
371
+ update_preedit_rect (self);
372
+
373
+ return FL_METHOD_RESPONSE (fl_method_success_response_new (nullptr ));
374
+ }
375
+
241
376
// Called when a method call is received from Flutter.
242
377
static void method_call_cb (FlMethodChannel* channel,
243
378
FlMethodCall* method_call,
@@ -258,6 +393,10 @@ static void method_call_cb(FlMethodChannel* channel,
258
393
response = clear_client (self);
259
394
} else if (strcmp (method, kHideMethod ) == 0 ) {
260
395
response = hide (self);
396
+ } else if (strcmp (method, kSetEditableSizeAndTransform ) == 0 ) {
397
+ response = set_editable_size_and_transform (self, args);
398
+ } else if (strcmp (method, kSetMarkedTextRect ) == 0 ) {
399
+ response = set_marked_text_rect (self, args);
261
400
} else {
262
401
response = FL_METHOD_RESPONSE (fl_method_not_implemented_response_new ());
263
402
}
@@ -268,6 +407,11 @@ static void method_call_cb(FlMethodChannel* channel,
268
407
}
269
408
}
270
409
410
+ static void view_weak_notify_cb (gpointer user_data, GObject* object) {
411
+ FlTextInputPlugin* self = FL_TEXT_INPUT_PLUGIN (object);
412
+ self->view = nullptr ;
413
+ }
414
+
271
415
static void fl_text_input_plugin_dispose (GObject* object) {
272
416
FlTextInputPlugin* self = FL_TEXT_INPUT_PLUGIN (object);
273
417
@@ -290,6 +434,15 @@ static void fl_text_input_plugin_init(FlTextInputPlugin* self) {
290
434
self->client_id = kClientIdUnset ;
291
435
self->im_context = gtk_im_multicontext_new ();
292
436
self->input_multiline = FALSE ;
437
+ g_signal_connect_object (self->im_context , " preedit-start" ,
438
+ G_CALLBACK (im_preedit_start_cb), self,
439
+ G_CONNECT_SWAPPED);
440
+ g_signal_connect_object (self->im_context , " preedit-end" ,
441
+ G_CALLBACK (im_preedit_end_cb), self,
442
+ G_CONNECT_SWAPPED);
443
+ g_signal_connect_object (self->im_context , " preedit-changed" ,
444
+ G_CALLBACK (im_preedit_changed_cb), self,
445
+ G_CONNECT_SWAPPED);
293
446
g_signal_connect_object (self->im_context , " commit" , G_CALLBACK (im_commit_cb),
294
447
self, G_CONNECT_SWAPPED);
295
448
g_signal_connect_object (self->im_context , " retrieve-surrounding" ,
@@ -301,7 +454,8 @@ static void fl_text_input_plugin_init(FlTextInputPlugin* self) {
301
454
self->text_model = new flutter::TextInputModel ();
302
455
}
303
456
304
- FlTextInputPlugin* fl_text_input_plugin_new (FlBinaryMessenger* messenger) {
457
+ FlTextInputPlugin* fl_text_input_plugin_new (FlBinaryMessenger* messenger,
458
+ FlView* view) {
305
459
g_return_val_if_fail (FL_IS_BINARY_MESSENGER (messenger), nullptr );
306
460
307
461
FlTextInputPlugin* self = FL_TEXT_INPUT_PLUGIN (
@@ -312,7 +466,8 @@ FlTextInputPlugin* fl_text_input_plugin_new(FlBinaryMessenger* messenger) {
312
466
fl_method_channel_new (messenger, kChannelName , FL_METHOD_CODEC (codec));
313
467
fl_method_channel_set_method_call_handler (self->channel , method_call_cb, self,
314
468
nullptr );
315
-
469
+ self->view = view;
470
+ g_object_weak_ref (G_OBJECT (view), view_weak_notify_cb, self);
316
471
return self;
317
472
}
318
473
0 commit comments