1 /*
2 * Copyright (C) 2002,2003 Red Hat, Inc.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17 */
18
19 /*
20 * SECTION: vte-access
21 * @short_description: Accessibility peer of #VteTerminal
22 *
23 * The accessibility peer of a #VteTerminal, implementing GNOME's accessibility
24 * framework.
25 */
26
27 #include "config.h"
28
29 #include <atk/atk.h>
30 #include <gtk/gtk.h>
31 #include <gtk/gtk-a11y.h>
32 #include <string.h>
33 #include "debug.h"
34 #include "vte.h"
35 #include "vteaccess.h"
36 #include "vteint.h"
37 #include "vte-private.h"
38
39 #ifdef HAVE_LOCALE_H
40 #include <locale.h>
41 #endif
42 #include <glib/gi18n-lib.h>
43
44 enum {
45 ACTION_MENU,
46 LAST_ACTION
47 };
48
49 typedef struct _VteTerminalAccessiblePrivate {
50 gboolean snapshot_contents_invalid; /* This data is stale. */
51 gboolean snapshot_caret_invalid; /* This data is stale. */
52 GString *snapshot_text; /* Pointer to UTF-8 text. */
53 GArray *snapshot_characters; /* Offsets to character begin points. */
54 GArray *snapshot_attributes; /* Attributes, per byte. */
55 GArray *snapshot_linebreaks; /* Offsets to line breaks. */
56 gint snapshot_caret; /* Location of the cursor (in characters). */
57 gboolean text_caret_moved_pending;
58
59 char *action_descriptions[LAST_ACTION];
60 } VteTerminalAccessiblePrivate;
61
62 enum direction {
63 direction_previous = -1,
64 direction_current = 0,
65 direction_next = 1
66 };
67
68 static gunichar vte_terminal_accessible_get_character_at_offset(AtkText *text,
69 gint offset);
70
71 static const char *vte_terminal_accessible_action_names[] = {
72 "menu",
73 NULL
74 };
75
76 static const char *vte_terminal_accessible_action_descriptions[] = {
77 "Popup context menu",
78 NULL
79 };
80
81 static void vte_terminal_accessible_text_iface_init(AtkTextIface *iface);
82 static void vte_terminal_accessible_component_iface_init(AtkComponentIface *component);
83 static void vte_terminal_accessible_action_iface_init(AtkActionIface *action);
84
G_DEFINE_TYPE_WITH_CODE(VteTerminalAccessible,_vte_terminal_accessible,GTK_TYPE_WIDGET_ACCESSIBLE,G_ADD_PRIVATE (VteTerminalAccessible)G_IMPLEMENT_INTERFACE (ATK_TYPE_TEXT,vte_terminal_accessible_text_iface_init)G_IMPLEMENT_INTERFACE (ATK_TYPE_COMPONENT,vte_terminal_accessible_component_iface_init)G_IMPLEMENT_INTERFACE (ATK_TYPE_ACTION,vte_terminal_accessible_action_iface_init))85 G_DEFINE_TYPE_WITH_CODE (VteTerminalAccessible, _vte_terminal_accessible, GTK_TYPE_WIDGET_ACCESSIBLE,
86 G_ADD_PRIVATE (VteTerminalAccessible)
87 G_IMPLEMENT_INTERFACE (ATK_TYPE_TEXT, vte_terminal_accessible_text_iface_init)
88 G_IMPLEMENT_INTERFACE (ATK_TYPE_COMPONENT, vte_terminal_accessible_component_iface_init)
89 G_IMPLEMENT_INTERFACE (ATK_TYPE_ACTION, vte_terminal_accessible_action_iface_init))
90
91 static gint
92 offset_from_xy (VteTerminalAccessiblePrivate *priv,
93 gint x, gint y)
94 {
95 gint offset;
96 gint linebreak;
97 gint next_linebreak;
98
99 if (y >= (gint) priv->snapshot_linebreaks->len)
100 y = priv->snapshot_linebreaks->len -1;
101
102 linebreak = g_array_index (priv->snapshot_linebreaks, int, y);
103 if (y + 1 == (gint) priv->snapshot_linebreaks->len)
104 next_linebreak = priv->snapshot_characters->len;
105 else
106 next_linebreak = g_array_index (priv->snapshot_linebreaks, int, y + 1);
107
108 offset = linebreak + x;
109 if (offset >= next_linebreak)
110 offset = next_linebreak -1;
111 return offset;
112 }
113
114 static void
xy_from_offset(VteTerminalAccessiblePrivate * priv,guint offset,gint * x,gint * y)115 xy_from_offset (VteTerminalAccessiblePrivate *priv,
116 guint offset, gint *x, gint *y)
117 {
118 guint i, linebreak;
119 gint cur_x, cur_y;
120 gint cur_offset = 0;
121
122 cur_x = -1;
123 cur_y = -1;
124 for (i = 0; i < priv->snapshot_linebreaks->len; i++) {
125 linebreak = g_array_index (priv->snapshot_linebreaks, int, i);
126 if (offset < linebreak) {
127 cur_x = offset - cur_offset;
128 cur_y = i - 1;
129 break;
130
131 } else {
132 cur_offset = linebreak;
133 }
134 }
135 if (i == priv->snapshot_linebreaks->len) {
136 if (offset <= priv->snapshot_characters->len) {
137 cur_x = offset - cur_offset;
138 cur_y = i - 1;
139 }
140 }
141 *x = cur_x;
142 *y = cur_y;
143 }
144
145 /* "Oh yeah, that's selected. Sure." callback. */
146 static gboolean
all_selected(VteTerminal * terminal,glong column,glong row,gpointer data)147 all_selected(VteTerminal *terminal, glong column, glong row, gpointer data)
148 {
149 return TRUE;
150 }
151
152 static void
emit_text_caret_moved(GObject * object,glong caret)153 emit_text_caret_moved(GObject *object, glong caret)
154 {
155 _vte_debug_print(VTE_DEBUG_SIGNALS|VTE_DEBUG_ALLY,
156 "Accessibility peer emitting "
157 "`text-caret-moved'.\n");
158 g_signal_emit_by_name(object, "text-caret-moved", caret);
159 }
160
161 static void
emit_text_changed_insert(GObject * object,const char * text,glong offset,glong len)162 emit_text_changed_insert(GObject *object,
163 const char *text, glong offset, glong len)
164 {
165 glong start, count;
166 if (len == 0) {
167 return;
168 }
169 /* Convert the byte offsets to character offsets. */
170 start = g_utf8_pointer_to_offset (text, text + offset);
171 count = g_utf8_pointer_to_offset (text + offset, text + offset + len);
172 _vte_debug_print(VTE_DEBUG_SIGNALS|VTE_DEBUG_ALLY,
173 "Accessibility peer emitting "
174 "`text-changed::insert' (%ld, %ld) (%ld, %ld).\n"
175 "Inserted text was `%.*s'.\n",
176 offset, len, start, count,
177 (int) len, text + offset);
178 g_signal_emit_by_name(object, "text-changed::insert", start, count);
179 }
180
181 static void
emit_text_changed_delete(GObject * object,const char * text,glong offset,glong len)182 emit_text_changed_delete(GObject *object,
183 const char *text, glong offset, glong len)
184 {
185 glong start, count;
186 if (len == 0) {
187 return;
188 }
189 /* Convert the byte offsets to characters. */
190 start = g_utf8_pointer_to_offset (text, text + offset);
191 count = g_utf8_pointer_to_offset (text + offset, text + offset + len);
192 _vte_debug_print(VTE_DEBUG_SIGNALS|VTE_DEBUG_ALLY,
193 "Accessibility peer emitting "
194 "`text-changed::delete' (%ld, %ld) (%ld, %ld).\n"
195 "Deleted text was `%.*s'.\n",
196 offset, len, start, count,
197 (int) len, text + offset);
198 g_signal_emit_by_name(object, "text-changed::delete", start, count);
199 }
200
201 static void
vte_terminal_accessible_update_private_data_if_needed(VteTerminalAccessible * accessible,GString ** old_text,GArray ** old_characters)202 vte_terminal_accessible_update_private_data_if_needed(VteTerminalAccessible *accessible,
203 GString **old_text,
204 GArray **old_characters)
205 {
206 VteTerminalAccessiblePrivate *priv = _vte_terminal_accessible_get_instance_private(accessible);
207 VteTerminal *terminal;
208 struct _VteCharAttributes attrs;
209 char *next, *tmp;
210 long row, offset, caret;
211 long ccol, crow;
212 guint i;
213
214 /* If nothing's changed, just return immediately. */
215 if ((priv->snapshot_contents_invalid == FALSE) &&
216 (priv->snapshot_caret_invalid == FALSE)) {
217 if (old_text) {
218 if (priv->snapshot_text) {
219 *old_text = g_string_new_len(priv->snapshot_text->str,
220 priv->snapshot_text->len);
221 } else {
222 *old_text = g_string_new("");
223 }
224 }
225 if (old_characters) {
226 if (priv->snapshot_characters) {
227 *old_characters = g_array_sized_new(FALSE, FALSE, sizeof(int),
228 priv->snapshot_characters->len);
229 g_array_append_vals(*old_characters,
230 priv->snapshot_characters->data,
231 priv->snapshot_characters->len);
232 } else {
233 *old_characters = g_array_new(FALSE, FALSE, sizeof(int));
234 }
235 }
236 return;
237 }
238
239 /* Re-read the contents of the widget if the contents have changed. */
240 terminal = VTE_TERMINAL(gtk_accessible_get_widget(GTK_ACCESSIBLE(accessible)));
241 if (priv->snapshot_contents_invalid) {
242 /* Free the outdated snapshot data, unless the caller
243 * wants it. */
244 if (old_text) {
245 if (priv->snapshot_text != NULL) {
246 *old_text = priv->snapshot_text;
247 } else {
248 *old_text = g_string_new("");
249 }
250 } else {
251 if (priv->snapshot_text != NULL) {
252 g_string_free(priv->snapshot_text, TRUE);
253 }
254 }
255 priv->snapshot_text = NULL;
256
257 /* Free the character offsets unless the caller wants it,
258 * and allocate a new array to hold them. */
259 if (old_characters) {
260 if (priv->snapshot_characters != NULL) {
261 *old_characters = priv->snapshot_characters;
262 } else {
263 *old_characters = g_array_new(FALSE, FALSE, sizeof(int));
264 }
265 } else {
266 if (priv->snapshot_characters != NULL) {
267 g_array_free(priv->snapshot_characters, TRUE);
268 }
269 }
270 priv->snapshot_characters = g_array_new(FALSE, FALSE, sizeof(int));
271
272 /* Free the attribute lists and allocate a new array to hold
273 * them. */
274 if (priv->snapshot_attributes != NULL) {
275 g_array_free(priv->snapshot_attributes, TRUE);
276 }
277 priv->snapshot_attributes = g_array_new(FALSE, FALSE,
278 sizeof(struct _VteCharAttributes));
279
280 /* Free the linebreak offsets and allocate a new array to hold
281 * them. */
282 if (priv->snapshot_linebreaks != NULL) {
283 g_array_free(priv->snapshot_linebreaks, TRUE);
284 }
285 priv->snapshot_linebreaks = g_array_new(FALSE, FALSE, sizeof(int));
286
287 /* Get a new view of the uber-label. */
288 tmp = vte_terminal_get_text_include_trailing_spaces(terminal,
289 all_selected,
290 NULL,
291 priv->snapshot_attributes);
292 if (tmp == NULL) {
293 /* Aaargh! We're screwed. */
294 return;
295 }
296 priv->snapshot_text = g_string_new_len(tmp,
297 priv->snapshot_attributes->len);
298 g_free(tmp);
299
300 /* Get the offsets to the beginnings of each character. */
301 i = 0;
302 next = priv->snapshot_text->str;
303 while (i < priv->snapshot_attributes->len) {
304 g_array_append_val(priv->snapshot_characters, i);
305 next = g_utf8_next_char(next);
306 if (next == NULL) {
307 break;
308 } else {
309 i = next - priv->snapshot_text->str;
310 }
311 }
312 /* Find offsets for the beginning of lines. */
313 for (i = 0, row = 0; i < priv->snapshot_characters->len; i++) {
314 /* Get the attributes for the current cell. */
315 offset = g_array_index(priv->snapshot_characters,
316 int, i);
317 attrs = g_array_index(priv->snapshot_attributes,
318 struct _VteCharAttributes,
319 offset);
320 /* If this character is on a row different from the row
321 * the character we looked at previously was on, then
322 * it's a new line and we need to keep track of where
323 * it is. */
324 if ((i == 0) || (attrs.row != row)) {
325 _vte_debug_print(VTE_DEBUG_ALLY,
326 "Row %d/%ld begins at %u.\n",
327 priv->snapshot_linebreaks->len,
328 attrs.row, i);
329 g_array_append_val(priv->snapshot_linebreaks, i);
330 }
331 row = attrs.row;
332 }
333 /* Add the final line break. */
334 g_array_append_val(priv->snapshot_linebreaks, i);
335 /* We're finished updating this. */
336 priv->snapshot_contents_invalid = FALSE;
337 }
338
339 /* Update the caret position. */
340 vte_terminal_get_cursor_position(terminal, &ccol, &crow);
341 _vte_debug_print(VTE_DEBUG_ALLY,
342 "Cursor at (%ld, " "%ld).\n", ccol, crow);
343
344 /* Get the offsets to the beginnings of each line. */
345 caret = 0;
346 for (i = 0; i < priv->snapshot_characters->len; i++) {
347 /* Get the attributes for the current cell. */
348 offset = g_array_index(priv->snapshot_characters,
349 int, i);
350 attrs = g_array_index(priv->snapshot_attributes,
351 struct _VteCharAttributes,
352 offset);
353 /* If this cell is "before" the cursor, move the
354 * caret to be "here". */
355 if ((attrs.row < crow) ||
356 ((attrs.row == crow) && (attrs.column < ccol))) {
357 caret = i + 1;
358 }
359 }
360
361 /* Make a note that we'll need to notify observers if the caret moved.
362 * But only notify them after sending text-changed. */
363 if (caret != priv->snapshot_caret) {
364 priv->snapshot_caret = caret;
365 priv->text_caret_moved_pending = TRUE;
366 }
367
368 /* Done updating the caret position, whether we needed to or not. */
369 priv->snapshot_caret_invalid = FALSE;
370
371 _vte_debug_print(VTE_DEBUG_ALLY,
372 "Refreshed accessibility snapshot, "
373 "%ld cells, %ld characters.\n",
374 (long)priv->snapshot_attributes->len,
375 (long)priv->snapshot_characters->len);
376 }
377
378 static void
vte_terminal_accessible_maybe_emit_text_caret_moved(VteTerminalAccessible * accessible)379 vte_terminal_accessible_maybe_emit_text_caret_moved(VteTerminalAccessible *accessible)
380 {
381 VteTerminalAccessiblePrivate *priv = _vte_terminal_accessible_get_instance_private(accessible);
382
383 if (priv->text_caret_moved_pending) {
384 emit_text_caret_moved(G_OBJECT(accessible), priv->snapshot_caret);
385 priv->text_caret_moved_pending = FALSE;
386 }
387 }
388
389 /* A signal handler to catch "text-inserted/deleted/modified" signals. */
390 static void
vte_terminal_accessible_text_modified(VteTerminal * terminal,gpointer data)391 vte_terminal_accessible_text_modified(VteTerminal *terminal, gpointer data)
392 {
393 VteTerminalAccessible *accessible = data;
394 VteTerminalAccessiblePrivate *priv = _vte_terminal_accessible_get_instance_private(accessible);
395 GString *old_text;
396 GArray *old_characters;
397 char *old, *current;
398 glong offset, caret_offset, olen, clen;
399 gint old_snapshot_caret;
400
401 old_snapshot_caret = priv->snapshot_caret;
402 priv->snapshot_contents_invalid = TRUE;
403 vte_terminal_accessible_update_private_data_if_needed(accessible,
404 &old_text,
405 &old_characters);
406 g_assert(old_text != NULL);
407 g_assert(old_characters != NULL);
408
409 current = priv->snapshot_text->str;
410 clen = priv->snapshot_text->len;
411 old = old_text->str;
412 olen = old_text->len;
413
414 if ((guint) priv->snapshot_caret < priv->snapshot_characters->len) {
415 caret_offset = g_array_index(priv->snapshot_characters,
416 int, priv->snapshot_caret);
417 } else {
418 /* caret was not in the line */
419 caret_offset = clen;
420 }
421
422 /* Find the offset where they don't match. */
423 offset = 0;
424 while ((offset < olen) && (offset < clen)) {
425 if (old[offset] != current[offset]) {
426 break;
427 }
428 offset++;
429 }
430
431 /* Check if we just backspaced over a space. */
432 if ((olen == offset) &&
433 (caret_offset < olen && old[caret_offset] == ' ') &&
434 (old_snapshot_caret == priv->snapshot_caret + 1)) {
435 GString *saved_text = priv->snapshot_text;
436 GArray *saved_characters = priv->snapshot_characters;
437
438 priv->snapshot_text = old_text;
439 priv->snapshot_characters = old_characters;
440 emit_text_changed_delete(G_OBJECT(accessible),
441 old, caret_offset, 1);
442 priv->snapshot_text = saved_text;
443 priv->snapshot_characters = saved_characters;
444 }
445
446
447 /* At least one of them had better have more data, right? */
448 if ((offset < olen) || (offset < clen)) {
449 /* Back up from both end points until we find the *last* point
450 * where they differed. */
451 gchar *op = old + olen;
452 gchar *cp = current + clen;
453 while (op > old + offset && cp > current + offset) {
454 gchar *opp = g_utf8_prev_char (op);
455 gchar *cpp = g_utf8_prev_char (cp);
456 if (g_utf8_get_char (opp) != g_utf8_get_char (cpp)) {
457 break;
458 }
459 op = opp;
460 cp = cpp;
461 }
462 /* recompute the respective lengths */
463 olen = op - old;
464 clen = cp - current;
465 /* At least one of them has to have text the other
466 * doesn't. */
467 g_assert((clen > offset) || (olen > offset));
468 g_assert((clen >= 0) && (olen >= 0));
469 /* Now emit a deleted signal for text that was in the old
470 * string but isn't in the new one... */
471 if (olen > offset) {
472 GString *saved_text = priv->snapshot_text;
473 GArray *saved_characters = priv->snapshot_characters;
474
475 priv->snapshot_text = old_text;
476 priv->snapshot_characters = old_characters;
477 emit_text_changed_delete(G_OBJECT(accessible),
478 old,
479 offset,
480 olen - offset);
481 priv->snapshot_text = saved_text;
482 priv->snapshot_characters = saved_characters;
483 }
484 /* .. and an inserted signal for text that wasn't in the old
485 * string but is in the new one. */
486 if (clen > offset) {
487 emit_text_changed_insert(G_OBJECT(accessible),
488 current,
489 offset,
490 clen - offset);
491 }
492 }
493
494 vte_terminal_accessible_maybe_emit_text_caret_moved(accessible);
495
496 g_string_free(old_text, TRUE);
497 g_array_free(old_characters, TRUE);
498 }
499
500 /* A signal handler to catch "text-scrolled" signals. */
501 static void
vte_terminal_accessible_text_scrolled(VteTerminal * terminal,gint howmuch,gpointer data)502 vte_terminal_accessible_text_scrolled(VteTerminal *terminal,
503 gint howmuch,
504 gpointer data)
505 {
506 VteTerminalAccessible *accessible = data;
507 VteTerminalAccessiblePrivate *priv = _vte_terminal_accessible_get_instance_private(accessible);
508 struct _VteCharAttributes attr;
509 long delta, row_count;
510 guint i, len;
511
512 g_assert(howmuch != 0);
513
514 row_count = vte_terminal_get_row_count(terminal);
515 if (((howmuch < 0) && (howmuch <= -row_count)) ||
516 ((howmuch > 0) && (howmuch >= row_count))) {
517 /* All of the text was removed. */
518 if (priv->snapshot_text != NULL) {
519 if (priv->snapshot_text->str != NULL) {
520 emit_text_changed_delete(G_OBJECT(accessible),
521 priv->snapshot_text->str,
522 0,
523 priv->snapshot_text->len);
524 }
525 }
526 priv->snapshot_contents_invalid = TRUE;
527 vte_terminal_accessible_update_private_data_if_needed(accessible,
528 NULL,
529 NULL);
530 /* All of the present text was added. */
531 if (priv->snapshot_text != NULL) {
532 if (priv->snapshot_text->str != NULL) {
533 emit_text_changed_insert(G_OBJECT(accessible),
534 priv->snapshot_text->str,
535 0,
536 priv->snapshot_text->len);
537 }
538 }
539 vte_terminal_accessible_maybe_emit_text_caret_moved(accessible);
540 return;
541 }
542 /* Find the start point. */
543 delta = 0;
544 if (priv->snapshot_attributes != NULL) {
545 if (priv->snapshot_attributes->len > 0) {
546 attr = g_array_index(priv->snapshot_attributes,
547 struct _VteCharAttributes,
548 0);
549 delta = attr.row;
550 }
551 }
552 /* We scrolled up, so text was added at the top and removed
553 * from the bottom. */
554 if ((howmuch < 0) && (howmuch > -row_count)) {
555 gboolean inserted = FALSE;
556 howmuch = -howmuch;
557 if (priv->snapshot_attributes != NULL &&
558 priv->snapshot_text != NULL) {
559 /* Find the first byte that scrolled off. */
560 for (i = 0; i < priv->snapshot_attributes->len; i++) {
561 attr = g_array_index(priv->snapshot_attributes,
562 struct _VteCharAttributes,
563 i);
564 if (attr.row >= delta + row_count - howmuch) {
565 break;
566 }
567 }
568 if (i < priv->snapshot_attributes->len) {
569 /* The rest of the string was deleted -- make a note. */
570 emit_text_changed_delete(G_OBJECT(accessible),
571 priv->snapshot_text->str,
572 i,
573 priv->snapshot_attributes->len - i);
574 }
575 inserted = TRUE;
576 }
577 /* Refresh. Note that i is now the length of the data which
578 * we expect to have left over. */
579 priv->snapshot_contents_invalid = TRUE;
580 vte_terminal_accessible_update_private_data_if_needed(accessible,
581 NULL,
582 NULL);
583 /* If we now have more text than before, the initial portion
584 * was added. */
585 if (inserted) {
586 len = priv->snapshot_text->len;
587 if (len > i) {
588 emit_text_changed_insert(G_OBJECT(accessible),
589 priv->snapshot_text->str,
590 0,
591 len - i);
592 }
593 }
594 vte_terminal_accessible_maybe_emit_text_caret_moved(accessible);
595 return;
596 }
597 /* We scrolled down, so text was added at the bottom and removed
598 * from the top. */
599 if ((howmuch > 0) && (howmuch < row_count)) {
600 gboolean inserted = FALSE;
601 if (priv->snapshot_attributes != NULL &&
602 priv->snapshot_text != NULL) {
603 /* Find the first byte that wasn't scrolled off the top. */
604 for (i = 0; i < priv->snapshot_attributes->len; i++) {
605 attr = g_array_index(priv->snapshot_attributes,
606 struct _VteCharAttributes,
607 i);
608 if (attr.row >= delta + howmuch) {
609 break;
610 }
611 }
612 /* That many bytes disappeared -- make a note. */
613 emit_text_changed_delete(G_OBJECT(accessible),
614 priv->snapshot_text->str,
615 0,
616 i);
617 /* Figure out how much text was left, and refresh. */
618 i = strlen(priv->snapshot_text->str + i);
619 inserted = TRUE;
620 }
621 priv->snapshot_contents_invalid = TRUE;
622 vte_terminal_accessible_update_private_data_if_needed(accessible,
623 NULL,
624 NULL);
625 /* Any newly-added string data is new, so note that it was
626 * inserted. */
627 if (inserted) {
628 len = priv->snapshot_text->len;
629 if (len > i) {
630 /* snapshot_text always contains a trailing '\n',
631 * insertion happens in front of it: bug 657960 */
632 g_assert(i >= 1);
633 emit_text_changed_insert(G_OBJECT(accessible),
634 priv->snapshot_text->str,
635 i - 1,
636 len - i);
637 }
638 }
639 vte_terminal_accessible_maybe_emit_text_caret_moved(accessible);
640 return;
641 }
642 g_assert_not_reached();
643 }
644
645 /* A signal handler to catch "cursor-moved" signals. */
646 static void
vte_terminal_accessible_invalidate_cursor(VteTerminal * terminal,gpointer data)647 vte_terminal_accessible_invalidate_cursor(VteTerminal *terminal, gpointer data)
648 {
649 VteTerminalAccessible *accessible = data;
650 VteTerminalAccessiblePrivate *priv = _vte_terminal_accessible_get_instance_private(accessible);
651
652 _vte_debug_print(VTE_DEBUG_ALLY,
653 "Invalidating accessibility cursor.\n");
654 priv->snapshot_caret_invalid = TRUE;
655 vte_terminal_accessible_update_private_data_if_needed(accessible,
656 NULL, NULL);
657 vte_terminal_accessible_maybe_emit_text_caret_moved(accessible);
658 }
659
660 /* Handle title changes by resetting the description. */
661 static void
vte_terminal_accessible_title_changed(VteTerminal * terminal,gpointer data)662 vte_terminal_accessible_title_changed(VteTerminal *terminal, gpointer data)
663 {
664 VteTerminalAccessible *accessible = VTE_TERMINAL_ACCESSIBLE(data);
665
666 atk_object_set_description(ATK_OBJECT(accessible), vte_terminal_get_window_title(terminal));
667 }
668
669 /* Reflect visibility-notify events. */
670 static gboolean
vte_terminal_accessible_visibility_notify(VteTerminal * terminal,GdkEventVisibility * event,gpointer data)671 vte_terminal_accessible_visibility_notify(VteTerminal *terminal,
672 GdkEventVisibility *event,
673 gpointer data)
674 {
675 VteTerminalAccessible *accessible = VTE_TERMINAL_ACCESSIBLE(data);
676 GtkWidget *widget;
677 gboolean visible;
678
679 visible = event->state != GDK_VISIBILITY_FULLY_OBSCURED;
680 /* The VISIBLE state indicates that this widget is "visible". */
681 atk_object_notify_state_change(ATK_OBJECT(accessible),
682 ATK_STATE_VISIBLE,
683 visible);
684 widget = &terminal->widget;
685 while (visible) {
686 if (gtk_widget_get_toplevel(widget) == widget) {
687 break;
688 }
689 if (widget == NULL) {
690 break;
691 }
692 visible = visible && (gtk_widget_get_visible(widget));
693 widget = gtk_widget_get_parent(widget);
694 }
695 /* The SHOWING state indicates that this widget, and all of its
696 * parents up to the toplevel, are "visible". */
697 atk_object_notify_state_change(ATK_OBJECT(accessible),
698 ATK_STATE_SHOWING,
699 visible);
700
701 return FALSE;
702 }
703
704 static void
vte_terminal_accessible_selection_changed(VteTerminal * terminal,gpointer data)705 vte_terminal_accessible_selection_changed (VteTerminal *terminal,
706 gpointer data)
707 {
708 VteTerminalAccessible *accessible = VTE_TERMINAL_ACCESSIBLE(data);
709
710 g_signal_emit_by_name (accessible, "text_selection_changed");
711 }
712
713 static void
vte_terminal_accessible_initialize(AtkObject * obj,gpointer data)714 vte_terminal_accessible_initialize (AtkObject *obj, gpointer data)
715 {
716 VteTerminal *terminal = VTE_TERMINAL (data);
717 const char *window_title;
718
719 ATK_OBJECT_CLASS (_vte_terminal_accessible_parent_class)->initialize (obj, data);
720
721 _vte_terminal_accessible_ref(terminal);
722
723 g_signal_connect(terminal, "text-inserted",
724 G_CALLBACK(vte_terminal_accessible_text_modified),
725 obj);
726 g_signal_connect(terminal, "text-deleted",
727 G_CALLBACK(vte_terminal_accessible_text_modified),
728 obj);
729 g_signal_connect(terminal, "text-modified",
730 G_CALLBACK(vte_terminal_accessible_text_modified),
731 obj);
732 g_signal_connect(terminal, "text-scrolled",
733 G_CALLBACK(vte_terminal_accessible_text_scrolled),
734 obj);
735 g_signal_connect(terminal, "cursor-moved",
736 G_CALLBACK(vte_terminal_accessible_invalidate_cursor),
737 obj);
738 g_signal_connect(terminal, "window-title-changed",
739 G_CALLBACK(vte_terminal_accessible_title_changed),
740 obj);
741
742 g_signal_connect(terminal, "visibility-notify-event",
743 G_CALLBACK(vte_terminal_accessible_visibility_notify), obj);
744 g_signal_connect(terminal, "selection-changed",
745 G_CALLBACK(vte_terminal_accessible_selection_changed), obj);
746
747 atk_object_set_name(obj, "Terminal");
748 window_title = vte_terminal_get_window_title(terminal);
749 atk_object_set_description(obj, window_title ? window_title : "");
750
751 atk_object_notify_state_change(obj,
752 ATK_STATE_FOCUSABLE, TRUE);
753 atk_object_notify_state_change(obj,
754 ATK_STATE_EXPANDABLE, FALSE);
755 atk_object_notify_state_change(obj,
756 ATK_STATE_RESIZABLE, TRUE);
757 atk_object_set_role(obj, ATK_ROLE_TERMINAL);
758 }
759
760 static void
_vte_terminal_accessible_init(VteTerminalAccessible * accessible)761 _vte_terminal_accessible_init (VteTerminalAccessible *accessible)
762 {
763 VteTerminalAccessiblePrivate *priv = _vte_terminal_accessible_get_instance_private (accessible);
764
765 _vte_debug_print(VTE_DEBUG_ALLY, "Initialising accessible peer.\n");
766
767 priv->snapshot_text = NULL;
768 priv->snapshot_characters = NULL;
769 priv->snapshot_attributes = NULL;
770 priv->snapshot_linebreaks = NULL;
771 priv->snapshot_caret = -1;
772 priv->snapshot_contents_invalid = TRUE;
773 priv->snapshot_caret_invalid = TRUE;
774 priv->text_caret_moved_pending = FALSE;
775 }
776
777 static void
vte_terminal_accessible_finalize(GObject * object)778 vte_terminal_accessible_finalize(GObject *object)
779 {
780 VteTerminalAccessible *accessible = VTE_TERMINAL_ACCESSIBLE(object);
781 VteTerminalAccessiblePrivate *priv = _vte_terminal_accessible_get_instance_private(accessible);
782 GtkWidget *widget;
783 gint i;
784
785 _vte_debug_print(VTE_DEBUG_ALLY, "Finalizing accessible peer.\n");
786
787 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE(accessible));
788
789 if (widget != NULL) {
790 g_signal_handlers_disconnect_matched(widget,
791 G_SIGNAL_MATCH_FUNC |
792 G_SIGNAL_MATCH_DATA,
793 0, 0, NULL,
794 vte_terminal_accessible_text_modified,
795 object);
796 g_signal_handlers_disconnect_matched(widget,
797 G_SIGNAL_MATCH_FUNC |
798 G_SIGNAL_MATCH_DATA,
799 0, 0, NULL,
800 vte_terminal_accessible_text_scrolled,
801 object);
802 g_signal_handlers_disconnect_matched(widget,
803 G_SIGNAL_MATCH_FUNC |
804 G_SIGNAL_MATCH_DATA,
805 0, 0, NULL,
806 vte_terminal_accessible_invalidate_cursor,
807 object);
808 g_signal_handlers_disconnect_matched(widget,
809 G_SIGNAL_MATCH_FUNC |
810 G_SIGNAL_MATCH_DATA,
811 0, 0, NULL,
812 vte_terminal_accessible_title_changed,
813 object);
814 g_signal_handlers_disconnect_matched(widget,
815 G_SIGNAL_MATCH_FUNC |
816 G_SIGNAL_MATCH_DATA,
817 0, 0, NULL,
818 vte_terminal_accessible_visibility_notify,
819 object);
820 }
821
822 if (priv->snapshot_text != NULL) {
823 g_string_free(priv->snapshot_text, TRUE);
824 }
825 if (priv->snapshot_characters != NULL) {
826 g_array_free(priv->snapshot_characters, TRUE);
827 }
828 if (priv->snapshot_attributes != NULL) {
829 g_array_free(priv->snapshot_attributes, TRUE);
830 }
831 if (priv->snapshot_linebreaks != NULL) {
832 g_array_free(priv->snapshot_linebreaks, TRUE);
833 }
834 for (i = 0; i < LAST_ACTION; i++) {
835 g_free (priv->action_descriptions[i]);
836 }
837
838 G_OBJECT_CLASS(_vte_terminal_accessible_parent_class)->finalize(object);
839 }
840
841 static gchar *
vte_terminal_accessible_get_text(AtkText * text,gint start_offset,gint end_offset)842 vte_terminal_accessible_get_text(AtkText *text,
843 gint start_offset, gint end_offset)
844 {
845 VteTerminalAccessible *accessible = VTE_TERMINAL_ACCESSIBLE(text);
846 VteTerminalAccessiblePrivate *priv = _vte_terminal_accessible_get_instance_private(accessible);
847 int start, end;
848 gchar *ret;
849
850 g_assert(VTE_IS_TERMINAL_ACCESSIBLE(accessible));
851
852 /* Swap around if start is greater than end */
853 if (start_offset > end_offset && end_offset != -1) {
854 gint tmp;
855
856 tmp = start_offset;
857 start_offset = end_offset;
858 end_offset = tmp;
859 }
860
861 g_assert((start_offset >= 0) && (end_offset >= -1));
862
863 vte_terminal_accessible_update_private_data_if_needed(accessible,
864 NULL, NULL);
865
866 _vte_debug_print(VTE_DEBUG_ALLY,
867 "Getting text from %d to %d of %d.\n",
868 start_offset, end_offset,
869 priv->snapshot_characters->len);
870
871 /* If the requested area is after all of the text, just return an
872 * empty string. */
873 if (start_offset >= (int) priv->snapshot_characters->len) {
874 return g_strdup("");
875 }
876
877 /* Map the offsets to, er, offsets. */
878 start = g_array_index(priv->snapshot_characters, int, start_offset);
879 if ((end_offset == -1) || (end_offset >= (int) priv->snapshot_characters->len) ) {
880 /* Get everything up to the end of the buffer. */
881 end = priv->snapshot_text->len;
882 } else {
883 /* Map the stopping point. */
884 end = g_array_index(priv->snapshot_characters, int, end_offset);
885 }
886 if (end <= start) {
887 ret = g_strdup ("");
888 } else {
889 ret = g_malloc(end - start + 1);
890 memcpy(ret, priv->snapshot_text->str + start, end - start);
891 ret[end - start] = '\0';
892 }
893 return ret;
894 }
895
896 /* Map a subsection of the text with before/at/after char/word/line specs
897 * into a run of Unicode characters. (The interface is specifying characters,
898 * not bytes, plus that saves us from having to deal with parts of multibyte
899 * characters, which are icky.) */
900 static gchar *
vte_terminal_accessible_get_text_somewhere(AtkText * text,gint offset,AtkTextBoundary boundary_type,enum direction direction,gint * start_offset,gint * end_offset)901 vte_terminal_accessible_get_text_somewhere(AtkText *text,
902 gint offset,
903 AtkTextBoundary boundary_type,
904 enum direction direction,
905 gint *start_offset,
906 gint *end_offset)
907 {
908 VteTerminalAccessible *accessible = VTE_TERMINAL_ACCESSIBLE(text);
909 VteTerminalAccessiblePrivate *priv = _vte_terminal_accessible_get_instance_private(accessible);
910 VteTerminal *terminal;
911 gunichar current, prev, next;
912 guint start, end, line;
913
914 vte_terminal_accessible_update_private_data_if_needed(accessible,
915 NULL, NULL);
916
917 terminal = VTE_TERMINAL(gtk_accessible_get_widget (GTK_ACCESSIBLE(text)));
918
919 _vte_debug_print(VTE_DEBUG_ALLY,
920 "Getting %s %s at %d of %d.\n",
921 (direction == direction_current) ? "this" :
922 ((direction == direction_next) ? "next" : "previous"),
923 (boundary_type == ATK_TEXT_BOUNDARY_CHAR) ? "char" :
924 ((boundary_type == ATK_TEXT_BOUNDARY_LINE_START) ? "line (start)" :
925 ((boundary_type == ATK_TEXT_BOUNDARY_LINE_END) ? "line (end)" :
926 ((boundary_type == ATK_TEXT_BOUNDARY_WORD_START) ? "word (start)" :
927 ((boundary_type == ATK_TEXT_BOUNDARY_WORD_END) ? "word (end)" :
928 ((boundary_type == ATK_TEXT_BOUNDARY_SENTENCE_START) ? "sentence (start)" :
929 ((boundary_type == ATK_TEXT_BOUNDARY_SENTENCE_END) ? "sentence (end)" : "unknown")))))),
930 offset, priv->snapshot_attributes->len);
931 g_assert(priv->snapshot_text != NULL);
932 g_assert(priv->snapshot_characters != NULL);
933 if (offset >= (int) priv->snapshot_characters->len) {
934 return g_strdup("");
935 }
936 g_assert(offset < (int) priv->snapshot_characters->len);
937 g_assert(offset >= 0);
938
939 switch (boundary_type) {
940 case ATK_TEXT_BOUNDARY_CHAR:
941 /* We're either looking at the character at this
942 * position, the one before it, or the one after it. */
943 offset += direction;
944 start = MAX(offset, 0);
945 end = MIN(offset + 1, (int) priv->snapshot_attributes->len);
946 break;
947 case ATK_TEXT_BOUNDARY_WORD_START:
948 /* Back up to the previous non-word-word transition. */
949 while (offset > 0) {
950 prev = vte_terminal_accessible_get_character_at_offset(text, offset - 1);
951 if (_vte_terminal_is_word_char(terminal, prev)) {
952 offset--;
953 } else {
954 break;
955 }
956 }
957 start = offset;
958 /* If we started in a word and we're looking for the
959 * word before this one, keep searching by backing up
960 * to the previous non-word character and then searching
961 * for the word-start before that. */
962 if (direction == direction_previous) {
963 while (offset > 0) {
964 prev = vte_terminal_accessible_get_character_at_offset(text, offset - 1);
965 if (!_vte_terminal_is_word_char(terminal, prev)) {
966 offset--;
967 } else {
968 break;
969 }
970 }
971 while (offset > 0) {
972 prev = vte_terminal_accessible_get_character_at_offset(text, offset - 1);
973 if (_vte_terminal_is_word_char(terminal, prev)) {
974 offset--;
975 } else {
976 break;
977 }
978 }
979 start = offset;
980 }
981 /* If we're looking for the word after this one,
982 * search forward by scanning forward for the next
983 * non-word character, then the next word character
984 * after that. */
985 if (direction == direction_next) {
986 while (offset < (int) priv->snapshot_characters->len) {
987 next = vte_terminal_accessible_get_character_at_offset(text, offset);
988 if (_vte_terminal_is_word_char(terminal, next)) {
989 offset++;
990 } else {
991 break;
992 }
993 }
994 while (offset < (int) priv->snapshot_characters->len) {
995 next = vte_terminal_accessible_get_character_at_offset(text, offset);
996 if (!_vte_terminal_is_word_char(terminal, next)) {
997 offset++;
998 } else {
999 break;
1000 }
1001 }
1002 start = offset;
1003 }
1004 /* Now find the end of this word. */
1005 while (offset < (int) priv->snapshot_characters->len) {
1006 current = vte_terminal_accessible_get_character_at_offset(text, offset);
1007 if (_vte_terminal_is_word_char(terminal, current)) {
1008 offset++;
1009 } else {
1010 break;
1011 }
1012
1013 }
1014 /* Now find the next non-word-word transition */
1015 while (offset < (int) priv->snapshot_characters->len) {
1016 next = vte_terminal_accessible_get_character_at_offset(text, offset);
1017 if (!_vte_terminal_is_word_char(terminal, next)) {
1018 offset++;
1019 } else {
1020 break;
1021 }
1022 }
1023 end = offset;
1024 break;
1025 case ATK_TEXT_BOUNDARY_WORD_END:
1026 /* Back up to the previous word-non-word transition. */
1027 current = vte_terminal_accessible_get_character_at_offset(text, offset);
1028 while (offset > 0) {
1029 prev = vte_terminal_accessible_get_character_at_offset(text, offset - 1);
1030 if (_vte_terminal_is_word_char(terminal, prev) &&
1031 !_vte_terminal_is_word_char(terminal, current)) {
1032 break;
1033 } else {
1034 offset--;
1035 current = prev;
1036 }
1037 }
1038 start = offset;
1039 /* If we're looking for the word end before this one,
1040 * keep searching by backing up to the previous word
1041 * character and then searching for the word-end
1042 * before that. */
1043 if (direction == direction_previous) {
1044 while (offset > 0) {
1045 prev = vte_terminal_accessible_get_character_at_offset(text, offset - 1);
1046 if (_vte_terminal_is_word_char(terminal, prev)) {
1047 offset--;
1048 } else {
1049 break;
1050 }
1051 }
1052 current = vte_terminal_accessible_get_character_at_offset(text, offset);
1053 while (offset > 0) {
1054 prev = vte_terminal_accessible_get_character_at_offset(text, offset - 1);
1055 if (_vte_terminal_is_word_char(terminal, prev) &&
1056 !_vte_terminal_is_word_char(terminal, current)) {
1057 break;
1058 } else {
1059 offset--;
1060 current = prev;
1061 }
1062 }
1063 start = offset;
1064 }
1065 /* If we're looking for the word end after this one,
1066 * search forward by scanning forward for the next
1067 * word character, then the next non-word character
1068 * after that. */
1069 if (direction == direction_next) {
1070 while (offset < (int) priv->snapshot_characters->len) {
1071 current = vte_terminal_accessible_get_character_at_offset(text, offset);
1072 if (!_vte_terminal_is_word_char(terminal, current)) {
1073 offset++;
1074 } else {
1075 break;
1076 }
1077 }
1078 while (offset < (int) priv->snapshot_characters->len) {
1079 current = vte_terminal_accessible_get_character_at_offset(text, offset);
1080 if (_vte_terminal_is_word_char(terminal, current)) {
1081 offset++;
1082 } else {
1083 break;
1084 }
1085 }
1086 start = offset;
1087 }
1088 /* Now find the next word end. */
1089 while (offset < (int) priv->snapshot_characters->len) {
1090 current = vte_terminal_accessible_get_character_at_offset(text, offset);
1091 if (!_vte_terminal_is_word_char(terminal, current)) {
1092 offset++;
1093 } else {
1094 break;
1095 }
1096 }
1097 while (offset < (int) priv->snapshot_characters->len) {
1098 current = vte_terminal_accessible_get_character_at_offset(text, offset);
1099 if (_vte_terminal_is_word_char(terminal, current)) {
1100 offset++;
1101 } else {
1102 break;
1103 }
1104 }
1105 end = offset;
1106 break;
1107 case ATK_TEXT_BOUNDARY_LINE_START:
1108 case ATK_TEXT_BOUNDARY_LINE_END:
1109 /* Figure out which line we're on. If the start of the
1110 * i'th line is before the offset, then i could be the
1111 * line we're looking for. */
1112 line = 0;
1113 for (line = 0;
1114 line < priv->snapshot_linebreaks->len;
1115 line++) {
1116 if (g_array_index(priv->snapshot_linebreaks,
1117 int, line) > offset) {
1118 line--;
1119 break;
1120 }
1121 }
1122 _vte_debug_print(VTE_DEBUG_ALLY,
1123 "Character %d is on line %d.\n",
1124 offset, line);
1125 /* Perturb the line number to handle before/at/after. */
1126 line += direction;
1127 line = MIN(line, priv->snapshot_linebreaks->len - 1);
1128 /* Read the offsets for this line. */
1129 start = g_array_index(priv->snapshot_linebreaks,
1130 int, line);
1131 line++;
1132 line = MIN(line, priv->snapshot_linebreaks->len - 1);
1133 end = g_array_index(priv->snapshot_linebreaks,
1134 int, line);
1135 _vte_debug_print(VTE_DEBUG_ALLY,
1136 "Line runs from %d to %d.\n",
1137 start, end);
1138 break;
1139 case ATK_TEXT_BOUNDARY_SENTENCE_START:
1140 case ATK_TEXT_BOUNDARY_SENTENCE_END:
1141 /* This doesn't make sense. Fall through. */
1142 default:
1143 start = end = 0;
1144 break;
1145 }
1146 *start_offset = start = MIN(start, priv->snapshot_characters->len - 1);
1147 *end_offset = end = CLAMP(end, start, priv->snapshot_characters->len);
1148 return vte_terminal_accessible_get_text(text, start, end);
1149 }
1150
1151 static gchar *
vte_terminal_accessible_get_text_before_offset(AtkText * text,gint offset,AtkTextBoundary boundary_type,gint * start_offset,gint * end_offset)1152 vte_terminal_accessible_get_text_before_offset(AtkText *text, gint offset,
1153 AtkTextBoundary boundary_type,
1154 gint *start_offset,
1155 gint *end_offset)
1156 {
1157 VteTerminalAccessible *accessible = VTE_TERMINAL_ACCESSIBLE(text);
1158
1159 vte_terminal_accessible_update_private_data_if_needed(accessible,
1160 NULL, NULL);
1161 return vte_terminal_accessible_get_text_somewhere(text,
1162 offset,
1163 boundary_type,
1164 -1,
1165 start_offset,
1166 end_offset);
1167 }
1168
1169 static gchar *
vte_terminal_accessible_get_text_after_offset(AtkText * text,gint offset,AtkTextBoundary boundary_type,gint * start_offset,gint * end_offset)1170 vte_terminal_accessible_get_text_after_offset(AtkText *text, gint offset,
1171 AtkTextBoundary boundary_type,
1172 gint *start_offset,
1173 gint *end_offset)
1174 {
1175 VteTerminalAccessible *accessible = VTE_TERMINAL_ACCESSIBLE(text);
1176
1177 vte_terminal_accessible_update_private_data_if_needed(accessible,
1178 NULL, NULL);
1179 return vte_terminal_accessible_get_text_somewhere(text,
1180 offset,
1181 boundary_type,
1182 1,
1183 start_offset,
1184 end_offset);
1185 }
1186
1187 static gchar *
vte_terminal_accessible_get_text_at_offset(AtkText * text,gint offset,AtkTextBoundary boundary_type,gint * start_offset,gint * end_offset)1188 vte_terminal_accessible_get_text_at_offset(AtkText *text, gint offset,
1189 AtkTextBoundary boundary_type,
1190 gint *start_offset,
1191 gint *end_offset)
1192 {
1193 VteTerminalAccessible *accessible = VTE_TERMINAL_ACCESSIBLE(text);
1194
1195 vte_terminal_accessible_update_private_data_if_needed(accessible,
1196 NULL, NULL);
1197 return vte_terminal_accessible_get_text_somewhere(text,
1198 offset,
1199 boundary_type,
1200 0,
1201 start_offset,
1202 end_offset);
1203 }
1204
1205 static gunichar
vte_terminal_accessible_get_character_at_offset(AtkText * text,gint offset)1206 vte_terminal_accessible_get_character_at_offset(AtkText *text, gint offset)
1207 {
1208 VteTerminalAccessible *accessible = VTE_TERMINAL_ACCESSIBLE(text);
1209 VteTerminalAccessiblePrivate *priv = _vte_terminal_accessible_get_instance_private(accessible);
1210 char *unichar;
1211 gunichar ret;
1212
1213 vte_terminal_accessible_update_private_data_if_needed(accessible,
1214 NULL, NULL);
1215
1216 g_assert(offset < (int) priv->snapshot_characters->len);
1217
1218 unichar = vte_terminal_accessible_get_text(text, offset, offset + 1);
1219 ret = g_utf8_get_char(unichar);
1220 g_free(unichar);
1221
1222 return ret;
1223 }
1224
1225 static gint
vte_terminal_accessible_get_caret_offset(AtkText * text)1226 vte_terminal_accessible_get_caret_offset(AtkText *text)
1227 {
1228 VteTerminalAccessible *accessible = VTE_TERMINAL_ACCESSIBLE(text);
1229 VteTerminalAccessiblePrivate *priv = _vte_terminal_accessible_get_instance_private(accessible);
1230
1231 vte_terminal_accessible_update_private_data_if_needed(accessible,
1232 NULL, NULL);
1233
1234 return priv->snapshot_caret;
1235 }
1236
1237 static AtkAttributeSet *
get_attribute_set(struct _VteCharAttributes attr)1238 get_attribute_set (struct _VteCharAttributes attr)
1239 {
1240 AtkAttributeSet *set = NULL;
1241 AtkAttribute *at;
1242
1243 if (attr.underline) {
1244 at = g_new (AtkAttribute, 1);
1245 at->name = g_strdup ("underline");
1246 at->value = g_strdup ("true");
1247 set = g_slist_append (set, at);
1248 }
1249 if (attr.strikethrough) {
1250 at = g_new (AtkAttribute, 1);
1251 at->name = g_strdup ("strikethrough");
1252 at->value = g_strdup ("true");
1253 set = g_slist_append (set, at);
1254 }
1255 at = g_new (AtkAttribute, 1);
1256 at->name = g_strdup ("fg-color");
1257 at->value = g_strdup_printf ("%u,%u,%u",
1258 attr.fore.red, attr.fore.green, attr.fore.blue);
1259 set = g_slist_append (set, at);
1260
1261 at = g_new (AtkAttribute, 1);
1262 at->name = g_strdup ("bg-color");
1263 at->value = g_strdup_printf ("%u,%u,%u",
1264 attr.back.red, attr.back.green, attr.back.blue);
1265 set = g_slist_append (set, at);
1266
1267 return set;
1268 }
1269
1270 static gboolean
_pango_color_equal(const PangoColor * a,const PangoColor * b)1271 _pango_color_equal(const PangoColor *a,
1272 const PangoColor *b)
1273 {
1274 return a->red == b->red &&
1275 a->green == b->green &&
1276 a->blue == b->blue;
1277 }
1278
1279 static AtkAttributeSet *
vte_terminal_accessible_get_run_attributes(AtkText * text,gint offset,gint * start_offset,gint * end_offset)1280 vte_terminal_accessible_get_run_attributes(AtkText *text, gint offset,
1281 gint *start_offset, gint *end_offset)
1282 {
1283 VteTerminalAccessible *accessible = VTE_TERMINAL_ACCESSIBLE(text);
1284 VteTerminalAccessiblePrivate *priv = _vte_terminal_accessible_get_instance_private(accessible);
1285 guint i;
1286 struct _VteCharAttributes cur_attr;
1287 struct _VteCharAttributes attr;
1288
1289 vte_terminal_accessible_update_private_data_if_needed(accessible,
1290 NULL, NULL);
1291
1292 attr = g_array_index (priv->snapshot_attributes,
1293 struct _VteCharAttributes,
1294 offset);
1295 *start_offset = 0;
1296 for (i = offset; i--;) {
1297 cur_attr = g_array_index (priv->snapshot_attributes,
1298 struct _VteCharAttributes,
1299 i);
1300 if (!_pango_color_equal (&cur_attr.fore, &attr.fore) ||
1301 !_pango_color_equal (&cur_attr.back, &attr.back) ||
1302 cur_attr.underline != attr.underline ||
1303 cur_attr.strikethrough != attr.strikethrough) {
1304 *start_offset = i + 1;
1305 break;
1306 }
1307 }
1308 *end_offset = priv->snapshot_attributes->len - 1;
1309 for (i = offset + 1; i < priv->snapshot_attributes->len; i++) {
1310 cur_attr = g_array_index (priv->snapshot_attributes,
1311 struct _VteCharAttributes,
1312 i);
1313 if (!_pango_color_equal (&cur_attr.fore, &attr.fore) ||
1314 !_pango_color_equal (&cur_attr.back, &attr.back) ||
1315 cur_attr.underline != attr.underline ||
1316 cur_attr.strikethrough != attr.strikethrough) {
1317 *end_offset = i - 1;
1318 break;
1319 }
1320 }
1321
1322 return get_attribute_set (attr);
1323 }
1324
1325 static AtkAttributeSet *
vte_terminal_accessible_get_default_attributes(AtkText * text)1326 vte_terminal_accessible_get_default_attributes(AtkText *text)
1327 {
1328 return NULL;
1329 }
1330
1331 static void
vte_terminal_accessible_get_character_extents(AtkText * text,gint offset,gint * x,gint * y,gint * width,gint * height,AtkCoordType coords)1332 vte_terminal_accessible_get_character_extents(AtkText *text, gint offset,
1333 gint *x, gint *y,
1334 gint *width, gint *height,
1335 AtkCoordType coords)
1336 {
1337 VteTerminalAccessible *accessible = VTE_TERMINAL_ACCESSIBLE(text);
1338 VteTerminalAccessiblePrivate *priv = _vte_terminal_accessible_get_instance_private(accessible);
1339 VteTerminal *terminal;
1340 glong char_width, char_height;
1341 gint base_x, base_y, w, h;
1342
1343 vte_terminal_accessible_update_private_data_if_needed(accessible,
1344 NULL, NULL);
1345 terminal = VTE_TERMINAL (gtk_accessible_get_widget (GTK_ACCESSIBLE (text)));
1346
1347 atk_component_get_extents (ATK_COMPONENT (text), &base_x, &base_y, &w, &h, coords);
1348 xy_from_offset (priv, offset, x, y);
1349 char_width = vte_terminal_get_char_width (terminal);
1350 char_height = vte_terminal_get_char_height (terminal);
1351 *x *= char_width;
1352 *y *= char_height;
1353 *width = char_width;
1354 *height = char_height;
1355 *x += base_x;
1356 *y += base_y;
1357 }
1358
1359 static gint
vte_terminal_accessible_get_character_count(AtkText * text)1360 vte_terminal_accessible_get_character_count(AtkText *text)
1361 {
1362 VteTerminalAccessible *accessible = VTE_TERMINAL_ACCESSIBLE(text);
1363 VteTerminalAccessiblePrivate *priv = _vte_terminal_accessible_get_instance_private(accessible);
1364
1365 vte_terminal_accessible_update_private_data_if_needed(accessible,
1366 NULL, NULL);
1367
1368 return priv->snapshot_attributes->len;
1369 }
1370
1371 static gint
vte_terminal_accessible_get_offset_at_point(AtkText * text,gint x,gint y,AtkCoordType coords)1372 vte_terminal_accessible_get_offset_at_point(AtkText *text,
1373 gint x, gint y,
1374 AtkCoordType coords)
1375 {
1376 VteTerminalAccessible *accessible = VTE_TERMINAL_ACCESSIBLE(text);
1377 VteTerminalAccessiblePrivate *priv = _vte_terminal_accessible_get_instance_private(accessible);
1378 VteTerminal *terminal;
1379 glong char_width, char_height;
1380 gint base_x, base_y, w, h;
1381
1382 vte_terminal_accessible_update_private_data_if_needed(accessible,
1383 NULL, NULL);
1384 terminal = VTE_TERMINAL (gtk_accessible_get_widget (GTK_ACCESSIBLE (text)));
1385
1386 atk_component_get_extents (ATK_COMPONENT (text), &base_x, &base_y, &w, &h, coords);
1387 /* FIXME: use _vte_terminal_xy_to_grid */
1388 char_width = vte_terminal_get_char_width (terminal);
1389 char_height = vte_terminal_get_char_height (terminal);
1390 x -= base_x;
1391 y -= base_y;
1392 x /= char_width;
1393 y /= char_height;
1394 return offset_from_xy (priv, x, y);
1395 }
1396
1397 static gint
vte_terminal_accessible_get_n_selections(AtkText * text)1398 vte_terminal_accessible_get_n_selections(AtkText *text)
1399 {
1400 VteTerminalAccessible *accessible = VTE_TERMINAL_ACCESSIBLE(text);
1401 GtkWidget *widget;
1402 VteTerminal *terminal;
1403
1404 vte_terminal_accessible_update_private_data_if_needed(accessible,
1405 NULL, NULL);
1406 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE(text));
1407 if (widget == NULL) {
1408 /* State is defunct */
1409 return -1;
1410 }
1411
1412 terminal = VTE_TERMINAL (widget);
1413 return (vte_terminal_get_has_selection (terminal)) ? 1 : 0;
1414 }
1415
1416 static gchar *
vte_terminal_accessible_get_selection(AtkText * text,gint selection_number,gint * start_offset,gint * end_offset)1417 vte_terminal_accessible_get_selection(AtkText *text, gint selection_number,
1418 gint *start_offset, gint *end_offset)
1419 {
1420 VteTerminalAccessible *accessible = VTE_TERMINAL_ACCESSIBLE(text);
1421 VteTerminalAccessiblePrivate *priv = _vte_terminal_accessible_get_instance_private(accessible);
1422 GtkWidget *widget;
1423 VteTerminal *terminal;
1424 long start_x, start_y, end_x, end_y;
1425
1426 vte_terminal_accessible_update_private_data_if_needed(accessible,
1427 NULL, NULL);
1428 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE(text));
1429 if (widget == NULL) {
1430 /* State is defunct */
1431 return NULL;
1432 }
1433
1434 terminal = VTE_TERMINAL (widget);
1435 if (!vte_terminal_get_has_selection (terminal)) {
1436 return NULL;
1437 }
1438 if (selection_number != 0) {
1439 return NULL;
1440 }
1441
1442 _vte_terminal_get_start_selection (terminal, &start_x, &start_y);
1443
1444 *start_offset = offset_from_xy (priv, start_x, start_y);
1445 _vte_terminal_get_end_selection (terminal, &end_x, &end_y);
1446 *end_offset = offset_from_xy (priv, end_x, end_y);
1447 return _vte_terminal_get_selection (terminal);
1448 }
1449
1450 static gboolean
vte_terminal_accessible_add_selection(AtkText * text,gint start_offset,gint end_offset)1451 vte_terminal_accessible_add_selection(AtkText *text,
1452 gint start_offset, gint end_offset)
1453 {
1454 VteTerminalAccessible *accessible = VTE_TERMINAL_ACCESSIBLE(text);
1455 VteTerminalAccessiblePrivate *priv = _vte_terminal_accessible_get_instance_private(accessible);
1456 GtkWidget *widget;
1457 VteTerminal *terminal;
1458 gint start_x, start_y, end_x, end_y;
1459
1460 vte_terminal_accessible_update_private_data_if_needed(accessible,
1461 NULL, NULL);
1462 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE(text));
1463 if (widget == NULL) {
1464 /* State is defunct */
1465 return FALSE;
1466 }
1467
1468 terminal = VTE_TERMINAL (widget);
1469 g_assert(!vte_terminal_get_has_selection (terminal));
1470
1471 xy_from_offset (priv, start_offset, &start_x, &start_y);
1472 xy_from_offset (priv, end_offset, &end_x, &end_y);
1473 _vte_terminal_select_text (terminal, start_x, start_y, end_x, end_y, start_offset, end_offset);
1474 return TRUE;
1475 }
1476
1477 static gboolean
vte_terminal_accessible_remove_selection(AtkText * text,gint selection_number)1478 vte_terminal_accessible_remove_selection(AtkText *text,
1479 gint selection_number)
1480 {
1481 VteTerminalAccessible *accessible = VTE_TERMINAL_ACCESSIBLE(text);
1482 GtkWidget *widget;
1483 VteTerminal *terminal;
1484
1485 vte_terminal_accessible_update_private_data_if_needed(accessible,
1486 NULL, NULL);
1487 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE(text));
1488 if (widget == NULL) {
1489 /* State is defunct */
1490 return FALSE;
1491 }
1492
1493 terminal = VTE_TERMINAL (widget);
1494 if (selection_number == 0 && vte_terminal_get_has_selection (terminal)) {
1495 _vte_terminal_remove_selection (terminal);
1496 return TRUE;
1497 } else {
1498 return FALSE;
1499 }
1500 }
1501
1502 static gboolean
vte_terminal_accessible_set_selection(AtkText * text,gint selection_number,gint start_offset,gint end_offset)1503 vte_terminal_accessible_set_selection(AtkText *text, gint selection_number,
1504 gint start_offset, gint end_offset)
1505 {
1506 VteTerminalAccessible *accessible = VTE_TERMINAL_ACCESSIBLE(text);
1507 GtkWidget *widget;
1508 VteTerminal *terminal;
1509
1510 vte_terminal_accessible_update_private_data_if_needed(accessible,
1511 NULL, NULL);
1512 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE(text));
1513 if (widget == NULL) {
1514 /* State is defunct */
1515 return FALSE;
1516 }
1517
1518 terminal = VTE_TERMINAL (widget);
1519 if (selection_number != 0) {
1520 return FALSE;
1521 }
1522 if (vte_terminal_get_has_selection (terminal)) {
1523 _vte_terminal_remove_selection (terminal);
1524 }
1525
1526 return vte_terminal_accessible_add_selection (text, start_offset, end_offset);
1527 }
1528
1529 static gboolean
vte_terminal_accessible_set_caret_offset(AtkText * text,gint offset)1530 vte_terminal_accessible_set_caret_offset(AtkText *text, gint offset)
1531 {
1532 VteTerminalAccessible *accessible = VTE_TERMINAL_ACCESSIBLE(text);
1533
1534 vte_terminal_accessible_update_private_data_if_needed(accessible,
1535 NULL, NULL);
1536 /* Whoa, very not allowed. */
1537 return FALSE;
1538 }
1539
1540 static void
vte_terminal_accessible_text_iface_init(AtkTextIface * text)1541 vte_terminal_accessible_text_iface_init(AtkTextIface *text)
1542 {
1543 text->get_text = vte_terminal_accessible_get_text;
1544 text->get_text_after_offset = vte_terminal_accessible_get_text_after_offset;
1545 text->get_text_at_offset = vte_terminal_accessible_get_text_at_offset;
1546 text->get_character_at_offset = vte_terminal_accessible_get_character_at_offset;
1547 text->get_text_before_offset = vte_terminal_accessible_get_text_before_offset;
1548 text->get_caret_offset = vte_terminal_accessible_get_caret_offset;
1549 text->get_run_attributes = vte_terminal_accessible_get_run_attributes;
1550 text->get_default_attributes = vte_terminal_accessible_get_default_attributes;
1551 text->get_character_extents = vte_terminal_accessible_get_character_extents;
1552 text->get_character_count = vte_terminal_accessible_get_character_count;
1553 text->get_offset_at_point = vte_terminal_accessible_get_offset_at_point;
1554 text->get_n_selections = vte_terminal_accessible_get_n_selections;
1555 text->get_selection = vte_terminal_accessible_get_selection;
1556 text->add_selection = vte_terminal_accessible_add_selection;
1557 text->remove_selection = vte_terminal_accessible_remove_selection;
1558 text->set_selection = vte_terminal_accessible_set_selection;
1559 text->set_caret_offset = vte_terminal_accessible_set_caret_offset;
1560 }
1561
1562 static gboolean
vte_terminal_accessible_set_extents(AtkComponent * component,gint x,gint y,gint width,gint height,AtkCoordType coord_type)1563 vte_terminal_accessible_set_extents(AtkComponent *component,
1564 gint x, gint y,
1565 gint width, gint height,
1566 AtkCoordType coord_type)
1567 {
1568 /* FIXME? We can change the size, but our position is controlled
1569 * by the parent container. */
1570 return FALSE;
1571 }
1572
1573 static gboolean
vte_terminal_accessible_set_position(AtkComponent * component,gint x,gint y,AtkCoordType coord_type)1574 vte_terminal_accessible_set_position(AtkComponent *component,
1575 gint x, gint y,
1576 AtkCoordType coord_type)
1577 {
1578 /* Controlled by the parent container, if there is one. */
1579 return FALSE;
1580 }
1581
1582 static gboolean
vte_terminal_accessible_set_size(AtkComponent * component,gint width,gint height)1583 vte_terminal_accessible_set_size(AtkComponent *component,
1584 gint width, gint height)
1585 {
1586 VteTerminalAccessible *accessible = VTE_TERMINAL_ACCESSIBLE(component);
1587 VteTerminal *terminal;
1588 long columns, rows;
1589 GtkWidget *widget;
1590
1591 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE(accessible));
1592 if (widget == NULL) {
1593 return FALSE;
1594 }
1595 terminal = VTE_TERMINAL(widget);
1596
1597 /* If the size is an exact multiple of the cell size, use that,
1598 * otherwise round down. */
1599 (void) _vte_terminal_size_to_grid_size(terminal, width, height, &columns, &rows);
1600
1601 vte_terminal_set_size(terminal, columns, rows);
1602 return (vte_terminal_get_row_count (terminal) == rows) &&
1603 (vte_terminal_get_column_count (terminal) == columns);
1604 }
1605
1606 static AtkObject *
vte_terminal_accessible_ref_accessible_at_point(AtkComponent * component,gint x,gint y,AtkCoordType coord_type)1607 vte_terminal_accessible_ref_accessible_at_point(AtkComponent *component,
1608 gint x, gint y,
1609 AtkCoordType coord_type)
1610 {
1611 /* There are no children. */
1612 return NULL;
1613 }
1614
1615 static void
vte_terminal_accessible_component_iface_init(AtkComponentIface * component)1616 vte_terminal_accessible_component_iface_init(AtkComponentIface *component)
1617 {
1618 component->ref_accessible_at_point = vte_terminal_accessible_ref_accessible_at_point;
1619 component->set_extents = vte_terminal_accessible_set_extents;
1620 component->set_position = vte_terminal_accessible_set_position;
1621 component->set_size = vte_terminal_accessible_set_size;
1622 }
1623
1624 /* AtkAction interface */
1625
1626 static gboolean
vte_terminal_accessible_do_action(AtkAction * accessible,int i)1627 vte_terminal_accessible_do_action (AtkAction *accessible, int i)
1628 {
1629 GtkWidget *widget;
1630 gboolean retval = FALSE;
1631
1632 g_return_val_if_fail (i < LAST_ACTION, FALSE);
1633
1634 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
1635 if (!widget) {
1636 return FALSE;
1637 }
1638
1639 switch (i) {
1640 case ACTION_MENU :
1641 g_signal_emit_by_name (widget, "popup_menu", &retval);
1642 break;
1643 default :
1644 g_warning ("Invalid action passed to VteTerminalAccessible::do_action");
1645 return FALSE;
1646 }
1647 return retval;
1648 }
1649
1650 static int
vte_terminal_accessible_get_n_actions(AtkAction * accessible)1651 vte_terminal_accessible_get_n_actions (AtkAction *accessible)
1652 {
1653 return LAST_ACTION;
1654 }
1655
1656 static const char *
vte_terminal_accessible_action_get_description(AtkAction * action,int i)1657 vte_terminal_accessible_action_get_description (AtkAction *action, int i)
1658 {
1659 VteTerminalAccessible *accessible = VTE_TERMINAL_ACCESSIBLE(action);
1660 VteTerminalAccessiblePrivate *priv = _vte_terminal_accessible_get_instance_private(accessible);
1661
1662 g_return_val_if_fail (i < LAST_ACTION, NULL);
1663
1664 if (priv->action_descriptions[i]) {
1665 return priv->action_descriptions[i];
1666 } else {
1667 return vte_terminal_accessible_action_descriptions[i];
1668 }
1669 }
1670
1671 static const char *
vte_terminal_accessible_action_get_name(AtkAction * accessible,int i)1672 vte_terminal_accessible_action_get_name (AtkAction *accessible, int i)
1673 {
1674 g_return_val_if_fail (i < LAST_ACTION, NULL);
1675
1676 return vte_terminal_accessible_action_names[i];
1677 }
1678
1679 static const char *
vte_terminal_accessible_action_get_keybinding(AtkAction * accessible,int i)1680 vte_terminal_accessible_action_get_keybinding (AtkAction *accessible, int i)
1681 {
1682 g_return_val_if_fail (i < LAST_ACTION, NULL);
1683
1684 return NULL;
1685 }
1686
1687 static gboolean
vte_terminal_accessible_action_set_description(AtkAction * action,int i,const char * description)1688 vte_terminal_accessible_action_set_description (AtkAction *action,
1689 int i,
1690 const char *description)
1691 {
1692 VteTerminalAccessible *accessible = VTE_TERMINAL_ACCESSIBLE(action);
1693 VteTerminalAccessiblePrivate *priv = _vte_terminal_accessible_get_instance_private(accessible);
1694
1695 g_return_val_if_fail (i < LAST_ACTION, FALSE);
1696
1697 if (priv->action_descriptions[i]) {
1698 g_free (priv->action_descriptions[i]);
1699 }
1700 priv->action_descriptions[i] = g_strdup (description);
1701
1702 return TRUE;
1703 }
1704
1705 static void
vte_terminal_accessible_action_iface_init(AtkActionIface * action)1706 vte_terminal_accessible_action_iface_init(AtkActionIface *action)
1707 {
1708 action->do_action = vte_terminal_accessible_do_action;
1709 action->get_n_actions = vte_terminal_accessible_get_n_actions;
1710 action->get_description = vte_terminal_accessible_action_get_description;
1711 action->get_name = vte_terminal_accessible_action_get_name;
1712 action->get_keybinding = vte_terminal_accessible_action_get_keybinding;
1713 action->set_description = vte_terminal_accessible_action_set_description;
1714 }
1715
1716 static void
_vte_terminal_accessible_class_init(VteTerminalAccessibleClass * klass)1717 _vte_terminal_accessible_class_init(VteTerminalAccessibleClass *klass)
1718 {
1719 GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
1720 AtkObjectClass *class = ATK_OBJECT_CLASS (klass);
1721
1722 gobject_class->finalize = vte_terminal_accessible_finalize;
1723
1724 class->initialize = vte_terminal_accessible_initialize;
1725 }
1726