vteaccess.c 52.6 KB
Newer Older
1
/*
2
 * Copyright (C) 2002,2003 Red Hat, Inc.
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
 *
 * This is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Library General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/* VTE accessibility object.  Based heavily on inspection of libzvt's
 * accessibility code. */

22
#ident "$Id$"
23

24
#include "../config.h"
25

26 27
#include <atk/atk.h>
#include <gtk/gtk.h>
28
#include <string.h>
29
#include "debug.h"
30 31
#include "vte.h"
#include "vteaccess.h"
32
#include "vteint.h"
33

34 35
#ifdef HAVE_LOCALE_H
#include <locale.h>
36 37 38 39
#endif

#ifdef ENABLE_NLS
#include <libintl.h>
40 41 42 43
#else
#define bindtextdomain(package,dir)
#endif

44
#define VTE_TERMINAL_ACCESSIBLE_PRIVATE_DATA "VteTerminalAccessiblePrivateData"
45

46
typedef struct _VteTerminalAccessiblePrivate {
47 48
	gboolean snapshot_contents_invalid;	/* This data is stale. */
	gboolean snapshot_caret_invalid;	/* This data is stale. */
49
	GString *snapshot_text;		/* Pointer to UTF-8 text. */
50 51 52 53
	GArray *snapshot_characters;	/* Offsets to character begin points. */
	GArray *snapshot_attributes;	/* Attributes, per byte. */
	GArray *snapshot_linebreaks;	/* Offsets to line breaks. */
	gint snapshot_caret;		/* Location of the cursor. */
54 55
} VteTerminalAccessiblePrivate;

56 57 58
enum direction {
	direction_previous = -1,
	direction_current = 0,
59
	direction_next = 1
60 61 62 63 64
};

static gunichar vte_terminal_accessible_get_character_at_offset(AtkText *text,
								gint offset);

65 66
static gpointer parent_class = NULL;

67
/* Create snapshot private data. */
68 69 70 71 72
static VteTerminalAccessiblePrivate *
vte_terminal_accessible_new_private_data(void)
{
	VteTerminalAccessiblePrivate *priv;
	priv = g_malloc0(sizeof(*priv));
73 74 75
	priv->snapshot_text = NULL;
	priv->snapshot_characters = NULL;
	priv->snapshot_attributes = NULL;
76
	priv->snapshot_linebreaks = NULL;
77 78 79
	priv->snapshot_caret = -1;
	priv->snapshot_contents_invalid = TRUE;
	priv->snapshot_caret_invalid = TRUE;
80 81 82
	return priv;
}

83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
/* Free snapshot private data. */
static void
vte_terminal_accessible_free_private_data(VteTerminalAccessiblePrivate *priv)
{
	g_return_if_fail(priv != NULL);
	if (priv->snapshot_text != NULL) {
		g_string_free(priv->snapshot_text, TRUE);
		priv->snapshot_text = NULL;
	}
	if (priv->snapshot_characters != NULL) {
		g_array_free(priv->snapshot_characters, TRUE);
		priv->snapshot_characters = NULL;
	}
	if (priv->snapshot_attributes != NULL) {
		g_array_free(priv->snapshot_attributes, TRUE);
		priv->snapshot_attributes = NULL;
	}
	if (priv->snapshot_linebreaks != NULL) {
		g_array_free(priv->snapshot_linebreaks, TRUE);
		priv->snapshot_linebreaks = NULL;
	}
	g_free(priv);
}

107
static gint
108
offset_from_xy (VteTerminalAccessiblePrivate *priv,
109 110 111 112 113 114 115 116 117 118 119 120
		gint x, gint y)
{
	gint offset;
	gint linebreak;
	gint next_linebreak;

	if (y >= priv->snapshot_linebreaks->len)
		y = priv->snapshot_linebreaks->len -1;

	linebreak = g_array_index (priv->snapshot_linebreaks, int, y);
	if (y +1 == priv->snapshot_linebreaks->len)
		next_linebreak = priv->snapshot_characters->len;
121
	else
122
		next_linebreak = g_array_index (priv->snapshot_linebreaks, int, y + 1);
123

124 125 126 127 128 129 130
	offset = linebreak + x;
	if (offset >= next_linebreak)
		offset = next_linebreak -1;
	return offset;
}

static void
131
xy_from_offset (VteTerminalAccessiblePrivate *priv,
132 133 134 135 136
		gint offset, gint *x, gint *y)
{
	gint i;
	gint linebreak;
	gint cur_x, cur_y;
137
	gint cur_offset = 0;
138 139 140 141 142 143 144

	cur_x = -1;
	cur_y = -1;
	for (i = 0; i < priv->snapshot_linebreaks->len; i++) {
		linebreak = g_array_index (priv->snapshot_linebreaks, int, i);
		if (offset < linebreak) {
			cur_x = offset - cur_offset;
145
			cur_y = i - 1;
146
			break;
147

148 149 150 151 152 153 154
		}  else {
			cur_offset = linebreak;
		}
	}
	if (i == priv->snapshot_linebreaks->len) {
		if (offset < priv->snapshot_characters->len) {
			cur_x = offset - cur_offset;
155
			cur_y = i - 1;
156 157 158
		}
	}
	*x = cur_x;
159
	*y = cur_y;
160 161
}

162
/* "Oh yeah, that's selected.  Sure." callback. */
163
static gboolean
164
all_selected(VteTerminal *terminal, glong column, glong row, gpointer data)
165 166 167 168
{
	return TRUE;
}

169
static void
170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189
emit_text_caret_moved(GObject *object, glong caret)
{
#ifdef VTE_DEBUG
	if (_vte_debug_on(VTE_DEBUG_SIGNALS)) {
		fprintf(stderr, "Accessibility peer emitting "
			"`text-caret-moved'.\n");
	}
#endif
	g_signal_emit_by_name(object, "text-caret-moved", caret);
}

static void
emit_text_changed_insert(GObject *object,
			 const char *text, glong offset, glong len)
{
	const char *p;
	glong start, count;
	if (len == 0) {
		return;
	}
190 191 192 193
	/* Convert the byte offsets to character offsets. */
	start = 0;
	p = text;
	while (p < text + offset) {
194
		start++;
195
		p = g_utf8_next_char(p);
196
	}
197 198 199
	count = 0;
	p = text + offset;
	while (p < text + offset + len) {
200
		count++;
201
		p = g_utf8_next_char(p);
202
	}
203 204 205 206 207 208 209 210 211
#ifdef VTE_DEBUG
	if (_vte_debug_on(VTE_DEBUG_SIGNALS)) {
		fprintf(stderr, "Accessibility peer emitting "
			"`text-changed::insert' (%ld, %ld) (%ld, %ld).\n",
			offset, len, start, count);
		fprintf(stderr, "Inserted text was `%.*s'.\n",
			(int) len, text + offset);
	}
#endif
212 213 214 215 216 217 218 219 220 221 222 223 224
	g_signal_emit_by_name(object, "text-changed::insert", start, count);
}

