blob: 9c483e61fe360952c51212b4a952e9d9a92c7ff8 [file] [log] [blame]
// 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/linux/fl_accessible_text_field.h"
#include "flutter/shell/platform/linux/public/flutter_linux/fl_standard_message_codec.h"
#include "flutter/shell/platform/linux/public/flutter_linux/fl_value.h"
struct _FlAccessibleTextField {
FlAccessibleNode parent_instance;
gint selection_base;
gint selection_extent;
GtkEntryBuffer* buffer;
};
static void fl_accessible_text_iface_init(AtkTextIface* iface);
static void fl_accessible_editable_text_iface_init(AtkEditableTextIface* iface);
G_DEFINE_TYPE_WITH_CODE(
FlAccessibleTextField,
fl_accessible_text_field,
FL_TYPE_ACCESSIBLE_NODE,
G_IMPLEMENT_INTERFACE(ATK_TYPE_TEXT, fl_accessible_text_iface_init)
G_IMPLEMENT_INTERFACE(ATK_TYPE_EDITABLE_TEXT,
fl_accessible_editable_text_iface_init))
static gchar* get_substring(FlAccessibleTextField* self,
glong start,
glong end) {
const gchar* value = gtk_entry_buffer_get_text(self->buffer);
if (end == -1) {
// g_utf8_substring() accepts -1 since 2.72
end = g_utf8_strlen(value, -1);
}
return g_utf8_substring(value, start, end);
}
static void perform_set_text_action(FlAccessibleTextField* self,
const char* text) {
g_autoptr(FlValue) value = fl_value_new_string(text);
g_autoptr(FlStandardMessageCodec) codec = fl_standard_message_codec_new();
g_autoptr(GBytes) message =
fl_message_codec_encode_message(FL_MESSAGE_CODEC(codec), value, nullptr);
fl_accessible_node_perform_action(FL_ACCESSIBLE_NODE(self),
kFlutterSemanticsActionSetText, message);
}
static void perform_set_selection_action(FlAccessibleTextField* self,
gint base,
gint extent) {
g_autoptr(FlValue) value = fl_value_new_map();
fl_value_set_string_take(value, "base", fl_value_new_int(base));
fl_value_set_string_take(value, "extent", fl_value_new_int(extent));
g_autoptr(FlStandardMessageCodec) codec = fl_standard_message_codec_new();
g_autoptr(GBytes) message =
fl_message_codec_encode_message(FL_MESSAGE_CODEC(codec), value, nullptr);
fl_accessible_node_perform_action(
FL_ACCESSIBLE_NODE(self), kFlutterSemanticsActionSetSelection, message);
}
// Implements GObject::dispose.
static void fl_accessible_text_field_dispose(GObject* object) {
FlAccessibleTextField* self = FL_ACCESSIBLE_TEXT_FIELD(object);
g_clear_object(&self->buffer);
G_OBJECT_CLASS(fl_accessible_text_field_parent_class)->dispose(object);
}
// Implements FlAccessibleNode::set_value.
static void fl_accessible_text_field_set_value(FlAccessibleNode* node,
const gchar* value) {
g_return_if_fail(FL_IS_ACCESSIBLE_TEXT_FIELD(node));
FlAccessibleTextField* self = FL_ACCESSIBLE_TEXT_FIELD(node);
if (g_strcmp0(gtk_entry_buffer_get_text(self->buffer), value) == 0) {
return;
}
gtk_entry_buffer_set_text(self->buffer, value, -1);
}
// Implements FlAccessibleNode::set_text_selection.
static void fl_accessible_text_field_set_text_selection(FlAccessibleNode* node,
gint base,
gint extent) {
g_return_if_fail(FL_IS_ACCESSIBLE_TEXT_FIELD(node));
FlAccessibleTextField* self = FL_ACCESSIBLE_TEXT_FIELD(node);
gboolean caret_moved = extent != self->selection_extent;
gboolean has_selection = base != extent;
gboolean had_selection = self->selection_base != self->selection_extent;
gboolean selection_changed = (has_selection || had_selection) &&
(caret_moved || base != self->selection_base);
self->selection_base = base;
self->selection_extent = extent;
if (selection_changed) {
g_signal_emit_by_name(self, "text-selection-changed", nullptr);
}
if (caret_moved) {
g_signal_emit_by_name(self, "text-caret-moved", extent, nullptr);
}
}
// Overrides FlAccessibleNode::perform_action.
void fl_accessible_text_field_perform_action(FlAccessibleNode* self,
FlutterSemanticsAction action,
GBytes* data) {
FlAccessibleNodeClass* parent_class =
FL_ACCESSIBLE_NODE_CLASS(fl_accessible_text_field_parent_class);
switch (action) {
case kFlutterSemanticsActionMoveCursorForwardByCharacter:
case kFlutterSemanticsActionMoveCursorBackwardByCharacter:
case kFlutterSemanticsActionMoveCursorForwardByWord:
case kFlutterSemanticsActionMoveCursorBackwardByWord: {
// These actions require a boolean argument that indicates whether the
// selection should be extended or collapsed when moving the cursor.
g_autoptr(FlValue) extend_selection = fl_value_new_bool(false);
g_autoptr(FlStandardMessageCodec) codec = fl_standard_message_codec_new();
g_autoptr(GBytes) message = fl_message_codec_encode_message(
FL_MESSAGE_CODEC(codec), extend_selection, nullptr);
parent_class->perform_action(self, action, message);
break;
}
default:
parent_class->perform_action(self, action, data);
break;
}
}
// Implements AtkText::get_character_count.
static gint fl_accessible_text_field_get_character_count(AtkText* text) {
g_return_val_if_fail(FL_IS_ACCESSIBLE_TEXT_FIELD(text), 0);
FlAccessibleTextField* self = FL_ACCESSIBLE_TEXT_FIELD(text);
return gtk_entry_buffer_get_length(self->buffer);
}
// Implements AtkText::get_text.
static gchar* fl_accessible_text_field_get_text(AtkText* text,
gint start_offset,
gint end_offset) {
g_return_val_if_fail(FL_IS_ACCESSIBLE_TEXT_FIELD(text), nullptr);
FlAccessibleTextField* self = FL_ACCESSIBLE_TEXT_FIELD(text);
return get_substring(self, start_offset, end_offset);
}
// Implements AtkText::get_caret_offset.
static gint fl_accessible_text_field_get_caret_offset(AtkText* text) {
g_return_val_if_fail(FL_IS_ACCESSIBLE_TEXT_FIELD(text), -1);
FlAccessibleTextField* self = FL_ACCESSIBLE_TEXT_FIELD(text);
return self->selection_extent;
}
// Implements AtkText::set_caret_offset.
static gboolean fl_accessible_text_field_set_caret_offset(AtkText* text,
gint offset) {
g_return_val_if_fail(FL_IS_ACCESSIBLE_TEXT_FIELD(text), false);
FlAccessibleTextField* self = FL_ACCESSIBLE_TEXT_FIELD(text);
perform_set_selection_action(self, offset, offset);
return true;
}
// Implements AtkText::get_n_selections.
static gint fl_accessible_text_field_get_n_selections(AtkText* text) {
g_return_val_if_fail(FL_IS_ACCESSIBLE_TEXT_FIELD(text), 0);
FlAccessibleTextField* self = FL_ACCESSIBLE_TEXT_FIELD(text);
if (self->selection_base == self->selection_extent) {
return 0;
}
return 1;
}
// Implements AtkText::get_selection.
static gchar* fl_accessible_text_field_get_selection(AtkText* text,
gint selection_num,
gint* start_offset,
gint* end_offset) {
g_return_val_if_fail(FL_IS_ACCESSIBLE_TEXT_FIELD(text), nullptr);
FlAccessibleTextField* self = FL_ACCESSIBLE_TEXT_FIELD(text);
if (selection_num != 0 || self->selection_base == self->selection_extent) {
return nullptr;
}
gint start = MIN(self->selection_base, self->selection_extent);
gint end = MAX(self->selection_base, self->selection_extent);
if (start_offset != nullptr) {
*start_offset = start;
}
if (end_offset != nullptr) {
*end_offset = end;
}
return get_substring(self, start, end);
}
// Implements AtkText::add_selection.
static gboolean fl_accessible_text_field_add_selection(AtkText* text,
gint start_offset,
gint end_offset) {
g_return_val_if_fail(FL_IS_ACCESSIBLE_TEXT_FIELD(text), false);
FlAccessibleTextField* self = FL_ACCESSIBLE_TEXT_FIELD(text);
if (self->selection_base != self->selection_extent) {
return false;
}
perform_set_selection_action(self, start_offset, end_offset);
return true;
}
// Implements AtkText::remove_selection.
static gboolean fl_accessible_text_field_remove_selection(AtkText* text,
gint selection_num) {
g_return_val_if_fail(FL_IS_ACCESSIBLE_TEXT_FIELD(text), false);
FlAccessibleTextField* self = FL_ACCESSIBLE_TEXT_FIELD(text);
if (selection_num != 0 || self->selection_base == self->selection_extent) {
return false;
}
perform_set_selection_action(self, self->selection_extent,
self->selection_extent);
return true;
}
// Implements AtkText::set_selection.
static gboolean fl_accessible_text_field_set_selection(AtkText* text,
gint selection_num,
gint start_offset,
gint end_offset) {
g_return_val_if_fail(FL_IS_ACCESSIBLE_TEXT_FIELD(text), false);
FlAccessibleTextField* self = FL_ACCESSIBLE_TEXT_FIELD(text);
if (selection_num != 0) {
return false;
}
perform_set_selection_action(self, start_offset, end_offset);
return true;
}
// Implements AtkEditableText::set_text_contents.
static void fl_accessible_text_field_set_text_contents(
AtkEditableText* editable_text,
const gchar* string) {
g_return_if_fail(FL_IS_ACCESSIBLE_TEXT_FIELD(editable_text));
FlAccessibleTextField* self = FL_ACCESSIBLE_TEXT_FIELD(editable_text);
perform_set_text_action(self, string);
}
// Implements AtkEditableText::insert_text.
static void fl_accessible_text_field_insert_text(AtkEditableText* editable_text,
const gchar* string,
gint length,
gint* position) {
g_return_if_fail(FL_IS_ACCESSIBLE_TEXT_FIELD(editable_text));
FlAccessibleTextField* self = FL_ACCESSIBLE_TEXT_FIELD(editable_text);
*position +=
gtk_entry_buffer_insert_text(self->buffer, *position, string, length);
perform_set_text_action(self, gtk_entry_buffer_get_text(self->buffer));
perform_set_selection_action(self, *position, *position);
}
// Implements AtkEditableText::delete_text.
static void fl_accessible_node_delete_text(AtkEditableText* editable_text,
gint start_pos,
gint end_pos) {
g_return_if_fail(FL_IS_ACCESSIBLE_TEXT_FIELD(editable_text));
FlAccessibleTextField* self = FL_ACCESSIBLE_TEXT_FIELD(editable_text);
gtk_entry_buffer_delete_text(self->buffer, start_pos, end_pos - start_pos);
perform_set_text_action(self, gtk_entry_buffer_get_text(self->buffer));
perform_set_selection_action(self, start_pos, start_pos);
}
// Implement AtkEditableText::copy_text.
static void fl_accessible_text_field_copy_text(AtkEditableText* editable_text,
gint start_pos,
gint end_pos) {
g_return_if_fail(FL_IS_ACCESSIBLE_TEXT_FIELD(editable_text));
FlAccessibleTextField* self = FL_ACCESSIBLE_TEXT_FIELD(editable_text);
perform_set_selection_action(self, start_pos, end_pos);
fl_accessible_node_perform_action(FL_ACCESSIBLE_NODE(editable_text),
kFlutterSemanticsActionCopy, nullptr);
}
// Implements AtkEditableText::cut_text.
static void fl_accessible_text_field_cut_text(AtkEditableText* editable_text,
gint start_pos,
gint end_pos) {
g_return_if_fail(FL_IS_ACCESSIBLE_TEXT_FIELD(editable_text));
FlAccessibleTextField* self = FL_ACCESSIBLE_TEXT_FIELD(editable_text);
perform_set_selection_action(self, start_pos, end_pos);
fl_accessible_node_perform_action(FL_ACCESSIBLE_NODE(editable_text),
kFlutterSemanticsActionCut, nullptr);
}
// Implements AtkEditableText::paste_text.
static void fl_accessible_text_field_paste_text(AtkEditableText* editable_text,
gint position) {
g_return_if_fail(FL_IS_ACCESSIBLE_TEXT_FIELD(editable_text));
FlAccessibleTextField* self = FL_ACCESSIBLE_TEXT_FIELD(editable_text);
perform_set_selection_action(self, position, position);
fl_accessible_node_perform_action(FL_ACCESSIBLE_NODE(editable_text),
kFlutterSemanticsActionPaste, nullptr);
}
static void fl_accessible_text_field_class_init(
FlAccessibleTextFieldClass* klass) {
G_OBJECT_CLASS(klass)->dispose = fl_accessible_text_field_dispose;
FL_ACCESSIBLE_NODE_CLASS(klass)->set_value =
fl_accessible_text_field_set_value;
FL_ACCESSIBLE_NODE_CLASS(klass)->set_text_selection =
fl_accessible_text_field_set_text_selection;
FL_ACCESSIBLE_NODE_CLASS(klass)->perform_action =
fl_accessible_text_field_perform_action;
}
static void fl_accessible_text_iface_init(AtkTextIface* iface) {
iface->get_character_count = fl_accessible_text_field_get_character_count;
iface->get_text = fl_accessible_text_field_get_text;
// TODO(jpnurmi): get_text_at/before/after_offset
iface->get_caret_offset = fl_accessible_text_field_get_caret_offset;
iface->set_caret_offset = fl_accessible_text_field_set_caret_offset;
iface->get_n_selections = fl_accessible_text_field_get_n_selections;
iface->get_selection = fl_accessible_text_field_get_selection;
iface->add_selection = fl_accessible_text_field_add_selection;
iface->remove_selection = fl_accessible_text_field_remove_selection;
iface->set_selection = fl_accessible_text_field_set_selection;
}
static void fl_accessible_editable_text_iface_init(
AtkEditableTextIface* iface) {
iface->set_text_contents = fl_accessible_text_field_set_text_contents;
iface->insert_text = fl_accessible_text_field_insert_text;
iface->delete_text = fl_accessible_node_delete_text;
iface->copy_text = fl_accessible_text_field_copy_text;
iface->cut_text = fl_accessible_text_field_cut_text;
iface->paste_text = fl_accessible_text_field_paste_text;
}
static void fl_accessible_text_field_init(FlAccessibleTextField* self) {
self->selection_base = -1;
self->selection_extent = -1;
self->buffer = gtk_entry_buffer_new("", 0);
g_signal_connect_object(
self->buffer, "inserted-text",
G_CALLBACK(+[](FlAccessibleTextField* self, guint position, gchar* chars,
guint n_chars) {
g_signal_emit_by_name(self, "text-insert", position, n_chars, chars,
nullptr);
}),
self, G_CONNECT_SWAPPED);
g_signal_connect_object(self->buffer, "deleted-text",
G_CALLBACK(+[](FlAccessibleTextField* self,
guint position, guint n_chars) {
g_autofree gchar* chars = atk_text_get_text(
ATK_TEXT(self), position, position + n_chars);
g_signal_emit_by_name(self, "text-remove", position,
n_chars, chars, nullptr);
}),
self, G_CONNECT_SWAPPED);
}
FlAccessibleNode* fl_accessible_text_field_new(FlEngine* engine, int32_t id) {
return FL_ACCESSIBLE_NODE(g_object_new(fl_accessible_text_field_get_type(),
"engine", engine, "id", id, nullptr));
}