blob: 77ba12aec12829c03aab0ca5d2efa29044dda2f0 [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_view_accessible.h"
#include "flutter/shell/platform/linux/fl_accessible_node.h"
#include "flutter/shell/platform/linux/fl_accessible_text_field.h"
#include "flutter/shell/platform/linux/public/flutter_linux/fl_value.h"
#include "flutter/shell/platform/linux/public/flutter_linux/fl_view.h"
static constexpr int32_t kRootSemanticsNodeId = 0;
struct _FlViewAccessible {
AtkPlug parent_instance;
FlEngine* engine;
// Semantics nodes keyed by ID
GHashTable* semantics_nodes_by_id;
// Flag to track when root node is created.
gboolean root_node_created;
};
enum { kProp0, kPropEngine, kPropLast };
G_DEFINE_TYPE(FlViewAccessible, fl_view_accessible, ATK_TYPE_PLUG)
static FlAccessibleNode* create_node(FlViewAccessible* self,
FlutterSemanticsNode2* semantics) {
if (semantics->flags & kFlutterSemanticsFlagIsTextField) {
return fl_accessible_text_field_new(self->engine, semantics->id);
}
return fl_accessible_node_new(self->engine, semantics->id);
}
static FlAccessibleNode* lookup_node(FlViewAccessible* self, int32_t id) {
return FL_ACCESSIBLE_NODE(
g_hash_table_lookup(self->semantics_nodes_by_id, GINT_TO_POINTER(id)));
}
// Gets the ATK node for the given id.
// If the node doesn't exist it will be created.
static FlAccessibleNode* get_node(FlViewAccessible* self,
FlutterSemanticsNode2* semantics) {
FlAccessibleNode* node = lookup_node(self, semantics->id);
if (node != nullptr) {
return node;
}
node = create_node(self, semantics);
if (semantics->id == kRootSemanticsNodeId) {
fl_accessible_node_set_parent(node, ATK_OBJECT(self), 0);
}
g_hash_table_insert(self->semantics_nodes_by_id,
GINT_TO_POINTER(semantics->id),
reinterpret_cast<gpointer>(node));
// Update when root node is created.
if (!self->root_node_created && semantics->id == kRootSemanticsNodeId) {
g_signal_emit_by_name(self, "children-changed::add", 0, node, nullptr);
self->root_node_created = true;
}
return node;
}
// Implements AtkObject::get_n_children
static gint fl_view_accessible_get_n_children(AtkObject* accessible) {
FlViewAccessible* self = FL_VIEW_ACCESSIBLE(accessible);
FlAccessibleNode* node = lookup_node(self, 0);
if (node == nullptr) {
return 0;
}
return 1;
}
// Implements AtkObject::ref_child
static AtkObject* fl_view_accessible_ref_child(AtkObject* accessible, gint i) {
FlViewAccessible* self = FL_VIEW_ACCESSIBLE(accessible);
FlAccessibleNode* node = lookup_node(self, 0);
if (i != 0 || node == nullptr) {
return nullptr;
}
return ATK_OBJECT(g_object_ref(node));
}
// Implements AtkObject::get_role
static AtkRole fl_view_accessible_get_role(AtkObject* accessible) {
return ATK_ROLE_PANEL;
}
// Implements AtkObject::ref_state_set
static AtkStateSet* fl_view_accessible_ref_state_set(AtkObject* accessible) {
FlViewAccessible* self = FL_VIEW_ACCESSIBLE(accessible);
FlAccessibleNode* node = lookup_node(self, 0);
return node != nullptr ? atk_object_ref_state_set(ATK_OBJECT(node)) : nullptr;
}
static void fl_view_accessible_dispose(GObject* object) {
FlViewAccessible* self = FL_VIEW_ACCESSIBLE(object);
g_clear_pointer(&self->semantics_nodes_by_id, g_hash_table_unref);
if (self->engine != nullptr) {
g_object_remove_weak_pointer(object,
reinterpret_cast<gpointer*>(&self->engine));
self->engine = nullptr;
}
G_OBJECT_CLASS(fl_view_accessible_parent_class)->dispose(object);
}
static void fl_view_accessible_class_init(FlViewAccessibleClass* klass) {
ATK_OBJECT_CLASS(klass)->get_n_children = fl_view_accessible_get_n_children;
ATK_OBJECT_CLASS(klass)->ref_child = fl_view_accessible_ref_child;
ATK_OBJECT_CLASS(klass)->get_role = fl_view_accessible_get_role;
ATK_OBJECT_CLASS(klass)->ref_state_set = fl_view_accessible_ref_state_set;
G_OBJECT_CLASS(klass)->dispose = fl_view_accessible_dispose;
}
static void fl_view_accessible_init(FlViewAccessible* self) {
self->semantics_nodes_by_id = g_hash_table_new_full(
g_direct_hash, g_direct_equal, nullptr, g_object_unref);
}
FlViewAccessible* fl_view_accessible_new(FlEngine* engine) {
FlViewAccessible* self =
FL_VIEW_ACCESSIBLE(g_object_new(fl_view_accessible_get_type(), nullptr));
self->engine = engine;
g_object_add_weak_pointer(G_OBJECT(self),
reinterpret_cast<gpointer*>(&self->engine));
return self;
}
void fl_view_accessible_handle_update_semantics(
FlViewAccessible* self,
const FlutterSemanticsUpdate2* update) {
g_autoptr(GHashTable) pending_children =
g_hash_table_new_full(g_direct_hash, g_direct_equal, nullptr,
reinterpret_cast<GDestroyNotify>(fl_value_unref));
for (size_t i = 0; i < update->node_count; i++) {
FlutterSemanticsNode2* node = update->nodes[i];
FlAccessibleNode* atk_node = get_node(self, node);
fl_accessible_node_set_flags(atk_node, node->flags);
fl_accessible_node_set_actions(atk_node, node->actions);
fl_accessible_node_set_name(atk_node, node->label);
fl_accessible_node_set_extents(
atk_node, node->rect.left + node->transform.transX,
node->rect.top + node->transform.transY,
node->rect.right - node->rect.left, node->rect.bottom - node->rect.top);
fl_accessible_node_set_value(atk_node, node->value);
fl_accessible_node_set_text_selection(atk_node, node->text_selection_base,
node->text_selection_extent);
fl_accessible_node_set_text_direction(atk_node, node->text_direction);
FlValue* children = fl_value_new_int32_list(
node->children_in_traversal_order, node->child_count);
g_hash_table_insert(pending_children, atk_node, children);
}
g_hash_table_foreach_remove(
pending_children,
[](gpointer key, gpointer value, gpointer user_data) -> gboolean {
FlViewAccessible* self = FL_VIEW_ACCESSIBLE(user_data);
FlAccessibleNode* parent = FL_ACCESSIBLE_NODE(key);
size_t child_count = fl_value_get_length(static_cast<FlValue*>(value));
const int32_t* children_in_traversal_order =
fl_value_get_int32_list(static_cast<FlValue*>(value));
g_autoptr(GPtrArray) children = g_ptr_array_new();
for (size_t i = 0; i < child_count; i++) {
FlAccessibleNode* child =
lookup_node(self, children_in_traversal_order[i]);
g_assert(child != nullptr);
fl_accessible_node_set_parent(child, ATK_OBJECT(parent), i);
g_ptr_array_add(children, child);
}
fl_accessible_node_set_children(parent, children);
return true;
},
self);
}