static void
emit_text_changed_delete(GObject *object,
			 const char *text, glong offset, glong len)
{
	const char *p;
	glong start, count;
	if (len == 0) {
		return;
	}
	/* Convert the byte offsets to characters. */
225 226 227
	start = 0;
	p = text;
	while (p < text + offset) {
228
		start++;
229
		p = g_utf8_next_char(p);
230
	}
231 232 233
	count = 0;
	p = text + offset;
	while (p < text + offset + len) {
234
		count++;
235
		p = g_utf8_next_char(p);
236
	}
237 238 239 240 241 242 243 244 245
#ifdef VTE_DEBUG
	if (_vte_debug_on(VTE_DEBUG_SIGNALS)) {
		fprintf(stderr, "Accessibility peer emitting "
			"`text-changed::delete' (%ld, %ld) (%ld, %ld).\n",
			offset, len, start, count);
		fprintf(stderr, "Deleted text was `%.*s'.\n",
			(int) len, text + offset);
	}
#endif
246 247 248 249 250
	g_signal_emit_by_name(object, "text-changed::delete", start, count);
}

static void
vte_terminal_accessible_update_private_data_if_needed(AtkObject *text,
251
						      char **old, glong *olen)
252 253 254
{
	VteTerminal *terminal;
	VteTerminalAccessiblePrivate *priv;
255
	struct _VteCharAttributes attrs;
256
	char *next, *tmp;
257
	long row, i, offset, caret;
258
	long ccol, crow;
259 260 261

	g_return_if_fail(VTE_IS_TERMINAL_ACCESSIBLE(text));

262
	/* Retrieve the private data structure.  It must already exist. */
263 264 265 266 267
	priv = g_object_get_data(G_OBJECT(text),
				 VTE_TERMINAL_ACCESSIBLE_PRIVATE_DATA);
	g_return_if_fail(priv != NULL);

	/* If nothing's changed, just return immediately. */
268 269
	if ((priv->snapshot_contents_invalid == FALSE) &&
	    (priv->snapshot_caret_invalid == FALSE)) {
270 271
		if (old) {
			if (priv->snapshot_text) {
272 273 274 275 276 277 278 279
				*old = g_malloc(priv->snapshot_text->len + 1);
				memcpy(*old,
				       priv->snapshot_text->str,
				       priv->snapshot_text->len);
				(*old)[priv->snapshot_text->len] = '\0';
				if (olen) {
					*olen = priv->snapshot_text->len;
				}
280 281
			} else {
				*old = g_strdup("");
282 283 284 285 286 287 288
				if (olen) {
					*olen = 0;
				}
			}
		} else {
			if (olen) {
				g_assert_not_reached();
289 290
			}
		}
291 292 293
		return;
	}

294
	/* Re-read the contents of the widget if the contents have changed. */
295
	terminal = VTE_TERMINAL((GTK_ACCESSIBLE(text))->widget);
296
	if (priv->snapshot_contents_invalid) {
297 298 299 300
		/* Free the outdated snapshot data, unless the caller
		 * wants it. */
		if (old) {
			if (priv->snapshot_text != NULL) {
301 302 303 304 305
				*old = priv->snapshot_text->str;
				if (olen) {
					*olen = priv->snapshot_text->len;
				}
				g_string_free(priv->snapshot_text, FALSE);
306 307
			} else {
				*old = g_strdup("");
308 309 310
				if (olen) {
					*olen = 0;
				}
311 312
			}
		} else {
313 314 315
			if (olen) {
				g_assert_not_reached();
			}
316 317 318
			if (priv->snapshot_text != NULL) {
				g_string_free(priv->snapshot_text, TRUE);
			}
319
		}
320
		priv->snapshot_text = NULL;
321

322 323 324 325 326 327 328
		/* Free the character offsets and allocate a new array to hold
		 * them. */
		if (priv->snapshot_characters != NULL) {
			g_array_free(priv->snapshot_characters, TRUE);
			priv->snapshot_characters = NULL;
		}
		priv->snapshot_characters = g_array_new(FALSE, TRUE, sizeof(int));
329

330 331 332 333 334 335 336
		/* Free the attribute lists and allocate a new array to hold
		 * them. */
		if (priv->snapshot_attributes != NULL) {
			g_array_free(priv->snapshot_attributes, TRUE);
			priv->snapshot_attributes = NULL;
		}
		priv->snapshot_attributes = g_array_new(FALSE, TRUE,
337
							sizeof(struct _VteCharAttributes));
338 339 340 341 342 343 344 345 346 347

		/* Free the linebreak offsets and allocate a new array to hold
		 * them. */
		if (priv->snapshot_linebreaks != NULL) {
			g_array_free(priv->snapshot_linebreaks, TRUE);
			priv->snapshot_linebreaks = NULL;
		}
		priv->snapshot_linebreaks = g_array_new(FALSE, TRUE, sizeof(int));

		/* Get a new view of the uber-label. */
348 349 350 351
		tmp = vte_terminal_get_text_include_trailing_spaces(terminal,
								    all_selected,
								    NULL,
								    priv->snapshot_attributes);
352
		if (tmp == NULL) {
353 354
			/* Aaargh!  We're screwed. */
			return;
355
		}
356 357 358
		priv->snapshot_text = g_string_new_len(tmp,
						       priv->snapshot_attributes->len);
		g_free(tmp);
359

360 361
		/* Get the offsets to the beginnings of each character. */
		i = 0;
362
		next = priv->snapshot_text->str;
363 364
		while (i < priv->snapshot_attributes->len) {
			g_array_append_val(priv->snapshot_characters, i);
365
			next = g_utf8_next_char(next);
366 367 368
			if (next == NULL) {
				break;
			} else {
369
				i = next - priv->snapshot_text->str;
370
			}
371
		}
372 373 374 375 376 377
		/* Find offsets for the beginning of lines. */
		for (i = 0, row = 0; i < priv->snapshot_characters->len; i++) {
			/* Get the attributes for the current cell. */
			offset = g_array_index(priv->snapshot_characters,
					       int, i);
			attrs = g_array_index(priv->snapshot_attributes,
378
					      struct _VteCharAttributes,
379 380 381 382 383 384
					      offset);
			/* If this character is on a row different from the row
			 * the character we looked at previously was on, then
			 * it's a new line and we need to keep track of where
			 * it is. */
			if ((i == 0) || (attrs.row != row)) {
385
#ifdef VTE_DEBUG
386
				if (_vte_debug_on(VTE_DEBUG_MISC)) {
387
					fprintf(stderr, "Row %d/%ld begins at "
388
						"%ld.\n",
389 390 391
						priv->snapshot_linebreaks->len,
						attrs.row, i);
				}
392
#endif
393
				g_array_append_val(priv->snapshot_linebreaks, i);
394 395
			}
			row = attrs.row;
396
		}
397 398 399 400
		/* Add the final line break. */
		g_array_append_val(priv->snapshot_linebreaks, i);
		/* We're finished updating this. */
		priv->snapshot_contents_invalid = FALSE;
401
	}
402

403 404
	/* Update the caret position. */
	vte_terminal_get_cursor_position(terminal, &ccol, &crow);
405 406 407 408 409
#ifdef VTE_DEBUG
	if (_vte_debug_on(VTE_DEBUG_MISC)) {
		fprintf(stderr, "Cursor at (%ld, " "%ld).\n", ccol, crow);
	}
#endif
410 411 412 413 414 415 416 417

	/* Get the offsets to the beginnings of each line. */
	caret = -1;
	for (i = 0; i < priv->snapshot_characters->len; i++) {
		/* Get the attributes for the current cell. */
		offset = g_array_index(priv->snapshot_characters,
				       int, i);
		attrs = g_array_index(priv->snapshot_attributes,
418
				      struct _VteCharAttributes,
419 420 421 422 423 424
				      offset);
		/* If this cell is "before" the cursor, move the
		 * caret to be "here". */
		if ((attrs.row < crow) ||
		    ((attrs.row == crow) && (attrs.column < ccol))) {
			caret = i + 1;
425
		}
426
	}
427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442

	/* If no cells are before the caret, then the caret must be
	 * at the end of the buffer. */
	if (caret == -1) {
		caret = priv->snapshot_characters->len;
	}

	/* Notify observers if the caret moved. */
	if (caret != priv->snapshot_caret) {
		priv->snapshot_caret = caret;
		emit_text_caret_moved(G_OBJECT(text), caret);
	}

	/* Done updating the caret position, whether we needed to or not. */
	priv->snapshot_caret_invalid = FALSE;

443
#ifdef VTE_DEBUG
444
	if (_vte_debug_on(VTE_DEBUG_MISC)) {
445
		fprintf(stderr, "Refreshed accessibility snapshot, "
446
			"%ld cells.\n", (long)priv->snapshot_attributes->len);
447
	}
448
#endif
449 450
}

