|
|
|
|
@@ -59,7 +59,7 @@ typedef enum {
|
|
|
|
|
kFlTextInputTypeNone,
|
|
|
|
|
} FlTextInputType;
|
|
|
|
|
|
|
|
|
|
struct FlTextInputHandlerPrivate {
|
|
|
|
|
struct _FlTextInputHandler {
|
|
|
|
|
GObject parent_instance;
|
|
|
|
|
|
|
|
|
|
FlMethodChannel* channel;
|
|
|
|
|
@@ -97,9 +97,7 @@ struct FlTextInputHandlerPrivate {
|
|
|
|
|
GdkRectangle composing_rect;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
G_DEFINE_TYPE_WITH_PRIVATE(FlTextInputHandler,
|
|
|
|
|
fl_text_input_handler,
|
|
|
|
|
G_TYPE_OBJECT)
|
|
|
|
|
G_DEFINE_TYPE(FlTextInputHandler, fl_text_input_handler, G_TYPE_OBJECT)
|
|
|
|
|
|
|
|
|
|
// Completes method call and returns TRUE if the call was successful.
|
|
|
|
|
static gboolean finish_method(GObject* object,
|
|
|
|
|
@@ -126,17 +124,14 @@ static void update_editing_state_response_cb(GObject* object,
|
|
|
|
|
|
|
|
|
|
// Informs Flutter of text input changes.
|
|
|
|
|
static void update_editing_state(FlTextInputHandler* self) {
|
|
|
|
|
FlTextInputHandlerPrivate* priv = static_cast<FlTextInputHandlerPrivate*>(
|
|
|
|
|
fl_text_input_handler_get_instance_private(self));
|
|
|
|
|
|
|
|
|
|
g_autoptr(FlValue) args = fl_value_new_list();
|
|
|
|
|
fl_value_append_take(args, fl_value_new_int(priv->client_id));
|
|
|
|
|
fl_value_append_take(args, fl_value_new_int(self->client_id));
|
|
|
|
|
g_autoptr(FlValue) value = fl_value_new_map();
|
|
|
|
|
|
|
|
|
|
flutter::TextRange selection = priv->text_model->selection();
|
|
|
|
|
flutter::TextRange selection = self->text_model->selection();
|
|
|
|
|
fl_value_set_string_take(
|
|
|
|
|
value, kTextKey,
|
|
|
|
|
fl_value_new_string(priv->text_model->GetText().c_str()));
|
|
|
|
|
fl_value_new_string(self->text_model->GetText().c_str()));
|
|
|
|
|
fl_value_set_string_take(value, kSelectionBaseKey,
|
|
|
|
|
fl_value_new_int(selection.base()));
|
|
|
|
|
fl_value_set_string_take(value, kSelectionExtentKey,
|
|
|
|
|
@@ -144,9 +139,9 @@ static void update_editing_state(FlTextInputHandler* self) {
|
|
|
|
|
|
|
|
|
|
int composing_base = -1;
|
|
|
|
|
int composing_extent = -1;
|
|
|
|
|
if (!priv->text_model->composing_range().collapsed()) {
|
|
|
|
|
composing_base = priv->text_model->composing_range().base();
|
|
|
|
|
composing_extent = priv->text_model->composing_range().extent();
|
|
|
|
|
if (!self->text_model->composing_range().collapsed()) {
|
|
|
|
|
composing_base = self->text_model->composing_range().base();
|
|
|
|
|
composing_extent = self->text_model->composing_range().extent();
|
|
|
|
|
}
|
|
|
|
|
fl_value_set_string_take(value, kComposingBaseKey,
|
|
|
|
|
fl_value_new_int(composing_base));
|
|
|
|
|
@@ -161,7 +156,7 @@ static void update_editing_state(FlTextInputHandler* self) {
|
|
|
|
|
|
|
|
|
|
fl_value_append(args, value);
|
|
|
|
|
|
|
|
|
|
fl_method_channel_invoke_method(priv->channel, kUpdateEditingStateMethod,
|
|
|
|
|
fl_method_channel_invoke_method(self->channel, kUpdateEditingStateMethod,
|
|
|
|
|
args, nullptr,
|
|
|
|
|
update_editing_state_response_cb, self);
|
|
|
|
|
}
|
|
|
|
|
@@ -169,11 +164,8 @@ static void update_editing_state(FlTextInputHandler* self) {
|
|
|
|
|
// Informs Flutter of text input changes by passing just the delta.
|
|
|
|
|
static void update_editing_state_with_delta(FlTextInputHandler* self,
|
|
|
|
|
flutter::TextEditingDelta* delta) {
|
|
|
|
|
FlTextInputHandlerPrivate* priv = static_cast<FlTextInputHandlerPrivate*>(
|
|
|
|
|
fl_text_input_handler_get_instance_private(self));
|
|
|
|
|
|
|
|
|
|
g_autoptr(FlValue) args = fl_value_new_list();
|
|
|
|
|
fl_value_append_take(args, fl_value_new_int(priv->client_id));
|
|
|
|
|
fl_value_append_take(args, fl_value_new_int(self->client_id));
|
|
|
|
|
|
|
|
|
|
g_autoptr(FlValue) deltaValue = fl_value_new_map();
|
|
|
|
|
fl_value_set_string_take(deltaValue, "oldText",
|
|
|
|
|
@@ -188,7 +180,7 @@ static void update_editing_state_with_delta(FlTextInputHandler* self,
|
|
|
|
|
fl_value_set_string_take(deltaValue, "deltaEnd",
|
|
|
|
|
fl_value_new_int(delta->delta_end()));
|
|
|
|
|
|
|
|
|
|
flutter::TextRange selection = priv->text_model->selection();
|
|
|
|
|
flutter::TextRange selection = self->text_model->selection();
|
|
|
|
|
fl_value_set_string_take(deltaValue, "selectionBase",
|
|
|
|
|
fl_value_new_int(selection.base()));
|
|
|
|
|
|
|
|
|
|
@@ -203,9 +195,9 @@ static void update_editing_state_with_delta(FlTextInputHandler* self,
|
|
|
|
|
|
|
|
|
|
int composing_base = -1;
|
|
|
|
|
int composing_extent = -1;
|
|
|
|
|
if (!priv->text_model->composing_range().collapsed()) {
|
|
|
|
|
composing_base = priv->text_model->composing_range().base();
|
|
|
|
|
composing_extent = priv->text_model->composing_range().extent();
|
|
|
|
|
if (!self->text_model->composing_range().collapsed()) {
|
|
|
|
|
composing_base = self->text_model->composing_range().base();
|
|
|
|
|
composing_extent = self->text_model->composing_range().extent();
|
|
|
|
|
}
|
|
|
|
|
fl_value_set_string_take(deltaValue, "composingBase",
|
|
|
|
|
fl_value_new_int(composing_base));
|
|
|
|
|
@@ -220,7 +212,7 @@ static void update_editing_state_with_delta(FlTextInputHandler* self,
|
|
|
|
|
fl_value_append(args, value);
|
|
|
|
|
|
|
|
|
|
fl_method_channel_invoke_method(
|
|
|
|
|
priv->channel, kUpdateEditingStateWithDeltasMethod, args, nullptr,
|
|
|
|
|
self->channel, kUpdateEditingStateWithDeltasMethod, args, nullptr,
|
|
|
|
|
update_editing_state_response_cb, self);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -236,48 +228,41 @@ static void perform_action_response_cb(GObject* object,
|
|
|
|
|
|
|
|
|
|
// Inform Flutter that the input has been activated.
|
|
|
|
|
static void perform_action(FlTextInputHandler* self) {
|
|
|
|
|
FlTextInputHandlerPrivate* priv = static_cast<FlTextInputHandlerPrivate*>(
|
|
|
|
|
fl_text_input_handler_get_instance_private(self));
|
|
|
|
|
|
|
|
|
|
g_return_if_fail(FL_IS_TEXT_INPUT_HANDLER(self));
|
|
|
|
|
g_return_if_fail(priv->client_id != 0);
|
|
|
|
|
g_return_if_fail(priv->input_action != nullptr);
|
|
|
|
|
g_return_if_fail(self->client_id != 0);
|
|
|
|
|
g_return_if_fail(self->input_action != nullptr);
|
|
|
|
|
|
|
|
|
|
g_autoptr(FlValue) args = fl_value_new_list();
|
|
|
|
|
fl_value_append_take(args, fl_value_new_int(priv->client_id));
|
|
|
|
|
fl_value_append_take(args, fl_value_new_string(priv->input_action));
|
|
|
|
|
fl_value_append_take(args, fl_value_new_int(self->client_id));
|
|
|
|
|
fl_value_append_take(args, fl_value_new_string(self->input_action));
|
|
|
|
|
|
|
|
|
|
fl_method_channel_invoke_method(priv->channel, kPerformActionMethod, args,
|
|
|
|
|
fl_method_channel_invoke_method(self->channel, kPerformActionMethod, args,
|
|
|
|
|
nullptr, perform_action_response_cb, self);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Signal handler for GtkIMContext::preedit-start
|
|
|
|
|
static void im_preedit_start_cb(FlTextInputHandler* self) {
|
|
|
|
|
FlTextInputHandlerPrivate* priv = static_cast<FlTextInputHandlerPrivate*>(
|
|
|
|
|
fl_text_input_handler_get_instance_private(self));
|
|
|
|
|
priv->text_model->BeginComposing();
|
|
|
|
|
self->text_model->BeginComposing();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Signal handler for GtkIMContext::preedit-changed
|
|
|
|
|
static void im_preedit_changed_cb(FlTextInputHandler* self) {
|
|
|
|
|
FlTextInputHandlerPrivate* priv = static_cast<FlTextInputHandlerPrivate*>(
|
|
|
|
|
fl_text_input_handler_get_instance_private(self));
|
|
|
|
|
std::string text_before_change = priv->text_model->GetText();
|
|
|
|
|
std::string text_before_change = self->text_model->GetText();
|
|
|
|
|
flutter::TextRange composing_before_change =
|
|
|
|
|
priv->text_model->composing_range();
|
|
|
|
|
self->text_model->composing_range();
|
|
|
|
|
g_autofree gchar* buf = nullptr;
|
|
|
|
|
gint cursor_offset = 0;
|
|
|
|
|
gtk_im_context_get_preedit_string(priv->im_context, &buf, nullptr,
|
|
|
|
|
gtk_im_context_get_preedit_string(self->im_context, &buf, nullptr,
|
|
|
|
|
&cursor_offset);
|
|
|
|
|
if (priv->text_model->composing()) {
|
|
|
|
|
cursor_offset += priv->text_model->composing_range().start();
|
|
|
|
|
if (self->text_model->composing()) {
|
|
|
|
|
cursor_offset += self->text_model->composing_range().start();
|
|
|
|
|
} else {
|
|
|
|
|
cursor_offset += priv->text_model->selection().start();
|
|
|
|
|
cursor_offset += self->text_model->selection().start();
|
|
|
|
|
}
|
|
|
|
|
priv->text_model->UpdateComposingText(buf);
|
|
|
|
|
priv->text_model->SetSelection(flutter::TextRange(cursor_offset));
|
|
|
|
|
self->text_model->UpdateComposingText(buf);
|
|
|
|
|
self->text_model->SetSelection(flutter::TextRange(cursor_offset));
|
|
|
|
|
|
|
|
|
|
if (priv->enable_delta_model) {
|
|
|
|
|
if (self->enable_delta_model) {
|
|
|
|
|
std::string text(buf);
|
|
|
|
|
flutter::TextEditingDelta delta = flutter::TextEditingDelta(
|
|
|
|
|
text_before_change, composing_before_change, text);
|
|
|
|
|
@@ -289,20 +274,18 @@ static void im_preedit_changed_cb(FlTextInputHandler* self) {
|
|
|
|
|
|
|
|
|
|
// Signal handler for GtkIMContext::commit
|
|
|
|
|
static void im_commit_cb(FlTextInputHandler* self, const gchar* text) {
|
|
|
|
|
FlTextInputHandlerPrivate* priv = static_cast<FlTextInputHandlerPrivate*>(
|
|
|
|
|
fl_text_input_handler_get_instance_private(self));
|
|
|
|
|
std::string text_before_change = priv->text_model->GetText();
|
|
|
|
|
std::string text_before_change = self->text_model->GetText();
|
|
|
|
|
flutter::TextRange composing_before_change =
|
|
|
|
|
priv->text_model->composing_range();
|
|
|
|
|
flutter::TextRange selection_before_change = priv->text_model->selection();
|
|
|
|
|
gboolean was_composing = priv->text_model->composing();
|
|
|
|
|
self->text_model->composing_range();
|
|
|
|
|
flutter::TextRange selection_before_change = self->text_model->selection();
|
|
|
|
|
gboolean was_composing = self->text_model->composing();
|
|
|
|
|
|
|
|
|
|
priv->text_model->AddText(text);
|
|
|
|
|
if (priv->text_model->composing()) {
|
|
|
|
|
priv->text_model->CommitComposing();
|
|
|
|
|
self->text_model->AddText(text);
|
|
|
|
|
if (self->text_model->composing()) {
|
|
|
|
|
self->text_model->CommitComposing();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (priv->enable_delta_model) {
|
|
|
|
|
if (self->enable_delta_model) {
|
|
|
|
|
flutter::TextRange replace_range =
|
|
|
|
|
was_composing ? composing_before_change : selection_before_change;
|
|
|
|
|
std::unique_ptr<flutter::TextEditingDelta> delta =
|
|
|
|
|
@@ -316,12 +299,10 @@ static void im_commit_cb(FlTextInputHandler* self, const gchar* text) {
|
|
|
|
|
|
|
|
|
|
// Signal handler for GtkIMContext::preedit-end
|
|
|
|
|
static void im_preedit_end_cb(FlTextInputHandler* self) {
|
|
|
|
|
FlTextInputHandlerPrivate* priv = static_cast<FlTextInputHandlerPrivate*>(
|
|
|
|
|
fl_text_input_handler_get_instance_private(self));
|
|
|
|
|
priv->text_model->EndComposing();
|
|
|
|
|
if (priv->enable_delta_model) {
|
|
|
|
|
self->text_model->EndComposing();
|
|
|
|
|
if (self->enable_delta_model) {
|
|
|
|
|
flutter::TextEditingDelta delta =
|
|
|
|
|
flutter::TextEditingDelta(priv->text_model->GetText());
|
|
|
|
|
flutter::TextEditingDelta(self->text_model->GetText());
|
|
|
|
|
update_editing_state_with_delta(self, &delta);
|
|
|
|
|
} else {
|
|
|
|
|
update_editing_state(self);
|
|
|
|
|
@@ -330,11 +311,9 @@ static void im_preedit_end_cb(FlTextInputHandler* self) {
|
|
|
|
|
|
|
|
|
|
// Signal handler for GtkIMContext::retrieve-surrounding
|
|
|
|
|
static gboolean im_retrieve_surrounding_cb(FlTextInputHandler* self) {
|
|
|
|
|
FlTextInputHandlerPrivate* priv = static_cast<FlTextInputHandlerPrivate*>(
|
|
|
|
|
fl_text_input_handler_get_instance_private(self));
|
|
|
|
|
auto text = priv->text_model->GetText();
|
|
|
|
|
size_t cursor_offset = priv->text_model->GetCursorOffset();
|
|
|
|
|
gtk_im_context_set_surrounding(priv->im_context, text.c_str(), -1,
|
|
|
|
|
auto text = self->text_model->GetText();
|
|
|
|
|
size_t cursor_offset = self->text_model->GetCursorOffset();
|
|
|
|
|
gtk_im_context_set_surrounding(self->im_context, text.c_str(), -1,
|
|
|
|
|
cursor_offset);
|
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
|
|
|
|
@@ -343,15 +322,12 @@ static gboolean im_retrieve_surrounding_cb(FlTextInputHandler* self) {
|
|
|
|
|
static gboolean im_delete_surrounding_cb(FlTextInputHandler* self,
|
|
|
|
|
gint offset,
|
|
|
|
|
gint n_chars) {
|
|
|
|
|
FlTextInputHandlerPrivate* priv = static_cast<FlTextInputHandlerPrivate*>(
|
|
|
|
|
fl_text_input_handler_get_instance_private(self));
|
|
|
|
|
|
|
|
|
|
std::string text_before_change = priv->text_model->GetText();
|
|
|
|
|
if (priv->text_model->DeleteSurrounding(offset, n_chars)) {
|
|
|
|
|
if (priv->enable_delta_model) {
|
|
|
|
|
std::string text_before_change = self->text_model->GetText();
|
|
|
|
|
if (self->text_model->DeleteSurrounding(offset, n_chars)) {
|
|
|
|
|
if (self->enable_delta_model) {
|
|
|
|
|
flutter::TextEditingDelta delta = flutter::TextEditingDelta(
|
|
|
|
|
text_before_change, priv->text_model->composing_range(),
|
|
|
|
|
priv->text_model->GetText());
|
|
|
|
|
text_before_change, self->text_model->composing_range(),
|
|
|
|
|
self->text_model->GetText());
|
|
|
|
|
update_editing_state_with_delta(self, &delta);
|
|
|
|
|
} else {
|
|
|
|
|
update_editing_state(self);
|
|
|
|
|
@@ -367,25 +343,23 @@ static FlMethodResponse* set_client(FlTextInputHandler* self, FlValue* args) {
|
|
|
|
|
return FL_METHOD_RESPONSE(fl_method_error_response_new(
|
|
|
|
|
kBadArgumentsError, "Expected 2-element list", nullptr));
|
|
|
|
|
}
|
|
|
|
|
FlTextInputHandlerPrivate* priv = static_cast<FlTextInputHandlerPrivate*>(
|
|
|
|
|
fl_text_input_handler_get_instance_private(self));
|
|
|
|
|
|
|
|
|
|
priv->client_id = fl_value_get_int(fl_value_get_list_value(args, 0));
|
|
|
|
|
self->client_id = fl_value_get_int(fl_value_get_list_value(args, 0));
|
|
|
|
|
FlValue* config_value = fl_value_get_list_value(args, 1);
|
|
|
|
|
g_free(priv->input_action);
|
|
|
|
|
g_free(self->input_action);
|
|
|
|
|
FlValue* input_action_value =
|
|
|
|
|
fl_value_lookup_string(config_value, kInputActionKey);
|
|
|
|
|
if (fl_value_get_type(input_action_value) == FL_VALUE_TYPE_STRING) {
|
|
|
|
|
priv->input_action = g_strdup(fl_value_get_string(input_action_value));
|
|
|
|
|
self->input_action = g_strdup(fl_value_get_string(input_action_value));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FlValue* enable_delta_model_value =
|
|
|
|
|
fl_value_lookup_string(config_value, kEnableDeltaModel);
|
|
|
|
|
gboolean enable_delta_model = fl_value_get_bool(enable_delta_model_value);
|
|
|
|
|
priv->enable_delta_model = enable_delta_model;
|
|
|
|
|
self->enable_delta_model = enable_delta_model;
|
|
|
|
|
|
|
|
|
|
// Reset the input type, then set only if appropriate.
|
|
|
|
|
priv->input_type = kFlTextInputTypeText;
|
|
|
|
|
self->input_type = kFlTextInputTypeText;
|
|
|
|
|
FlValue* input_type_value =
|
|
|
|
|
fl_value_lookup_string(config_value, kTextInputTypeKey);
|
|
|
|
|
if (fl_value_get_type(input_type_value) == FL_VALUE_TYPE_MAP) {
|
|
|
|
|
@@ -394,9 +368,9 @@ static FlMethodResponse* set_client(FlTextInputHandler* self, FlValue* args) {
|
|
|
|
|
if (fl_value_get_type(input_type_name) == FL_VALUE_TYPE_STRING) {
|
|
|
|
|
const gchar* input_type = fl_value_get_string(input_type_name);
|
|
|
|
|
if (g_strcmp0(input_type, kMultilineInputType) == 0) {
|
|
|
|
|
priv->input_type = kFlTextInputTypeMultiline;
|
|
|
|
|
self->input_type = kFlTextInputTypeMultiline;
|
|
|
|
|
} else if (g_strcmp0(input_type, kNoneInputType) == 0) {
|
|
|
|
|
priv->input_type = kFlTextInputTypeNone;
|
|
|
|
|
self->input_type = kFlTextInputTypeNone;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@@ -406,22 +380,18 @@ static FlMethodResponse* set_client(FlTextInputHandler* self, FlValue* args) {
|
|
|
|
|
|
|
|
|
|
// Hides the input method.
|
|
|
|
|
static FlMethodResponse* hide(FlTextInputHandler* self) {
|
|
|
|
|
FlTextInputHandlerPrivate* priv = static_cast<FlTextInputHandlerPrivate*>(
|
|
|
|
|
fl_text_input_handler_get_instance_private(self));
|
|
|
|
|
gtk_im_context_focus_out(priv->im_context);
|
|
|
|
|
gtk_im_context_focus_out(self->im_context);
|
|
|
|
|
|
|
|
|
|
return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Shows the input method.
|
|
|
|
|
static FlMethodResponse* show(FlTextInputHandler* self) {
|
|
|
|
|
FlTextInputHandlerPrivate* priv = static_cast<FlTextInputHandlerPrivate*>(
|
|
|
|
|
fl_text_input_handler_get_instance_private(self));
|
|
|
|
|
if (priv->input_type == kFlTextInputTypeNone) {
|
|
|
|
|
if (self->input_type == kFlTextInputTypeNone) {
|
|
|
|
|
return hide(self);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
gtk_im_context_focus_in(priv->im_context);
|
|
|
|
|
gtk_im_context_focus_in(self->im_context);
|
|
|
|
|
|
|
|
|
|
return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
|
|
|
|
|
}
|
|
|
|
|
@@ -429,11 +399,9 @@ static FlMethodResponse* show(FlTextInputHandler* self) {
|
|
|
|
|
// Updates the editing state from Flutter.
|
|
|
|
|
static FlMethodResponse* set_editing_state(FlTextInputHandler* self,
|
|
|
|
|
FlValue* args) {
|
|
|
|
|
FlTextInputHandlerPrivate* priv = static_cast<FlTextInputHandlerPrivate*>(
|
|
|
|
|
fl_text_input_handler_get_instance_private(self));
|
|
|
|
|
const gchar* text =
|
|
|
|
|
fl_value_get_string(fl_value_lookup_string(args, kTextKey));
|
|
|
|
|
priv->text_model->SetText(text);
|
|
|
|
|
self->text_model->SetText(text);
|
|
|
|
|
|
|
|
|
|
int64_t selection_base =
|
|
|
|
|
fl_value_get_int(fl_value_lookup_string(args, kSelectionBaseKey));
|
|
|
|
|
@@ -444,8 +412,8 @@ static FlMethodResponse* set_editing_state(FlTextInputHandler* self,
|
|
|
|
|
selection_base = selection_extent = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
priv->text_model->SetText(text);
|
|
|
|
|
priv->text_model->SetSelection(
|
|
|
|
|
self->text_model->SetText(text);
|
|
|
|
|
self->text_model->SetSelection(
|
|
|
|
|
flutter::TextRange(selection_base, selection_extent));
|
|
|
|
|
|
|
|
|
|
int64_t composing_base =
|
|
|
|
|
@@ -453,11 +421,11 @@ static FlMethodResponse* set_editing_state(FlTextInputHandler* self,
|
|
|
|
|
int64_t composing_extent =
|
|
|
|
|
fl_value_get_int(fl_value_lookup_string(args, kComposingExtentKey));
|
|
|
|
|
if (composing_base == -1 && composing_extent == -1) {
|
|
|
|
|
priv->text_model->EndComposing();
|
|
|
|
|
self->text_model->EndComposing();
|
|
|
|
|
} else {
|
|
|
|
|
size_t composing_start = std::min(composing_base, composing_extent);
|
|
|
|
|
size_t cursor_offset = selection_base - composing_start;
|
|
|
|
|
priv->text_model->SetComposingRange(
|
|
|
|
|
self->text_model->SetComposingRange(
|
|
|
|
|
flutter::TextRange(composing_base, composing_extent), cursor_offset);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -466,9 +434,7 @@ static FlMethodResponse* set_editing_state(FlTextInputHandler* self,
|
|
|
|
|
|
|
|
|
|
// Called when the input method client is complete.
|
|
|
|
|
static FlMethodResponse* clear_client(FlTextInputHandler* self) {
|
|
|
|
|
FlTextInputHandlerPrivate* priv = static_cast<FlTextInputHandlerPrivate*>(
|
|
|
|
|
fl_text_input_handler_get_instance_private(self));
|
|
|
|
|
priv->client_id = kClientIdUnset;
|
|
|
|
|
self->client_id = kClientIdUnset;
|
|
|
|
|
|
|
|
|
|
return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
|
|
|
|
|
}
|
|
|
|
|
@@ -482,28 +448,25 @@ static FlMethodResponse* clear_client(FlTextInputHandler* self) {
|
|
|
|
|
// of these updates. It transforms the composing rect to GDK window coordinates
|
|
|
|
|
// and notifies GTK of the updated cursor position.
|
|
|
|
|
static void update_im_cursor_position(FlTextInputHandler* self) {
|
|
|
|
|
FlTextInputHandlerPrivate* priv = static_cast<FlTextInputHandlerPrivate*>(
|
|
|
|
|
fl_text_input_handler_get_instance_private(self));
|
|
|
|
|
|
|
|
|
|
g_autoptr(FlTextInputViewDelegate) view_delegate =
|
|
|
|
|
FL_TEXT_INPUT_VIEW_DELEGATE(g_weak_ref_get(&priv->view_delegate));
|
|
|
|
|
FL_TEXT_INPUT_VIEW_DELEGATE(g_weak_ref_get(&self->view_delegate));
|
|
|
|
|
if (view_delegate == nullptr) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Skip update if not composing to avoid setting to position 0.
|
|
|
|
|
if (!priv->text_model->composing()) {
|
|
|
|
|
if (!self->text_model->composing()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Transform the x, y positions of the cursor from local coordinates to
|
|
|
|
|
// Flutter view coordinates.
|
|
|
|
|
gint x = priv->composing_rect.x * priv->editabletext_transform[0][0] +
|
|
|
|
|
priv->composing_rect.y * priv->editabletext_transform[1][0] +
|
|
|
|
|
priv->editabletext_transform[3][0] + priv->composing_rect.width;
|
|
|
|
|
gint y = priv->composing_rect.x * priv->editabletext_transform[0][1] +
|
|
|
|
|
priv->composing_rect.y * priv->editabletext_transform[1][1] +
|
|
|
|
|
priv->editabletext_transform[3][1] + priv->composing_rect.height;
|
|
|
|
|
gint x = self->composing_rect.x * self->editabletext_transform[0][0] +
|
|
|
|
|
self->composing_rect.y * self->editabletext_transform[1][0] +
|
|
|
|
|
self->editabletext_transform[3][0] + self->composing_rect.width;
|
|
|
|
|
gint y = self->composing_rect.x * self->editabletext_transform[0][1] +
|
|
|
|
|
self->composing_rect.y * self->editabletext_transform[1][1] +
|
|
|
|
|
self->editabletext_transform[3][1] + self->composing_rect.height;
|
|
|
|
|
|
|
|
|
|
// Transform from Flutter view coordinates to GTK window coordinates.
|
|
|
|
|
GdkRectangle preedit_rect = {};
|
|
|
|
|
@@ -512,7 +475,7 @@ static void update_im_cursor_position(FlTextInputHandler* self) {
|
|
|
|
|
|
|
|
|
|
// Set the cursor location in window coordinates so that GTK can position any
|
|
|
|
|
// system input method windows.
|
|
|
|
|
gtk_im_context_set_cursor_location(priv->im_context, &preedit_rect);
|
|
|
|
|
gtk_im_context_set_cursor_location(self->im_context, &preedit_rect);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Handles updates to the EditableText size and position from the framework.
|
|
|
|
|
@@ -530,9 +493,7 @@ static FlMethodResponse* set_editable_size_and_transform(
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < transform_len; ++i) {
|
|
|
|
|
double val = fl_value_get_float(fl_value_get_list_value(transform, i));
|
|
|
|
|
FlTextInputHandlerPrivate* priv = static_cast<FlTextInputHandlerPrivate*>(
|
|
|
|
|
fl_text_input_handler_get_instance_private(self));
|
|
|
|
|
priv->editabletext_transform[i / 4][i % 4] = val;
|
|
|
|
|
self->editabletext_transform[i / 4][i % 4] = val;
|
|
|
|
|
}
|
|
|
|
|
update_im_cursor_position(self);
|
|
|
|
|
|
|
|
|
|
@@ -547,15 +508,13 @@ static FlMethodResponse* set_editable_size_and_transform(
|
|
|
|
|
// composing region, the cursor rect is sent.
|
|
|
|
|
static FlMethodResponse* set_marked_text_rect(FlTextInputHandler* self,
|
|
|
|
|
FlValue* args) {
|
|
|
|
|
FlTextInputHandlerPrivate* priv = static_cast<FlTextInputHandlerPrivate*>(
|
|
|
|
|
fl_text_input_handler_get_instance_private(self));
|
|
|
|
|
priv->composing_rect.x =
|
|
|
|
|
self->composing_rect.x =
|
|
|
|
|
fl_value_get_float(fl_value_lookup_string(args, "x"));
|
|
|
|
|
priv->composing_rect.y =
|
|
|
|
|
self->composing_rect.y =
|
|
|
|
|
fl_value_get_float(fl_value_lookup_string(args, "y"));
|
|
|
|
|
priv->composing_rect.width =
|
|
|
|
|
self->composing_rect.width =
|
|
|
|
|
fl_value_get_float(fl_value_lookup_string(args, "width"));
|
|
|
|
|
priv->composing_rect.height =
|
|
|
|
|
self->composing_rect.height =
|
|
|
|
|
fl_value_get_float(fl_value_lookup_string(args, "height"));
|
|
|
|
|
update_im_cursor_position(self);
|
|
|
|
|
|
|
|
|
|
@@ -599,148 +558,55 @@ static void method_call_cb(FlMethodChannel* channel,
|
|
|
|
|
// Disposes of an FlTextInputHandler.
|
|
|
|
|
static void fl_text_input_handler_dispose(GObject* object) {
|
|
|
|
|
FlTextInputHandler* self = FL_TEXT_INPUT_HANDLER(object);
|
|
|
|
|
FlTextInputHandlerPrivate* priv = static_cast<FlTextInputHandlerPrivate*>(
|
|
|
|
|
fl_text_input_handler_get_instance_private(self));
|
|
|
|
|
|
|
|
|
|
g_clear_object(&priv->channel);
|
|
|
|
|
g_clear_pointer(&priv->input_action, g_free);
|
|
|
|
|
g_clear_object(&priv->im_context);
|
|
|
|
|
if (priv->text_model != nullptr) {
|
|
|
|
|
delete priv->text_model;
|
|
|
|
|
priv->text_model = nullptr;
|
|
|
|
|
g_clear_object(&self->channel);
|
|
|
|
|
g_clear_pointer(&self->input_action, g_free);
|
|
|
|
|
g_clear_object(&self->im_context);
|
|
|
|
|
if (self->text_model != nullptr) {
|
|
|
|
|
delete self->text_model;
|
|
|
|
|
self->text_model = nullptr;
|
|
|
|
|
}
|
|
|
|
|
g_weak_ref_clear(&priv->view_delegate);
|
|
|
|
|
g_weak_ref_clear(&self->view_delegate);
|
|
|
|
|
|
|
|
|
|
G_OBJECT_CLASS(fl_text_input_handler_parent_class)->dispose(object);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Implements FlTextInputHandler::filter_keypress.
|
|
|
|
|
static gboolean fl_text_input_handler_filter_keypress_default(
|
|
|
|
|
FlTextInputHandler* self,
|
|
|
|
|
FlKeyEvent* event) {
|
|
|
|
|
g_return_val_if_fail(FL_IS_TEXT_INPUT_HANDLER(self), false);
|
|
|
|
|
|
|
|
|
|
FlTextInputHandlerPrivate* priv = static_cast<FlTextInputHandlerPrivate*>(
|
|
|
|
|
fl_text_input_handler_get_instance_private(self));
|
|
|
|
|
|
|
|
|
|
if (priv->client_id == kClientIdUnset) {
|
|
|
|
|
return FALSE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (gtk_im_context_filter_keypress(
|
|
|
|
|
priv->im_context,
|
|
|
|
|
reinterpret_cast<GdkEventKey*>(fl_key_event_get_origin(event)))) {
|
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string text_before_change = priv->text_model->GetText();
|
|
|
|
|
flutter::TextRange selection_before_change = priv->text_model->selection();
|
|
|
|
|
std::string text = priv->text_model->GetText();
|
|
|
|
|
|
|
|
|
|
// Handle the enter/return key.
|
|
|
|
|
gboolean do_action = FALSE;
|
|
|
|
|
// Handle navigation keys.
|
|
|
|
|
gboolean changed = FALSE;
|
|
|
|
|
if (fl_key_event_get_is_press(event)) {
|
|
|
|
|
switch (fl_key_event_get_keyval(event)) {
|
|
|
|
|
case GDK_KEY_End:
|
|
|
|
|
case GDK_KEY_KP_End:
|
|
|
|
|
if (fl_key_event_get_state(event) & GDK_SHIFT_MASK) {
|
|
|
|
|
changed = priv->text_model->SelectToEnd();
|
|
|
|
|
} else {
|
|
|
|
|
changed = priv->text_model->MoveCursorToEnd();
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case GDK_KEY_Return:
|
|
|
|
|
case GDK_KEY_KP_Enter:
|
|
|
|
|
case GDK_KEY_ISO_Enter:
|
|
|
|
|
if (priv->input_type == kFlTextInputTypeMultiline &&
|
|
|
|
|
strcmp(priv->input_action, kNewlineInputAction) == 0) {
|
|
|
|
|
priv->text_model->AddCodePoint('\n');
|
|
|
|
|
text = "\n";
|
|
|
|
|
changed = TRUE;
|
|
|
|
|
}
|
|
|
|
|
do_action = TRUE;
|
|
|
|
|
break;
|
|
|
|
|
case GDK_KEY_Home:
|
|
|
|
|
case GDK_KEY_KP_Home:
|
|
|
|
|
if (fl_key_event_get_state(event) & GDK_SHIFT_MASK) {
|
|
|
|
|
changed = priv->text_model->SelectToBeginning();
|
|
|
|
|
} else {
|
|
|
|
|
changed = priv->text_model->MoveCursorToBeginning();
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case GDK_KEY_BackSpace:
|
|
|
|
|
case GDK_KEY_Delete:
|
|
|
|
|
case GDK_KEY_KP_Delete:
|
|
|
|
|
case GDK_KEY_Left:
|
|
|
|
|
case GDK_KEY_KP_Left:
|
|
|
|
|
case GDK_KEY_Right:
|
|
|
|
|
case GDK_KEY_KP_Right:
|
|
|
|
|
// Already handled inside the framework in RenderEditable.
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (changed) {
|
|
|
|
|
if (priv->enable_delta_model) {
|
|
|
|
|
flutter::TextEditingDelta delta = flutter::TextEditingDelta(
|
|
|
|
|
text_before_change, selection_before_change, text);
|
|
|
|
|
update_editing_state_with_delta(self, &delta);
|
|
|
|
|
} else {
|
|
|
|
|
update_editing_state(self);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (do_action) {
|
|
|
|
|
perform_action(self);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return changed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Initializes the FlTextInputHandler class.
|
|
|
|
|
static void fl_text_input_handler_class_init(FlTextInputHandlerClass* klass) {
|
|
|
|
|
G_OBJECT_CLASS(klass)->dispose = fl_text_input_handler_dispose;
|
|
|
|
|
FL_TEXT_INPUT_HANDLER_CLASS(klass)->filter_keypress =
|
|
|
|
|
fl_text_input_handler_filter_keypress_default;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Initializes an instance of the FlTextInputHandler class.
|
|
|
|
|
static void fl_text_input_handler_init(FlTextInputHandler* self) {
|
|
|
|
|
FlTextInputHandlerPrivate* priv = static_cast<FlTextInputHandlerPrivate*>(
|
|
|
|
|
fl_text_input_handler_get_instance_private(self));
|
|
|
|
|
|
|
|
|
|
priv->client_id = kClientIdUnset;
|
|
|
|
|
priv->input_type = kFlTextInputTypeText;
|
|
|
|
|
priv->text_model = new flutter::TextInputModel();
|
|
|
|
|
self->client_id = kClientIdUnset;
|
|
|
|
|
self->input_type = kFlTextInputTypeText;
|
|
|
|
|
self->text_model = new flutter::TextInputModel();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void init_im_context(FlTextInputHandler* self,
|
|
|
|
|
GtkIMContext* im_context) {
|
|
|
|
|
FlTextInputHandlerPrivate* priv = static_cast<FlTextInputHandlerPrivate*>(
|
|
|
|
|
fl_text_input_handler_get_instance_private(self));
|
|
|
|
|
priv->im_context = GTK_IM_CONTEXT(g_object_ref(im_context));
|
|
|
|
|
self->im_context = GTK_IM_CONTEXT(g_object_ref(im_context));
|
|
|
|
|
|
|
|
|
|
// On Wayland, this call sets up the input method so it can be enabled
|
|
|
|
|
// immediately when required. Without it, on-screen keyboard's don't come up
|
|
|
|
|
// the first time a text field is focused.
|
|
|
|
|
gtk_im_context_focus_out(priv->im_context);
|
|
|
|
|
gtk_im_context_focus_out(self->im_context);
|
|
|
|
|
|
|
|
|
|
g_signal_connect_object(priv->im_context, "preedit-start",
|
|
|
|
|
g_signal_connect_object(self->im_context, "preedit-start",
|
|
|
|
|
G_CALLBACK(im_preedit_start_cb), self,
|
|
|
|
|
G_CONNECT_SWAPPED);
|
|
|
|
|
g_signal_connect_object(priv->im_context, "preedit-end",
|
|
|
|
|
g_signal_connect_object(self->im_context, "preedit-end",
|
|
|
|
|
G_CALLBACK(im_preedit_end_cb), self,
|
|
|
|
|
G_CONNECT_SWAPPED);
|
|
|
|
|
g_signal_connect_object(priv->im_context, "preedit-changed",
|
|
|
|
|
g_signal_connect_object(self->im_context, "preedit-changed",
|
|
|
|
|
G_CALLBACK(im_preedit_changed_cb), self,
|
|
|
|
|
G_CONNECT_SWAPPED);
|
|
|
|
|
g_signal_connect_object(priv->im_context, "commit", G_CALLBACK(im_commit_cb),
|
|
|
|
|
g_signal_connect_object(self->im_context, "commit", G_CALLBACK(im_commit_cb),
|
|
|
|
|
self, G_CONNECT_SWAPPED);
|
|
|
|
|
g_signal_connect_object(priv->im_context, "retrieve-surrounding",
|
|
|
|
|
g_signal_connect_object(self->im_context, "retrieve-surrounding",
|
|
|
|
|
G_CALLBACK(im_retrieve_surrounding_cb), self,
|
|
|
|
|
G_CONNECT_SWAPPED);
|
|
|
|
|
g_signal_connect_object(priv->im_context, "delete-surrounding",
|
|
|
|
|
g_signal_connect_object(self->im_context, "delete-surrounding",
|
|
|
|
|
G_CALLBACK(im_delete_surrounding_cb), self,
|
|
|
|
|
G_CONNECT_SWAPPED);
|
|
|
|
|
}
|
|
|
|
|
@@ -757,27 +623,93 @@ FlTextInputHandler* fl_text_input_handler_new(
|
|
|
|
|
g_object_new(fl_text_input_handler_get_type(), nullptr));
|
|
|
|
|
|
|
|
|
|
g_autoptr(FlJsonMethodCodec) codec = fl_json_method_codec_new();
|
|
|
|
|
FlTextInputHandlerPrivate* priv = static_cast<FlTextInputHandlerPrivate*>(
|
|
|
|
|
fl_text_input_handler_get_instance_private(self));
|
|
|
|
|
priv->channel =
|
|
|
|
|
self->channel =
|
|
|
|
|
fl_method_channel_new(messenger, kChannelName, FL_METHOD_CODEC(codec));
|
|
|
|
|
fl_method_channel_set_method_call_handler(priv->channel, method_call_cb, self,
|
|
|
|
|
fl_method_channel_set_method_call_handler(self->channel, method_call_cb, self,
|
|
|
|
|
nullptr);
|
|
|
|
|
|
|
|
|
|
init_im_context(self, im_context);
|
|
|
|
|
|
|
|
|
|
g_weak_ref_init(&priv->view_delegate, view_delegate);
|
|
|
|
|
g_weak_ref_init(&self->view_delegate, view_delegate);
|
|
|
|
|
|
|
|
|
|
return self;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Filters the a keypress given to the handler through the handler's
|
|
|
|
|
// filter_keypress callback.
|
|
|
|
|
gboolean fl_text_input_handler_filter_keypress(FlTextInputHandler* self,
|
|
|
|
|
FlKeyEvent* event) {
|
|
|
|
|
g_return_val_if_fail(FL_IS_TEXT_INPUT_HANDLER(self), FALSE);
|
|
|
|
|
if (FL_TEXT_INPUT_HANDLER_GET_CLASS(self)->filter_keypress) {
|
|
|
|
|
return FL_TEXT_INPUT_HANDLER_GET_CLASS(self)->filter_keypress(self, event);
|
|
|
|
|
|
|
|
|
|
if (self->client_id == kClientIdUnset) {
|
|
|
|
|
return FALSE;
|
|
|
|
|
}
|
|
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
|
|
if (gtk_im_context_filter_keypress(
|
|
|
|
|
self->im_context,
|
|
|
|
|
reinterpret_cast<GdkEventKey*>(fl_key_event_get_origin(event)))) {
|
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string text_before_change = self->text_model->GetText();
|
|
|
|
|
flutter::TextRange selection_before_change = self->text_model->selection();
|
|
|
|
|
std::string text = self->text_model->GetText();
|
|
|
|
|
|
|
|
|
|
// Handle the enter/return key.
|
|
|
|
|
gboolean do_action = FALSE;
|
|
|
|
|
// Handle navigation keys.
|
|
|
|
|
gboolean changed = FALSE;
|
|
|
|
|
if (fl_key_event_get_is_press(event)) {
|
|
|
|
|
switch (fl_key_event_get_keyval(event)) {
|
|
|
|
|
case GDK_KEY_End:
|
|
|
|
|
case GDK_KEY_KP_End:
|
|
|
|
|
if (fl_key_event_get_state(event) & GDK_SHIFT_MASK) {
|
|
|
|
|
changed = self->text_model->SelectToEnd();
|
|
|
|
|
} else {
|
|
|
|
|
changed = self->text_model->MoveCursorToEnd();
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case GDK_KEY_Return:
|
|
|
|
|
case GDK_KEY_KP_Enter:
|
|
|
|
|
case GDK_KEY_ISO_Enter:
|
|
|
|
|
if (self->input_type == kFlTextInputTypeMultiline &&
|
|
|
|
|
strcmp(self->input_action, kNewlineInputAction) == 0) {
|
|
|
|
|
self->text_model->AddCodePoint('\n');
|
|
|
|
|
text = "\n";
|
|
|
|
|
changed = TRUE;
|
|
|
|
|
}
|
|
|
|
|
do_action = TRUE;
|
|
|
|
|
break;
|
|
|
|
|
case GDK_KEY_Home:
|
|
|
|
|
case GDK_KEY_KP_Home:
|
|
|
|
|
if (fl_key_event_get_state(event) & GDK_SHIFT_MASK) {
|
|
|
|
|
changed = self->text_model->SelectToBeginning();
|
|
|
|
|
} else {
|
|
|
|
|
changed = self->text_model->MoveCursorToBeginning();
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case GDK_KEY_BackSpace:
|
|
|
|
|
case GDK_KEY_Delete:
|
|
|
|
|
case GDK_KEY_KP_Delete:
|
|
|
|
|
case GDK_KEY_Left:
|
|
|
|
|
case GDK_KEY_KP_Left:
|
|
|
|
|
case GDK_KEY_Right:
|
|
|
|
|
case GDK_KEY_KP_Right:
|
|
|
|
|
// Already handled inside the framework in RenderEditable.
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (changed) {
|
|
|
|
|
if (self->enable_delta_model) {
|
|
|
|
|
flutter::TextEditingDelta delta = flutter::TextEditingDelta(
|
|
|
|
|
text_before_change, selection_before_change, text);
|
|
|
|
|
update_editing_state_with_delta(self, &delta);
|
|
|
|
|
} else {
|
|
|
|
|
update_editing_state(self);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (do_action) {
|
|
|
|
|
perform_action(self);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return changed;
|
|
|
|
|
}
|
|
|
|
|
|