6
6
#include " flutter/shell/platform/linux/public/flutter_linux/fl_standard_message_codec.h"
7
7
#include " flutter/shell/platform/linux/public/flutter_linux/fl_value.h"
8
8
9
+ G_DEFINE_AUTOPTR_CLEANUP_FUNC (PangoContext, g_object_unref)
10
+ G_DEFINE_AUTOPTR_CLEANUP_FUNC(PangoLayout, g_object_unref)
11
+
12
+ typedef bool (*FlTextBoundaryCallback)(const PangoLogAttr* attr);
13
+
9
14
struct _FlAccessibleTextField {
10
15
FlAccessibleNode parent_instance;
11
16
12
17
gint selection_base;
13
18
gint selection_extent;
14
19
GtkEntryBuffer* buffer;
20
+ FlutterTextDirection text_direction;
15
21
};
16
22
17
23
static void fl_accessible_text_iface_init (AtkTextIface* iface);
@@ -36,6 +42,145 @@ static gchar* get_substring(FlAccessibleTextField* self,
36
42
return g_utf8_substring (value, start, end);
37
43
}
38
44
45
+ static PangoContext* get_pango_context (FlAccessibleTextField* self) {
46
+ PangoFontMap* font_map = pango_cairo_font_map_get_default ();
47
+ PangoContext* context = pango_font_map_create_context (font_map);
48
+ pango_context_set_base_dir (context,
49
+ self->text_direction == kFlutterTextDirectionRTL
50
+ ? PANGO_DIRECTION_RTL
51
+ : PANGO_DIRECTION_LTR);
52
+ return context;
53
+ }
54
+
55
+ static PangoLayout* create_pango_layout (FlAccessibleTextField* self) {
56
+ g_autoptr (PangoContext) context = get_pango_context (self);
57
+ PangoLayout* layout = pango_layout_new (context);
58
+ pango_layout_set_text (layout, gtk_entry_buffer_get_text (self->buffer ), -1 );
59
+ return layout;
60
+ }
61
+
62
+ static gchar* get_string_at_offset (FlAccessibleTextField* self,
63
+ gint start,
64
+ gint end,
65
+ FlTextBoundaryCallback is_start,
66
+ FlTextBoundaryCallback is_end,
67
+ gint* start_offset,
68
+ gint* end_offset) {
69
+ g_autoptr (PangoLayout) layout = create_pango_layout (self);
70
+
71
+ gint n_attrs = 0 ;
72
+ const PangoLogAttr* attrs =
73
+ pango_layout_get_log_attrs_readonly (layout, &n_attrs);
74
+
75
+ while (start > 0 && !is_start (&attrs[start])) {
76
+ --start;
77
+ }
78
+ if (start_offset != nullptr ) {
79
+ *start_offset = start;
80
+ }
81
+
82
+ while (end < n_attrs && !is_end (&attrs[end])) {
83
+ ++end;
84
+ }
85
+ if (end_offset != nullptr ) {
86
+ *end_offset = end;
87
+ }
88
+
89
+ return get_substring (self, start, end);
90
+ }
91
+
92
+ static gchar* get_char_at_offset (FlAccessibleTextField* self,
93
+ gint offset,
94
+ gint* start_offset,
95
+ gint* end_offset) {
96
+ return get_string_at_offset (
97
+ self, offset, offset + 1 ,
98
+ [](const PangoLogAttr* attr) -> bool { return attr->is_char_break ; },
99
+ [](const PangoLogAttr* attr) -> bool { return attr->is_char_break ; },
100
+ start_offset, end_offset);
101
+ }
102
+
103
+ static gchar* get_word_at_offset (FlAccessibleTextField* self,
104
+ gint offset,
105
+ gint* start_offset,
106
+ gint* end_offset) {
107
+ return get_string_at_offset (
108
+ self, offset, offset,
109
+ [](const PangoLogAttr* attr) -> bool { return attr->is_word_start ; },
110
+ [](const PangoLogAttr* attr) -> bool { return attr->is_word_end ; },
111
+ start_offset, end_offset);
112
+ }
113
+
114
+ static gchar* get_sentence_at_offset (FlAccessibleTextField* self,
115
+ gint offset,
116
+ gint* start_offset,
117
+ gint* end_offset) {
118
+ return get_string_at_offset (
119
+ self, offset, offset,
120
+ [](const PangoLogAttr* attr) -> bool { return attr->is_sentence_start ; },
121
+ [](const PangoLogAttr* attr) -> bool { return attr->is_sentence_end ; },
122
+ start_offset, end_offset);
123
+ }
124
+
125
+ static gchar* get_line_at_offset (FlAccessibleTextField* self,
126
+ gint offset,
127
+ gint* start_offset,
128
+ gint* end_offset) {
129
+ g_autoptr (PangoLayout) layout = create_pango_layout (self);
130
+
131
+ GSList* lines = pango_layout_get_lines_readonly (layout);
132
+ while (lines != nullptr ) {
133
+ PangoLayoutLine* line = static_cast <PangoLayoutLine*>(lines->data );
134
+ if (offset >= line->start_index &&
135
+ offset <= line->start_index + line->length ) {
136
+ if (start_offset != nullptr ) {
137
+ *start_offset = line->start_index ;
138
+ }
139
+ if (end_offset != nullptr ) {
140
+ *end_offset = line->start_index + line->length ;
141
+ }
142
+ return get_substring (self, line->start_index ,
143
+ line->start_index + line->length );
144
+ }
145
+ lines = lines->next ;
146
+ }
147
+
148
+ return nullptr ;
149
+ }
150
+
151
+ static gchar* get_paragraph_at_offset (FlAccessibleTextField* self,
152
+ gint offset,
153
+ gint* start_offset,
154
+ gint* end_offset) {
155
+ g_autoptr (PangoLayout) layout = create_pango_layout (self);
156
+
157
+ PangoLayoutLine* start = nullptr ;
158
+ PangoLayoutLine* end = nullptr ;
159
+ gint n_lines = pango_layout_get_line_count (layout);
160
+ for (gint i = 0 ; i < n_lines; ++i) {
161
+ PangoLayoutLine* line = pango_layout_get_line (layout, i);
162
+ if (line->is_paragraph_start ) {
163
+ end = line;
164
+ }
165
+ if (start != nullptr && end != nullptr && offset >= start->start_index &&
166
+ offset <= end->start_index + end->length ) {
167
+ if (start_offset != nullptr ) {
168
+ *start_offset = start->start_index ;
169
+ }
170
+ if (end_offset != nullptr ) {
171
+ *end_offset = end->start_index + end->length ;
172
+ }
173
+ return get_substring (self, start->start_index ,
174
+ end->start_index + end->length );
175
+ }
176
+ if (line->is_paragraph_start ) {
177
+ start = line;
178
+ }
179
+ }
180
+
181
+ return nullptr ;
182
+ }
183
+
39
184
static void perform_set_text_action (FlAccessibleTextField* self,
40
185
const char * text) {
41
186
g_autoptr (FlValue) value = fl_value_new_string (text);
@@ -109,6 +254,16 @@ static void fl_accessible_text_field_set_text_selection(FlAccessibleNode* node,
109
254
}
110
255
}
111
256
257
+ // Implements FlAccessibleNode::set_text_direction.
258
+ static void fl_accessible_text_field_set_text_direction (
259
+ FlAccessibleNode* node,
260
+ FlutterTextDirection direction) {
261
+ g_return_if_fail (FL_IS_ACCESSIBLE_TEXT_FIELD (node));
262
+ FlAccessibleTextField* self = FL_ACCESSIBLE_TEXT_FIELD (node);
263
+
264
+ self->text_direction = direction;
265
+ }
266
+
112
267
// Overrides FlAccessibleNode::perform_action.
113
268
void fl_accessible_text_field_perform_action (FlAccessibleNode* self,
114
269
FlutterSemanticsAction action,
@@ -154,6 +309,65 @@ static gchar* fl_accessible_text_field_get_text(AtkText* text,
154
309
return get_substring (self, start_offset, end_offset);
155
310
}
156
311
312
+ // Implements AtkText::get_string_at_offset.
313
+ static gchar* fl_accessible_text_field_get_string_at_offset (
314
+ AtkText* text,
315
+ gint offset,
316
+ AtkTextGranularity granularity,
317
+ gint* start_offset,
318
+ gint* end_offset) {
319
+ g_return_val_if_fail (FL_IS_ACCESSIBLE_TEXT_FIELD (text), nullptr );
320
+ FlAccessibleTextField* self = FL_ACCESSIBLE_TEXT_FIELD (text);
321
+
322
+ switch (granularity) {
323
+ case ATK_TEXT_GRANULARITY_CHAR:
324
+ return get_char_at_offset (self, offset, start_offset, end_offset);
325
+ case ATK_TEXT_GRANULARITY_WORD:
326
+ return get_word_at_offset (self, offset, start_offset, end_offset);
327
+ case ATK_TEXT_GRANULARITY_SENTENCE:
328
+ return get_sentence_at_offset (self, offset, start_offset, end_offset);
329
+ case ATK_TEXT_GRANULARITY_LINE:
330
+ return get_line_at_offset (self, offset, start_offset, end_offset);
331
+ case ATK_TEXT_GRANULARITY_PARAGRAPH:
332
+ return get_paragraph_at_offset (self, offset, start_offset, end_offset);
333
+ default :
334
+ return nullptr ;
335
+ }
336
+ }
337
+
338
+ // Implements AtkText::get_text_at_offset (deprecated but still commonly used).
339
+ static gchar* fl_accessible_text_field_get_text_at_offset (
340
+ AtkText* text,
341
+ gint offset,
342
+ AtkTextBoundary boundary_type,
343
+ gint* start_offset,
344
+ gint* end_offset) {
345
+ switch (boundary_type) {
346
+ case ATK_TEXT_BOUNDARY_CHAR:
347
+ return fl_accessible_text_field_get_string_at_offset (
348
+ text, offset, ATK_TEXT_GRANULARITY_CHAR, start_offset, end_offset);
349
+ break ;
350
+ case ATK_TEXT_BOUNDARY_WORD_START:
351
+ case ATK_TEXT_BOUNDARY_WORD_END:
352
+ return fl_accessible_text_field_get_string_at_offset (
353
+ text, offset, ATK_TEXT_GRANULARITY_WORD, start_offset, end_offset);
354
+ break ;
355
+ case ATK_TEXT_BOUNDARY_SENTENCE_START:
356
+ case ATK_TEXT_BOUNDARY_SENTENCE_END:
357
+ return fl_accessible_text_field_get_string_at_offset (
358
+ text, offset, ATK_TEXT_GRANULARITY_SENTENCE, start_offset,
359
+ end_offset);
360
+ break ;
361
+ case ATK_TEXT_BOUNDARY_LINE_START:
362
+ case ATK_TEXT_BOUNDARY_LINE_END:
363
+ return fl_accessible_text_field_get_string_at_offset (
364
+ text, offset, ATK_TEXT_GRANULARITY_LINE, start_offset, end_offset);
365
+ break ;
366
+ default :
367
+ return nullptr ;
368
+ }
369
+ }
370
+
157
371
// Implements AtkText::get_caret_offset.
158
372
static gint fl_accessible_text_field_get_caret_offset (AtkText* text) {
159
373
g_return_val_if_fail (FL_IS_ACCESSIBLE_TEXT_FIELD (text), -1 );
@@ -338,14 +552,17 @@ static void fl_accessible_text_field_class_init(
338
552
fl_accessible_text_field_set_value;
339
553
FL_ACCESSIBLE_NODE_CLASS (klass)->set_text_selection =
340
554
fl_accessible_text_field_set_text_selection;
555
+ FL_ACCESSIBLE_NODE_CLASS (klass)->set_text_direction =
556
+ fl_accessible_text_field_set_text_direction;
341
557
FL_ACCESSIBLE_NODE_CLASS (klass)->perform_action =
342
558
fl_accessible_text_field_perform_action;
343
559
}
344
560
345
561
static void fl_accessible_text_iface_init (AtkTextIface* iface) {
346
562
iface->get_character_count = fl_accessible_text_field_get_character_count;
347
563
iface->get_text = fl_accessible_text_field_get_text;
348
- // TODO(jpnurmi): get_text_at/before/after_offset
564
+ iface->get_text_at_offset = fl_accessible_text_field_get_text_at_offset;
565
+ iface->get_string_at_offset = fl_accessible_text_field_get_string_at_offset;
349
566
350
567
iface->get_caret_offset = fl_accessible_text_field_get_caret_offset;
351
568
iface->set_caret_offset = fl_accessible_text_field_set_caret_offset;
0 commit comments