451 452 453 454 455
/* A signal handler to catch "text-inserted/deleted/modified" signals. */
static void
vte_terminal_accessible_text_modified(VteTerminal *terminal, gpointer data)
{
	VteTerminalAccessiblePrivate *priv;
456 457
	char *old, *current;
	glong offset, olen, clen;
458 459 460 461 462 463 464 465 466

	g_return_if_fail(VTE_IS_TERMINAL_ACCESSIBLE(data));

	priv = g_object_get_data(G_OBJECT(data),
				 VTE_TERMINAL_ACCESSIBLE_PRIVATE_DATA);
	g_return_if_fail(priv != NULL);

	priv->snapshot_contents_invalid = TRUE;
	vte_terminal_accessible_update_private_data_if_needed(ATK_OBJECT(data),
467
							      &old, &olen);
468 469
	g_return_if_fail(old != NULL);

470 471
	current = priv->snapshot_text->str;
	clen = priv->snapshot_text->len;
472 473 474

	/* Find the offset where they don't match. */
	offset = 0;
475 476
	while ((offset < olen) && (offset < clen)) {
		if (old[offset] != current[offset]) {
477 478 479 480 481 482
			break;
		}
		offset++;
	}

	/* At least one of them had better have more data, right? */
483 484
	if ((offset < olen) || (offset < clen)) {
		/* Back up from both end points until we find the *last* point
485
		 * where they differed. */
486 487
		while ((olen > offset) && (clen > offset)) {
			if (old[olen - 1] != current[clen - 1]) {
488 489 490
				break;
			}
			olen--;
491
			clen--;
492 493 494
		}
		/* At least one of them has to have text the other
		 * doesn't. */
495 496
		g_assert((clen > offset) || (olen > offset));
		g_assert((clen >= 0) && (olen >= 0));
497 498
		/* Now emit a deleted signal for text that was in the old
		 * string but isn't in the new one... */
499 500 501 502 503 504
		if (olen > offset) {
			emit_text_changed_delete(G_OBJECT(data),
						 old,
						 offset,
						 olen - offset);
		}
505 506
		/* .. and an inserted signal for text that wasn't in the old
		 * string but is in the new one. */
507 508 509 510 511 512
		if (clen > offset) {
			emit_text_changed_insert(G_OBJECT(data),
						 current,
						 offset,
						 clen - offset);
		}
513 514 515 516 517 518 519 520 521 522 523 524
	}

	g_free(old);
}

/* A signal handler to catch "text-scrolled" signals. */
static void
vte_terminal_accessible_text_scrolled(VteTerminal *terminal,
				      gint howmuch,
				      gpointer data)
{
	VteTerminalAccessiblePrivate *priv;
525
	struct _VteCharAttributes attr;
526
	long i, len, delta;
527 528

	g_return_if_fail(VTE_IS_TERMINAL_ACCESSIBLE(data));
529
	g_return_if_fail(howmuch != 0);
530 531 532 533 534

	priv = g_object_get_data(G_OBJECT(data),
				 VTE_TERMINAL_ACCESSIBLE_PRIVATE_DATA);
	g_return_if_fail(priv != NULL);

535 536 537 538
	if (((howmuch < 0) && (howmuch <= -terminal->row_count)) ||
	    ((howmuch > 0) && (howmuch >= terminal->row_count))) {
		/* All of the text was removed. */
		if (priv->snapshot_text != NULL) {
539
			if (priv->snapshot_text->str != NULL) {
540
				emit_text_changed_delete(G_OBJECT(data),
541
							 priv->snapshot_text->str,
542
							 0,
543
							 priv->snapshot_text->len);
544 545 546 547
			}
		}
		priv->snapshot_contents_invalid = TRUE;
		vte_terminal_accessible_update_private_data_if_needed(ATK_OBJECT(data),
548
								      NULL,
549 550 551
								      NULL);
		/* All of the present text was added. */
		if (priv->snapshot_text != NULL) {
552
			if (priv->snapshot_text->str != NULL) {
553
				emit_text_changed_insert(G_OBJECT(data),
554
							 priv->snapshot_text->str,
555
							 0,
556
							 priv->snapshot_text->len);
557 558 559 560 561 562 563 564 565
			}
		}
		return;
	}
	/* Find the start point. */
	delta = 0;
	if (priv->snapshot_attributes != NULL) {
		if (priv->snapshot_attributes->len > 0) {
			attr = g_array_index(priv->snapshot_attributes,
566
					     struct _VteCharAttributes,
567 568
					     0);
			delta = attr.row;
569 570
		}
	}
571 572 573 574 575 576 577
	/* We scrolled up, so text was added at the top and removed
	 * from the bottom. */
	if ((howmuch < 0) && (howmuch > -terminal->row_count)) {
		howmuch = -howmuch;
		/* Find the first byte that scrolled off. */
		for (i = 0; i < priv->snapshot_attributes->len; i++) {
			attr = g_array_index(priv->snapshot_attributes,
578
					     struct _VteCharAttributes,
579 580 581 582 583 584 585 586
					     i);
			if (attr.row >= delta + terminal->row_count - howmuch) {
				break;
			}
		}
		if (i < priv->snapshot_attributes->len) {
			/* The rest of the string was deleted -- make a note. */
			emit_text_changed_delete(G_OBJECT(data),
587
						 priv->snapshot_text->str,
588 589 590 591 592 593 594
						 i,
						 priv->snapshot_attributes->len - i);
		}
		/* Refresh.  Note that i is now the length of the data which
		 * we expect to have left over. */
		priv->snapshot_contents_invalid = TRUE;
		vte_terminal_accessible_update_private_data_if_needed(ATK_OBJECT(data),
595
								      NULL,
596 597 598 599
								      NULL);
		/* If we now have more text than before, the initial portion
		 * was added. */
		if (priv->snapshot_text != NULL) {
600
			len = priv->snapshot_text->len;
601 602
			if (len > i) {
				emit_text_changed_insert(G_OBJECT(data),
603
							 priv->snapshot_text->str,
604 605 606
							 0,
							 len - i);
			}
607
		}
608
		return;
609
	}
610 611 612 613 614 615
	/* We scrolled down, so text was added at the bottom and removed
	 * from the top. */
	if ((howmuch > 0) && (howmuch < terminal->row_count)) {
		/* Find the first byte that wasn't scrolled off the top. */
		for (i = 0; i < priv->snapshot_attributes->len; i++) {
			attr = g_array_index(priv->snapshot_attributes,
616
					     struct _VteCharAttributes,
617 618 619 620 621 622 623
					     i);
			if (attr.row >= delta + howmuch) {
				break;
			}
		}
		/* That many bytes disappeared -- make a note. */
		emit_text_changed_delete(G_OBJECT(data),
624
					 priv->snapshot_text->str,
625 626 627
					 0,
					 i);
		/* Figure out how much text was left, and refresh. */
628
		i = strlen(priv->snapshot_text->str + i);
629 630
		priv->snapshot_contents_invalid = TRUE;
		vte_terminal_accessible_update_private_data_if_needed(ATK_OBJECT(data),
631
								      NULL,
632 633 634 635
								      NULL);
		/* Any newly-added string data is new, so note that it was
		 * inserted. */
		if (priv->snapshot_text != NULL) {
636
			len = priv->snapshot_text->len;
637 638
			if (len > i) {
				emit_text_changed_insert(G_OBJECT(data),
639
							 priv->snapshot_text->str,
640 641 642 643 644
							 i,
							 len - i);
			}
		}
		return;
645
	}
646
	g_assert_not_reached();
647 648 649 650 651 652 653 654 655 656 657 658 659 660 661
}

/* A signal handler to catch "cursor-moved" signals. */
static void
vte_terminal_accessible_invalidate_cursor(VteTerminal *terminal, gpointer data)
{
	VteTerminalAccessiblePrivate *priv;

	g_return_if_fail(VTE_IS_TERMINAL_ACCESSIBLE(data));

	priv = g_object_get_data(G_OBJECT(data),
				 VTE_TERMINAL_ACCESSIBLE_PRIVATE_DATA);
	g_return_if_fail(priv != NULL);

#ifdef VTE_DEBUG
662
	if (_vte_debug_on(VTE_DEBUG_MISC)) {
663 664 665 666
		fprintf(stderr, "Invalidating accessibility cursor.\n");
	}
#endif
	priv->snapshot_caret_invalid = TRUE;
667
	vte_terminal_accessible_update_private_data_if_needed(ATK_OBJECT(data),
668
							      NULL, NULL);
669 670
}

671
/* Handle title changes by resetting the description. */
672 673 674 675 676 677 678 679
static void
vte_terminal_accessible_title_changed(VteTerminal *terminal, gpointer data)
{
	g_return_if_fail(VTE_IS_TERMINAL_ACCESSIBLE(data));
	g_return_if_fail(VTE_IS_TERMINAL(terminal));
	atk_object_set_description(ATK_OBJECT(data), terminal->window_title);
}

680 681 682 683 684 685 686 687
/* Reflect focus-in events. */
static void
vte_terminal_accessible_focus_in(VteTerminal *terminal, GdkEventFocus *event,
				 gpointer data)
{
	g_return_if_fail(VTE_IS_TERMINAL_ACCESSIBLE(data));
	g_return_if_fail(VTE_IS_TERMINAL(terminal));
	g_signal_emit_by_name(data, "focus-event", TRUE);
688 689
	atk_object_notify_state_change(ATK_OBJECT(data),
				       ATK_STATE_FOCUSED, TRUE);
690 691 692 693 694 695 696 697 698 699
}

/* Reflect focus-out events. */
static void
vte_terminal_accessible_focus_out(VteTerminal *terminal, GdkEventFocus *event,
				  gpointer data)
{
	g_return_if_fail(VTE_IS_TERMINAL_ACCESSIBLE(data));
	g_return_if_fail(VTE_IS_TERMINAL(terminal));
	g_signal_emit_by_name(data, "focus-event", FALSE);
700
	atk_object_notify_state_change(ATK_OBJECT(data),
701
				       ATK_STATE_FOCUSED, FALSE);
702 703 704 705 706 707 708 709 710 711 712 713 714
}

/* Reflect visibility-notify events. */
static void
vte_terminal_accessible_visibility_notify(VteTerminal *terminal,
					  GdkEventVisibility *event,
					  gpointer data)
{
	GtkWidget *widget;
	gboolean visible;
	g_return_if_fail(VTE_IS_TERMINAL_ACCESSIBLE(data));
	g_return_if_fail(VTE_IS_TERMINAL(terminal));
	visible = event->state != GDK_VISIBILITY_FULLY_OBSCURED;
715
	/* The VISIBLE state indicates that this widget is "visible". */
716 717 718 719 720 721 722 723 724 725 726 727 728 729
	atk_object_notify_state_change(ATK_OBJECT(data),
				       ATK_STATE_VISIBLE,
				       visible);
	widget = GTK_WIDGET(terminal);
	while (visible) {
		if (gtk_widget_get_toplevel(widget) == widget) {
			break;
		}
		if (widget == NULL) {
			break;
		}
		visible = visible && (GTK_WIDGET_VISIBLE(widget));
		widget = gtk_widget_get_parent(widget);
	}
730 731
	/* The SHOWING state indicates that this widget, and all of its
	 * parents up to the toplevel, are "visible". */
732 733 734
	atk_object_notify_state_change(ATK_OBJECT(data),
				       ATK_STATE_SHOWING,
				       visible);
735 736
}

737 738
static void
vte_terminal_initialize (AtkObject *obj, gpointer data)
739
{
740
	VteTerminal *terminal;
741 742
	AtkObject *parent;

743
	ATK_OBJECT_CLASS (parent_class)->initialize (obj, data);
744

745
	terminal = VTE_TERMINAL (data);
746

747
	_vte_terminal_accessible_ref(terminal);
748

749
	g_object_set_data(G_OBJECT(obj),
750 751
			  VTE_TERMINAL_ACCESSIBLE_PRIVATE_DATA,
			  vte_terminal_accessible_new_private_data());
752 753 754

	g_signal_connect(G_OBJECT(terminal), "text-inserted",
			 GTK_SIGNAL_FUNC(vte_terminal_accessible_text_modified),
755
			 obj);
756 757
	g_signal_connect(G_OBJECT(terminal), "text-deleted",
			 GTK_SIGNAL_FUNC(vte_terminal_accessible_text_modified),
758
			 obj);
759 760
	g_signal_connect(G_OBJECT(terminal), "text-modified",
			 GTK_SIGNAL_FUNC(vte_terminal_accessible_text_modified),
761
			 obj);
762 763
	g_signal_connect(G_OBJECT(terminal), "text-scrolled",
			 GTK_SIGNAL_FUNC(vte_terminal_accessible_text_scrolled),
764
			 obj);
765
	g_signal_connect(G_OBJECT(terminal), "cursor-moved",
766
			 GTK_SIGNAL_FUNC(vte_terminal_accessible_invalidate_cursor),
767
			 obj);
Nalin Dahyabhai's avatar
Nalin Dahyabhai committed
768
	g_signal_connect(G_OBJECT(terminal), "window-title-changed",
769
			 GTK_SIGNAL_FUNC(vte_terminal_accessible_title_changed),
770
			 obj);
Nalin Dahyabhai's avatar
Nalin Dahyabhai committed
771
	g_signal_connect(G_OBJECT(terminal), "focus-in-event",
772
			 GTK_SIGNAL_FUNC(vte_terminal_accessible_focus_in),
773
			 obj);
Nalin Dahyabhai's avatar
Nalin Dahyabhai committed
774
	g_signal_connect(G_OBJECT(terminal), "focus-out-event",
775
			 GTK_SIGNAL_FUNC(vte_terminal_accessible_focus_out),
776
			 obj);
Nalin Dahyabhai's avatar
Nalin Dahyabhai committed
777
	g_signal_connect(G_OBJECT(terminal), "visibility-notify-event",
778
			 GTK_SIGNAL_FUNC(vte_terminal_accessible_visibility_notify),
779
			 obj);
780 781 782

	if (GTK_IS_WIDGET((GTK_WIDGET(terminal))->parent)) {
		parent = gtk_widget_get_accessible((GTK_WIDGET(terminal))->parent);
783
		if (ATK_IS_OBJECT(parent)) {
784
			atk_object_set_parent(obj, parent);
785
		}
786 787
	}

788 789
	atk_object_set_name(obj, "Terminal");
	atk_object_set_description(obj,
790 791 792
				   terminal->window_title ?
				   terminal->window_title :
				   "");
793

794
	atk_object_notify_state_change(obj,
795
				       ATK_STATE_FOCUSABLE, TRUE);
796
	atk_object_notify_state_change(obj,
797
				       ATK_STATE_EXPANDABLE, FALSE);
798
	atk_object_notify_state_change(obj,
799
				       ATK_STATE_RESIZABLE, TRUE);
800 801
	obj->role = ATK_ROLE_TERMINAL;
}
802

803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823
/**
 * vte_terminal_accessible_new:
 * @terminal: a #VteTerminal
 *
 * Creates a new accessibility peer for the terminal widget.
 *
 * Returns: the new #AtkObject
 */
AtkObject *
vte_terminal_accessible_new(VteTerminal *terminal)
{
	AtkObject *accessible;
	GObject *object;

	g_return_val_if_fail(VTE_IS_TERMINAL(terminal), NULL);

	object = g_object_new(VTE_TYPE_TERMINAL_ACCESSIBLE, NULL);
	accessible = ATK_OBJECT (object);
	atk_object_initialize(accessible, terminal);

	return accessible;
824 825
}

826 827 828
static void
vte_terminal_accessible_finalize(GObject *object)
{
829
	VteTerminalAccessiblePrivate *priv;
830
	GtkAccessible *accessible = NULL;
Nalin Dahyabhai's avatar
Nalin Dahyabhai committed
831
	GObjectClass *gobject_class;
832

833 834 835 836 837 838
#ifdef VTE_DEBUG
	if (_vte_debug_on(VTE_DEBUG_MISC)) {
		fprintf(stderr, "Finalizing accessible peer.\n");
	}
#endif

839 840 841 842
	g_return_if_fail(VTE_IS_TERMINAL_ACCESSIBLE(object));
	accessible = GTK_ACCESSIBLE(object);
	gobject_class = g_type_class_peek_parent(VTE_TERMINAL_ACCESSIBLE_GET_CLASS(object));

843 844 845 846 847 848 849 850 851
	if (accessible->widget != NULL) {
		g_object_remove_weak_pointer(G_OBJECT(accessible->widget),
					     (gpointer*) &accessible->widget);
	}
	if (G_IS_OBJECT(accessible->widget)) {
		g_signal_handlers_disconnect_matched(G_OBJECT(accessible->widget),
						     G_SIGNAL_MATCH_FUNC |
						     G_SIGNAL_MATCH_DATA,
						     0, 0, NULL,
852
						     (gpointer)vte_terminal_accessible_text_modified,
853 854 855 856 857
						     object);
		g_signal_handlers_disconnect_matched(G_OBJECT(accessible->widget),
						     G_SIGNAL_MATCH_FUNC |
						     G_SIGNAL_MATCH_DATA,
						     0, 0, NULL,
858
						     (gpointer)vte_terminal_accessible_text_scrolled,
859 860 861 862 863
						     object);
		g_signal_handlers_disconnect_matched(G_OBJECT(accessible->widget),
						     G_SIGNAL_MATCH_FUNC |
						     G_SIGNAL_MATCH_DATA,
						     0, 0, NULL,
864
						     (gpointer)vte_terminal_accessible_invalidate_cursor,
865 866 867 868 869 870 871
						     object);
		g_signal_handlers_disconnect_matched(G_OBJECT(accessible->widget),
						     G_SIGNAL_MATCH_FUNC |
						     G_SIGNAL_MATCH_DATA,
						     0, 0, NULL,
						     (gpointer)vte_terminal_accessible_title_changed,
						     object);
872 873 874 875 876 877 878 879 880 881 882 883
		g_signal_handlers_disconnect_matched(G_OBJECT(accessible->widget),
						     G_SIGNAL_MATCH_FUNC |
						     G_SIGNAL_MATCH_DATA,
						     0, 0, NULL,
						     (gpointer)vte_terminal_accessible_focus_in,
						     object);
		g_signal_handlers_disconnect_matched(G_OBJECT(accessible->widget),
						     G_SIGNAL_MATCH_FUNC |
						     G_SIGNAL_MATCH_DATA,
						     0, 0, NULL,
						     (gpointer)vte_terminal_accessible_focus_out,
						     object);
884 885 886 887 888 889
		g_signal_handlers_disconnect_matched(G_OBJECT(accessible->widget),
						     G_SIGNAL_MATCH_FUNC |
						     G_SIGNAL_MATCH_DATA,
						     0, 0, NULL,
						     (gpointer)vte_terminal_accessible_visibility_notify,
						     object);
890
	}
891 892 893 894 895 896 897 898
	priv = g_object_get_data(object,
				 VTE_TERMINAL_ACCESSIBLE_PRIVATE_DATA);
	if (priv != NULL) {
		vte_terminal_accessible_free_private_data(priv);
		g_object_set_data(object,
				  VTE_TERMINAL_ACCESSIBLE_PRIVATE_DATA,
				  NULL);
	}
899 900 901 902 903
	if (gobject_class->finalize != NULL) {
		gobject_class->finalize(object);
	}
}

904 905 906 907 908
static gchar *
vte_terminal_accessible_get_text(AtkText *text,
				 gint start_offset, gint end_offset)
{
	VteTerminalAccessiblePrivate *priv;
909
	int start, end;
910
	gchar *ret;
911

912 913
	g_return_val_if_fail((start_offset >= 0) && (end_offset >= -1),
			     g_strdup(""));
914

915
	vte_terminal_accessible_update_private_data_if_needed(ATK_OBJECT(text),
916
							      NULL, NULL);
917 918 919

	priv = g_object_get_data(G_OBJECT(text),
				 VTE_TERMINAL_ACCESSIBLE_PRIVATE_DATA);
920
#ifdef VTE_DEBUG
921
	if (_vte_debug_on(VTE_DEBUG_MISC)) {
922
		fprintf(stderr, "Getting text from %d to %d of %d.\n",
923 924
			start_offset, end_offset,
			priv->snapshot_characters->len);
925
	}
926
#endif
927
	g_return_val_if_fail(ATK_IS_TEXT(text), g_strdup(""));
928 929 930

	/* If the requested area is after all of the text, just return an
	 * empty string. */
931
	if (start_offset >= priv->snapshot_characters->len) {
932 933
		return g_strdup("");
	}
934

935 936
	/* Map the offsets to, er, offsets. */
	start = g_array_index(priv->snapshot_characters, int, start_offset);
937
	if ((end_offset == -1) || (end_offset >= priv->snapshot_characters->len) ) {
938
		/* Get everything up to the end of the buffer. */
939
		end = priv->snapshot_text->len;
940 941 942
	} else {
		/* Map the stopping point. */
		end = g_array_index(priv->snapshot_characters, int, end_offset);
943
	}
944 945 946 947
	ret = g_malloc(end - start + 1);
	memcpy(ret, priv->snapshot_text->str + start, end - start);
	ret[end - start] = '\0';
	return ret;
948 949
}

950 951 952 953
/* Map a subsection of the text with before/at/after char/word/line specs
 * into a run of Unicode characters.  (The interface is specifying characters,
 * not bytes, plus that saves us from having to deal with parts of multibyte
 * characters, which are icky.) */
954 955 956 957
static gchar *
vte_terminal_accessible_get_text_somewhere(AtkText *text,
					   gint offset,
					   AtkTextBoundary boundary_type,
958
					   enum direction direction,
959 960 961 962
					   gint *start_offset,
					   gint *end_offset)
{
	VteTerminalAccessiblePrivate *priv;
963 964 965
	VteTerminal *terminal;
	gunichar current, prev, next;
	int line;
966

967
	vte_terminal_accessible_update_private_data_if_needed(ATK_OBJECT(text),
968
							      NULL, NULL);
969 970 971

	priv = g_object_get_data(G_OBJECT(text),
				 VTE_TERMINAL_ACCESSIBLE_PRIVATE_DATA);
972
	terminal = VTE_TERMINAL((GTK_ACCESSIBLE(text))->widget);
973

974
#ifdef VTE_DEBUG
975
	if (_vte_debug_on(VTE_DEBUG_MISC)) {
976
		fprintf(stderr, "Getting %s %s at %d of %d.\n",
977 978
			(direction == direction_current) ? "this" :
			((direction == direction_next) ? "next" : "previous"),
979 980 981 982 983 984 985
			(boundary_type == ATK_TEXT_BOUNDARY_CHAR) ? "char" :
			((boundary_type == ATK_TEXT_BOUNDARY_LINE_START) ? "line (start)" :
			((boundary_type == ATK_TEXT_BOUNDARY_LINE_END) ? "line (end)" :
			((boundary_type == ATK_TEXT_BOUNDARY_WORD_START) ? "word (start)" :
			((boundary_type == ATK_TEXT_BOUNDARY_WORD_END) ? "word (end)" :
			((boundary_type == ATK_TEXT_BOUNDARY_SENTENCE_START) ? "sentence (start)" :
			((boundary_type == ATK_TEXT_BOUNDARY_SENTENCE_END) ? "sentence (end)" : "unknown")))))),
986
			offset, priv->snapshot_attributes->len);
987
	}
988
#endif
989
	g_return_val_if_fail(priv->snapshot_text != NULL, g_strdup(""));
990 991 992 993
	g_return_val_if_fail(priv->snapshot_characters != NULL, g_strdup(""));
	if (offset == priv->snapshot_characters->len) {
		return g_strdup("");
	}
994 995
	g_return_val_if_fail(offset < priv->snapshot_characters->len,
			     g_strdup(""));
996 997
	g_return_val_if_fail(offset >= 0, g_strdup(""));

998 999 1000 1001 1002 1003 1004
	switch (boundary_type) {
		case ATK_TEXT_BOUNDARY_CHAR:
			/* We're either looking at the character at this
			 * position, the one before it, or the one after it. */
			offset += direction;
			*start_offset = MAX(offset, 0);
			*end_offset = MIN(offset + 1,
1005
					  priv->snapshot_attributes->len);
1006 1007 1008
			break;
		case ATK_TEXT_BOUNDARY_WORD_START:
		case ATK_TEXT_BOUNDARY_WORD_END:
1009 1010 1011 1012 1013 1014
			/* Back up to the previous non-word-word transition. */
			while (offset > 0) {
				prev = vte_terminal_accessible_get_character_at_offset(text, offset - 1);
				if (vte_terminal_is_word_char(terminal, prev)) {
					offset--;
				} else {
1015 1016 1017
					break;
				}
			}
1018
			*start_offset = offset;
1019
			/* If we started in a word and we're looking for the
1020 1021 1022 1023 1024 1025 1026 1027 1028
			 * word before this one, keep searching by backing up
			 * to the previous non-word character and then searching
			 * for the word-start before that. */
			if (direction == direction_previous) {
				while (offset > 0) {
					prev = vte_terminal_accessible_get_character_at_offset(text, offset - 1);
					if (!vte_terminal_is_word_char(terminal, prev)) {
						offset--;
					} else {
1029 1030
						break;
					}
1031 1032 1033 1034 1035 1036
				}
				while (offset > 0) {
					prev = vte_terminal_accessible_get_character_at_offset(text, offset - 1);
					if (vte_terminal_is_word_char(terminal, prev)) {
						offset--;
					} else {
1037 1038 1039
						break;
					}
				}
1040
				*start_offset = offset;
1041 1042
			}
			/* If we're looking for the word after this one,
1043 1044 1045 1046 1047 1048 1049 1050 1051
			 * search forward by scanning forward for the next
			 * non-word character, then the next word character
			 * after that. */
			if (direction == direction_next) {
				while (offset < priv->snapshot_characters->len) {
					next = vte_terminal_accessible_get_character_at_offset(text, offset);
					if (vte_terminal_is_word_char(terminal, next)) {
						offset++;
					} else {
1052 1053
						break;
					}
1054 1055 1056 1057 1058 1059
				}
				while (offset < priv->snapshot_characters->len) {
					next = vte_terminal_accessible_get_character_at_offset(text, offset);
					if (!vte_terminal_is_word_char(terminal, next)) {
						offset++;
					} else {
1060 1061 1062
						break;
					}
				}
1063
				*start_offset = offset;
1064 1065
			}
			/* Now find the end of this word. */
1066 1067 1068 1069 1070
			while (offset < priv->snapshot_characters->len) {
				current = vte_terminal_accessible_get_character_at_offset(text, offset);
				if (vte_terminal_is_word_char(terminal, current)) {
					offset++;
				} else {
1071 1072
					break;
				}
1073

1074
			}
1075
			*end_offset = offset;
1076 1077 1078
			break;
		case ATK_TEXT_BOUNDARY_LINE_START:
		case ATK_TEXT_BOUNDARY_LINE_END:
1079 1080 1081
			/* Figure out which line we're on.  If the start of the
			 * i'th line is before the offset, then i could be the
			 * line we're looking for. */
1082
			line = 0;
1083 1084 1085
			for (line = 0;
			     line < priv->snapshot_linebreaks->len;
			     line++) {
1086
				if (g_array_index(priv->snapshot_linebreaks,
1087 1088
						  int, line) > offset) {
					line--;
1089 1090 1091
					break;
				}
			}
1092
#ifdef VTE_DEBUG
1093
			if (_vte_debug_on(VTE_DEBUG_MISC)) {
1094 1095 1096 1097
				fprintf(stderr, "Character %d is on line %d.\n",
					offset, line);
			}
#endif
1098 1099
			/* Perturb the line number to handle before/at/after. */
			line += direction;
1100 1101
			line = CLAMP(line,
				     0, priv->snapshot_linebreaks->len - 1);
1102
			/* Read the offsets for this line. */
1103 1104 1105 1106 1107
			*start_offset = g_array_index(priv->snapshot_linebreaks,
						      int, line);
			line++;
			line = CLAMP(line,
				     0, priv->snapshot_linebreaks->len - 1);
1108
			*end_offset = g_array_index(priv->snapshot_linebreaks,
1109 1110
						    int, line);
#ifdef VTE_DEBUG
1111
			if (_vte_debug_on(VTE_DEBUG_MISC)) {
1112 1113 1114 1115
				fprintf(stderr, "Line runs from %d to %d.\n",
					*start_offset, *end_offset);
			}
#endif
1116 1117 1118 1119 1120 1121 1122 1123
			break;
		case ATK_TEXT_BOUNDARY_SENTENCE_START:
		case ATK_TEXT_BOUNDARY_SENTENCE_END:
			/* This doesn't make sense.  Fall through. */
		default:
			*start_offset = *end_offset = 0;
			break;
	}
1124 1125 1126
	*start_offset = MIN(*start_offset, priv->snapshot_characters->len - 1);
	*end_offset = CLAMP(*end_offset, *start_offset,
			    priv->snapshot_characters->len);
1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138
	return vte_terminal_accessible_get_text(text,
						*start_offset,
						*end_offset);
}

static gchar *
vte_terminal_accessible_get_text_before_offset(AtkText *text, gint offset,
					       AtkTextBoundary boundary_type,
					       gint *start_offset,
					       gint *end_offset)
{
	g_return_val_if_fail(VTE_IS_TERMINAL_ACCESSIBLE(text), NULL);
1139
	vte_terminal_accessible_update_private_data_if_needed(ATK_OBJECT(text),
1140
							      NULL, NULL);
1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155
	return vte_terminal_accessible_get_text_somewhere(text,
							  offset,
							  boundary_type,
							  -1,
							  start_offset,
							  end_offset);
}

static gchar *
vte_terminal_accessible_get_text_after_offset(AtkText *text, gint offset,
					      AtkTextBoundary boundary_type,
					      gint *start_offset,
					      gint *end_offset)
{
	g_return_val_if_fail(VTE_IS_TERMINAL_ACCESSIBLE(text), NULL);
1156
	vte_terminal_accessible_update_private_data_if_needed(ATK_OBJECT(text),
1157
							      NULL, NULL);
1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172
	return vte_terminal_accessible_get_text_somewhere(text,
							  offset,
							  boundary_type,
							  1,
							  start_offset,
							  end_offset);
}

static gchar *
vte_terminal_accessible_get_text_at_offset(AtkText *text, gint offset,
					   AtkTextBoundary boundary_type,
					   gint *start_offset,
					   gint *end_offset)
{
	g_return_val_if_fail(VTE_IS_TERMINAL_ACCESSIBLE(text), NULL);
1173
	vte_terminal_accessible_update_private_data_if_needed(ATK_OBJECT(text),
1174
							      NULL, NULL);
1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186
	return vte_terminal_accessible_get_text_somewhere(text,
							  offset,
							  boundary_type,
							  0,
							  start_offset,
							  end_offset);
}

static gunichar
vte_terminal_accessible_get_character_at_offset(AtkText *text, gint offset)
{
	VteTerminalAccessiblePrivate *priv;
1187 1188 1189
	int mapped;
	char *unichar;
	gunichar ret;
1190

1191
	vte_terminal_accessible_update_private_data_if_needed(ATK_OBJECT(text),
1192
							      NULL, NULL);
1193 1194 1195 1196

	priv = g_object_get_data(G_OBJECT(text),
				 VTE_TERMINAL_ACCESSIBLE_PRIVATE_DATA);

1197 1198 1199 1200 1201 1202 1203 1204 1205
	g_return_val_if_fail(offset < priv->snapshot_characters->len, 0);

	mapped = g_array_index(priv->snapshot_characters, int, offset);

	unichar = vte_terminal_accessible_get_text(text, offset, offset + 1);
	ret = g_utf8_get_char(unichar);
	g_free(unichar);

	return ret;
1206 1207 1208 1209 1210 1211 1212
}

static gint
vte_terminal_accessible_get_caret_offset(AtkText *text)
{
	VteTerminalAccessiblePrivate *priv;

1213
	vte_terminal_accessible_update_private_data_if_needed(ATK_OBJECT(text),
1214
							      NULL, NULL);
1215 1216 1217 1218 1219 1220 1221

	priv = g_object_get_data(G_OBJECT(text),
				 VTE_TERMINAL_ACCESSIBLE_PRIVATE_DATA);

	return priv->snapshot_caret;
}

1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241
static AtkAttributeSet *
get_attribute_set (struct _VteCharAttributes attr)
{
	AtkAttributeSet *set = NULL;
	AtkAttribute *at;

	if (attr.underline) {
		at = g_new (AtkAttribute, 1);
		at->name = g_strdup ("underline");
		at->value = g_strdup ("true");
		set = g_slist_append (set, at);
	}
	if (attr.strikethrough) {
		at = g_new (AtkAttribute, 1);
		at->name = g_strdup ("strikethrough");
		at->value = g_strdup ("true");
		set = g_slist_append (set, at);
	}
	at = g_new (AtkAttribute, 1);
	at->name = g_strdup ("fg-color");
1242
	at->value = g_strdup_printf ("%u,%u,%u",
1243 1244 1245 1246 1247
				     attr.fore.red, attr.fore.green, attr.fore.blue);
	set = g_slist_append (set, at);

	at = g_new (AtkAttribute, 1);
	at->name = g_strdup ("bg-color");
1248
	at->value = g_strdup_printf ("%u,%u,%u",
1249 1250 1251 1252 1253
				     attr.back.red, attr.back.green, attr.back.blue);
	set = g_slist_append (set, at);

	return set;
}
1254

1255 1256 1257 1258
static AtkAttributeSet *
vte_terminal_accessible_get_run_attributes(AtkText *text, gint offset,
					   gint *start_offset, gint *end_offset)
{
1259 1260 1261 1262 1263
	VteTerminalAccessiblePrivate *priv;
	gint i;
	struct _VteCharAttributes cur_attr;
	struct _VteCharAttributes attr;

1264
	vte_terminal_accessible_update_private_data_if_needed(ATK_OBJECT(text),
1265
							      NULL, NULL);
1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281

	priv = g_object_get_data(G_OBJECT(text),
				 VTE_TERMINAL_ACCESSIBLE_PRIVATE_DATA);

	attr = g_array_index (priv->snapshot_attributes,
			      struct _VteCharAttributes,
			      offset);
	*start_offset = 0;
	for (i = offset - 1; i >= 0; i--) {
		cur_attr = g_array_index (priv->snapshot_attributes,
				      struct _VteCharAttributes,
				      i);
		if (!gdk_color_equal (&cur_attr.fore, &attr.fore) ||
		    !gdk_color_equal (&cur_attr.back, &attr.back) ||
		    cur_attr.underline != attr.underline ||
		    cur_attr.strikethrough != attr.strikethrough) {
1282
			*start_offset = i + 1;
1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298
			break;
		}
	}
	*end_offset = priv->snapshot_attributes->len - 1;
	for (i = offset + 1; i < priv->snapshot_attributes->len; i++) {
		cur_attr = g_array_index (priv->snapshot_attributes,
				      struct _VteCharAttributes,
				      i);
		if (!gdk_color_equal (&cur_attr.fore, &attr.fore) ||
		    !gdk_color_equal (&cur_attr.back, &attr.back) ||
		    cur_attr.underline != attr.underline ||
		    cur_attr.strikethrough != attr.strikethrough) {
			*end_offset = i - 1;
			break;
		}
	}
1299

1300
	return get_attribute_set (attr);
1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314
}

static AtkAttributeSet *
vte_terminal_accessible_get_default_attributes(AtkText *text)
{
	return NULL;
}

static void
vte_terminal_accessible_get_character_extents(AtkText *text, gint offset,
					      gint *x, gint *y,
					      gint *width, gint *height,
					      AtkCoordType coords)
{
1315 1316 1317 1318 1319
	VteTerminalAccessiblePrivate *priv;
	VteTerminal *terminal;
	glong char_width, char_height;
	gint base_x, base_y;

1320
	g_return_if_fail(VTE_IS_TERMINAL_ACCESSIBLE(text));
1321

1322
	vte_terminal_accessible_update_private_data_if_needed(ATK_OBJECT(text),
1323
							      NULL, NULL);
1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337
	priv = g_object_get_data(G_OBJECT(text),
				 VTE_TERMINAL_ACCESSIBLE_PRIVATE_DATA);
	terminal = VTE_TERMINAL (GTK_ACCESSIBLE (text)->widget);

	atk_component_get_position (ATK_COMPONENT (text), &base_x, &base_y, coords);
	xy_from_offset (priv, offset, x, y);
	char_width = vte_terminal_get_char_width (terminal);
	char_height = vte_terminal_get_char_height (terminal);
	*x *= char_width;
	*y *= char_height;
	*width = char_width;
	*height = char_height;
	*x += base_x;
	*y += base_y;
1338 1339 1340 1341 1342 1343 1344
}

static gint
vte_terminal_accessible_get_character_count(AtkText *text)
{
	VteTerminalAccessiblePrivate *priv;

1345
	vte_terminal_accessible_update_private_data_if_needed(ATK_OBJECT(text),
1346
							      NULL, NULL);
1347 1348 1349 1350

	priv = g_object_get_data(G_OBJECT(text),
				 VTE_TERMINAL_ACCESSIBLE_PRIVATE_DATA);

1351
	return priv->snapshot_attributes->len;
1352 1353 1354 1355 1356 1357 1358
}

static gint
vte_terminal_accessible_get_offset_at_point(AtkText *text,
					    gint x, gint y,
					    AtkCoordType coords)
{
1359 1360 1361 1362 1363 1364 1365
	VteTerminalAccessiblePrivate *priv;
	VteTerminal *terminal;
	glong char_width, char_height;
	gint base_x, base_y;

	g_return_val_if_fail(VTE_IS_TERMINAL_ACCESSIBLE(text), -1);

1366
	vte_terminal_accessible_update_private_data_if_needed(ATK_OBJECT(text),
1367
							      NULL, NULL);
1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379
	priv = g_object_get_data(G_OBJECT(text),
				 VTE_TERMINAL_ACCESSIBLE_PRIVATE_DATA);
	terminal = VTE_TERMINAL (GTK_ACCESSIBLE (text)->widget);

	atk_component_get_position (ATK_COMPONENT (text), &base_x, &base_y, coords);
	char_width = vte_terminal_get_char_width (terminal);
	char_height = vte_terminal_get_char_height (terminal);
	x -= base_x;
	y -= base_y;
	x /= char_width;
	y /= char_height;
	return offset_from_xy (priv, x, y);
1380 1381 1382 1383 1384 1385
}

static gint
vte_terminal_accessible_get_n_selections(AtkText *text)
{
	g_return_val_if_fail(VTE_IS_TERMINAL_ACCESSIBLE(text), 0);
1386
	vte_terminal_accessible_update_private_data_if_needed(ATK_OBJECT(text),