scintilla: Prevent running signal handlers on a destroyed a11y object
[geany-mirror.git] / scintilla / gtk / ScintillaGTKAccessible.cxx
blobff30141efe938ced14ea6e16981b6009d8fb3b14
1 /* Scintilla source code edit control */
2 /* ScintillaGTKAccessible.c - GTK+ accessibility for ScintillaGTK */
3 /* Copyright 2016 by Colomban Wendling <colomban@geany.org>
4 * The License.txt file describes the conditions under which this software may be distributed. */
6 // REFERENCES BETWEEN THE DIFFERENT OBJECTS
7 //
8 // ScintillaGTKAccessible is the actual implementation, as a C++ class.
9 // ScintillaObjectAccessible is the GObject derived from AtkObject that
10 // implements the various ATK interfaces, through ScintillaGTKAccessible.
11 // This follows the same pattern as ScintillaGTK and ScintillaObject.
13 // ScintillaGTK owns a strong reference to the ScintillaObjectAccessible, and
14 // is both responsible for creating and destroying that object.
16 // ScintillaObjectAccessible owns a strong reference to ScintillaGTKAccessible,
17 // and is responsible for creating and destroying that object.
19 // ScintillaGTKAccessible has weak references to both the ScintillaGTK and
20 // the ScintillaObjectAccessible objects associated, but does not own any
21 // strong references to those objects.
23 // The chain of ownership is as follows:
24 // ScintillaGTK -> ScintillaObjectAccessible -> ScintillaGTKAccessible
26 // DETAILS ON THE GOBJECT TYPE IMPLEMENTATION
28 // On GTK < 3.2, we need to use the AtkObjectFactory. We need to query
29 // the factory to see what type we should derive from, thus making use of
30 // dynamic inheritance. It's tricky, but it works so long as it's done
31 // carefully enough.
33 // On GTK 3.2 through 3.6, we need to hack around because GTK stopped
34 // registering its accessible types in the factory, so we can't query
35 // them that way. Unfortunately, the accessible types aren't exposed
36 // yet (not until 3.8), so there's no proper way to know which type to
37 // inherit from. To work around this, we instantiate the parent's
38 // AtkObject temporarily, and use it's type. It means creating an extra
39 // throwaway object and being able to pass the type information up to the
40 // type registration code, but it's the only solution I could find.
42 // On GTK 3.8 onward, we use the proper exposed GtkContainerAccessible as
43 // parent, and so a straightforward class.
45 // To hide and contain the complexity in type creation arising from the
46 // hackish support for GTK 3.2 to 3.8, the actual implementation for the
47 // widget's get_accessible() is located in the accessibility layer itself.
49 // Initially based on GtkTextViewAccessible from GTK 3.20
50 // Inspiration for the GTK < 3.2 part comes from Evince 2.24, thanks.
52 // FIXME: optimize character/byte offset conversion (with a cache?)
54 #include <stdlib.h>
55 #include <string.h>
57 #include <stdexcept>
58 #include <new>
59 #include <string>
60 #include <vector>
61 #include <map>
62 #include <algorithm>
64 #include <glib.h>
65 #include <gtk/gtk.h>
67 // whether we have widget_set() and widget_unset()
68 #define HAVE_WIDGET_SET_UNSET (GTK_CHECK_VERSION(3, 3, 6))
69 // whether GTK accessibility is available through the ATK factory
70 #define HAVE_GTK_FACTORY (! GTK_CHECK_VERSION(3, 1, 9))
71 // whether we have gtk-a11y.h and the public GTK accessible types
72 #define HAVE_GTK_A11Y_H (GTK_CHECK_VERSION(3, 7, 6))
74 #if HAVE_GTK_A11Y_H
75 # include <gtk/gtk-a11y.h>
76 #endif
78 #if defined(__WIN32__) || defined(_MSC_VER)
79 #include <windows.h>
80 #endif
82 // ScintillaGTK.h and stuff it needs
83 #include "Platform.h"
85 #include "ILexer.h"
86 #include "Scintilla.h"
87 #include "ScintillaWidget.h"
88 #ifdef SCI_LEXER
89 #include "SciLexer.h"
90 #endif
91 #include "StringCopy.h"
92 #ifdef SCI_LEXER
93 #include "LexerModule.h"
94 #endif
95 #include "Position.h"
96 #include "SplitVector.h"
97 #include "Partitioning.h"
98 #include "RunStyles.h"
99 #include "ContractionState.h"
100 #include "CellBuffer.h"
101 #include "CallTip.h"
102 #include "KeyMap.h"
103 #include "Indicator.h"
104 #include "XPM.h"
105 #include "LineMarker.h"
106 #include "Style.h"
107 #include "ViewStyle.h"
108 #include "CharClassify.h"
109 #include "Decoration.h"
110 #include "CaseFolder.h"
111 #include "Document.h"
112 #include "CaseConvert.h"
113 #include "UniConversion.h"
114 #include "UnicodeFromUTF8.h"
115 #include "Selection.h"
116 #include "PositionCache.h"
117 #include "EditModel.h"
118 #include "MarginView.h"
119 #include "EditView.h"
120 #include "Editor.h"
121 #include "AutoComplete.h"
122 #include "ScintillaBase.h"
124 #include "ScintillaGTK.h"
125 #include "ScintillaGTKAccessible.h"
127 #ifdef SCI_NAMESPACE
128 using namespace Scintilla;
129 #endif
131 struct ScintillaObjectAccessiblePrivate {
132 ScintillaGTKAccessible *pscin;
135 typedef GtkAccessible ScintillaObjectAccessible;
136 typedef GtkAccessibleClass ScintillaObjectAccessibleClass;
138 #define SCINTILLA_OBJECT_ACCESSIBLE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), SCINTILLA_TYPE_OBJECT_ACCESSIBLE, ScintillaObjectAccessible))
139 #define SCINTILLA_TYPE_OBJECT_ACCESSIBLE (scintilla_object_accessible_get_type(0))
141 // We can't use priv member because of dynamic inheritance, so we don't actually know the offset. Meh.
142 #define SCINTILLA_OBJECT_ACCESSIBLE_GET_PRIVATE(inst) (G_TYPE_INSTANCE_GET_PRIVATE((inst), SCINTILLA_TYPE_OBJECT_ACCESSIBLE, ScintillaObjectAccessiblePrivate))
144 static GType scintilla_object_accessible_get_type(GType parent_type);
146 ScintillaGTKAccessible *ScintillaGTKAccessible::FromAccessible(GtkAccessible *accessible) {
147 // FIXME: do we need the check below? GTK checks that in all methods, so maybe
148 GtkWidget *widget = gtk_accessible_get_widget(accessible);
149 if (! widget) {
150 return 0;
153 return SCINTILLA_OBJECT_ACCESSIBLE_GET_PRIVATE(accessible)->pscin;
156 ScintillaGTKAccessible::ScintillaGTKAccessible(GtkAccessible *accessible_, GtkWidget *widget_) :
157 accessible(accessible_),
158 sci(ScintillaGTK::FromWidget(widget_)),
159 deletionLengthChar(0),
160 old_pos(-1) {
161 g_signal_connect(widget_, "sci-notify", G_CALLBACK(SciNotify), this);
164 ScintillaGTKAccessible::~ScintillaGTKAccessible() {
165 g_signal_handlers_disconnect_by_func (sci->sci, reinterpret_cast<gpointer>(SciNotify), this);
168 gchar *ScintillaGTKAccessible::GetTextRangeUTF8(Position startByte, Position endByte) {
169 g_return_val_if_fail(startByte >= 0, NULL);
170 // FIXME: should we swap start/end if necessary?
171 g_return_val_if_fail(endByte >= startByte, NULL);
173 gchar *utf8Text = NULL;
174 const char *charSetBuffer;
176 // like TargetAsUTF8, but avoids a double conversion
177 if (sci->IsUnicodeMode() || ! *(charSetBuffer = sci->CharacterSetID())) {
178 int len = endByte - startByte;
179 utf8Text = (char *) g_malloc(len + 1);
180 sci->pdoc->GetCharRange(utf8Text, startByte, len);
181 utf8Text[len] = '\0';
182 } else {
183 // Need to convert
184 std::string s = sci->RangeText(startByte, endByte);
185 std::string tmputf = ConvertText(&s[0], s.length(), "UTF-8", charSetBuffer, false);
186 size_t len = tmputf.length();
187 utf8Text = (char *) g_malloc(len + 1);
188 memcpy(utf8Text, tmputf.c_str(), len);
189 utf8Text[len] = '\0';
192 return utf8Text;
195 gchar *ScintillaGTKAccessible::GetText(int startChar, int endChar) {
196 Position startByte, endByte;
197 if (endChar == -1) {
198 startByte = ByteOffsetFromCharacterOffset(startChar);
199 endByte = sci->pdoc->Length();
200 } else {
201 ByteRangeFromCharacterRange(startChar, endChar, startByte, endByte);
203 return GetTextRangeUTF8(startByte, endByte);
206 gchar *ScintillaGTKAccessible::GetTextAfterOffset(int charOffset,
207 AtkTextBoundary boundaryType, int *startChar, int *endChar) {
208 g_return_val_if_fail(charOffset >= 0, NULL);
210 Position startByte, endByte;
211 Position byteOffset = ByteOffsetFromCharacterOffset(charOffset);
213 switch (boundaryType) {
214 case ATK_TEXT_BOUNDARY_CHAR:
215 startByte = PositionAfter(byteOffset);
216 endByte = PositionAfter(startByte);
217 // FIXME: optimize conversion back, as we can reasonably assume +1 char?
218 break;
220 case ATK_TEXT_BOUNDARY_WORD_START:
221 startByte = sci->WndProc(SCI_WORDENDPOSITION, byteOffset, 1);
222 startByte = sci->WndProc(SCI_WORDENDPOSITION, startByte, 0);
223 endByte = sci->WndProc(SCI_WORDENDPOSITION, startByte, 1);
224 endByte = sci->WndProc(SCI_WORDENDPOSITION, endByte, 0);
225 break;
227 case ATK_TEXT_BOUNDARY_WORD_END:
228 startByte = sci->WndProc(SCI_WORDENDPOSITION, byteOffset, 0);
229 startByte = sci->WndProc(SCI_WORDENDPOSITION, startByte, 1);
230 endByte = sci->WndProc(SCI_WORDENDPOSITION, startByte, 0);
231 endByte = sci->WndProc(SCI_WORDENDPOSITION, endByte, 1);
232 break;
234 case ATK_TEXT_BOUNDARY_LINE_START: {
235 int line = sci->WndProc(SCI_LINEFROMPOSITION, byteOffset, 0);
236 startByte = sci->WndProc(SCI_POSITIONFROMLINE, line + 1, 0);
237 endByte = sci->WndProc(SCI_POSITIONFROMLINE, line + 2, 0);
238 break;
241 case ATK_TEXT_BOUNDARY_LINE_END: {
242 int line = sci->WndProc(SCI_LINEFROMPOSITION, byteOffset, 0);
243 startByte = sci->WndProc(SCI_GETLINEENDPOSITION, line, 0);
244 endByte = sci->WndProc(SCI_GETLINEENDPOSITION, line + 1, 0);
245 break;
248 default:
249 *startChar = *endChar = -1;
250 return NULL;
253 CharacterRangeFromByteRange(startByte, endByte, startChar, endChar);
254 return GetTextRangeUTF8(startByte, endByte);
257 gchar *ScintillaGTKAccessible::GetTextBeforeOffset(int charOffset,
258 AtkTextBoundary boundaryType, int *startChar, int *endChar) {
259 g_return_val_if_fail(charOffset >= 0, NULL);
261 Position startByte, endByte;
262 Position byteOffset = ByteOffsetFromCharacterOffset(charOffset);
264 switch (boundaryType) {
265 case ATK_TEXT_BOUNDARY_CHAR:
266 endByte = PositionBefore(byteOffset);
267 startByte = PositionBefore(endByte);
268 break;
270 case ATK_TEXT_BOUNDARY_WORD_START:
271 endByte = sci->WndProc(SCI_WORDSTARTPOSITION, byteOffset, 0);
272 endByte = sci->WndProc(SCI_WORDSTARTPOSITION, endByte, 1);
273 startByte = sci->WndProc(SCI_WORDSTARTPOSITION, endByte, 0);
274 startByte = sci->WndProc(SCI_WORDSTARTPOSITION, startByte, 1);
275 break;
277 case ATK_TEXT_BOUNDARY_WORD_END:
278 endByte = sci->WndProc(SCI_WORDSTARTPOSITION, byteOffset, 1);
279 endByte = sci->WndProc(SCI_WORDSTARTPOSITION, endByte, 0);
280 startByte = sci->WndProc(SCI_WORDSTARTPOSITION, endByte, 1);
281 startByte = sci->WndProc(SCI_WORDSTARTPOSITION, startByte, 0);
282 break;
284 case ATK_TEXT_BOUNDARY_LINE_START: {
285 int line = sci->WndProc(SCI_LINEFROMPOSITION, byteOffset, 0);
286 endByte = sci->WndProc(SCI_POSITIONFROMLINE, line, 0);
287 if (line > 0) {
288 startByte = sci->WndProc(SCI_POSITIONFROMLINE, line - 1, 0);
289 } else {
290 startByte = endByte;
292 break;
295 case ATK_TEXT_BOUNDARY_LINE_END: {
296 int line = sci->WndProc(SCI_LINEFROMPOSITION, byteOffset, 0);
297 if (line > 0) {
298 endByte = sci->WndProc(SCI_GETLINEENDPOSITION, line - 1, 0);
299 } else {
300 endByte = 0;
302 if (line > 1) {
303 startByte = sci->WndProc(SCI_GETLINEENDPOSITION, line - 2, 0);
304 } else {
305 startByte = endByte;
307 break;
310 default:
311 *startChar = *endChar = -1;
312 return NULL;
315 CharacterRangeFromByteRange(startByte, endByte, startChar, endChar);
316 return GetTextRangeUTF8(startByte, endByte);
319 gchar *ScintillaGTKAccessible::GetTextAtOffset(int charOffset,
320 AtkTextBoundary boundaryType, int *startChar, int *endChar) {
321 g_return_val_if_fail(charOffset >= 0, NULL);
323 Position startByte, endByte;
324 Position byteOffset = ByteOffsetFromCharacterOffset(charOffset);
326 switch (boundaryType) {
327 case ATK_TEXT_BOUNDARY_CHAR:
328 startByte = byteOffset;
329 endByte = sci->WndProc(SCI_POSITIONAFTER, byteOffset, 0);
330 break;
332 case ATK_TEXT_BOUNDARY_WORD_START:
333 startByte = sci->WndProc(SCI_WORDSTARTPOSITION, byteOffset, 1);
334 endByte = sci->WndProc(SCI_WORDENDPOSITION, byteOffset, 1);
335 if (! sci->WndProc(SCI_ISRANGEWORD, startByte, endByte)) {
336 // if the cursor was not on a word, forward back
337 startByte = sci->WndProc(SCI_WORDSTARTPOSITION, startByte, 0);
338 startByte = sci->WndProc(SCI_WORDSTARTPOSITION, startByte, 1);
340 endByte = sci->WndProc(SCI_WORDENDPOSITION, endByte, 0);
341 break;
343 case ATK_TEXT_BOUNDARY_WORD_END:
344 startByte = sci->WndProc(SCI_WORDSTARTPOSITION, byteOffset, 1);
345 endByte = sci->WndProc(SCI_WORDENDPOSITION, byteOffset, 1);
346 if (! sci->WndProc(SCI_ISRANGEWORD, startByte, endByte)) {
347 // if the cursor was not on a word, forward back
348 endByte = sci->WndProc(SCI_WORDENDPOSITION, endByte, 0);
349 endByte = sci->WndProc(SCI_WORDENDPOSITION, endByte, 1);
351 startByte = sci->WndProc(SCI_WORDSTARTPOSITION, startByte, 0);
352 break;
354 case ATK_TEXT_BOUNDARY_LINE_START: {
355 int line = sci->WndProc(SCI_LINEFROMPOSITION, byteOffset, 0);
356 startByte = sci->WndProc(SCI_POSITIONFROMLINE, line, 0);
357 endByte = sci->WndProc(SCI_POSITIONFROMLINE, line + 1, 0);
358 break;
361 case ATK_TEXT_BOUNDARY_LINE_END: {
362 int line = sci->WndProc(SCI_LINEFROMPOSITION, byteOffset, 0);
363 if (line > 0) {
364 startByte = sci->WndProc(SCI_GETLINEENDPOSITION, line - 1, 0);
365 } else {
366 startByte = 0;
368 endByte = sci->WndProc(SCI_GETLINEENDPOSITION, line, 0);
369 break;
372 default:
373 *startChar = *endChar = -1;
374 return NULL;
377 CharacterRangeFromByteRange(startByte, endByte, startChar, endChar);
378 return GetTextRangeUTF8(startByte, endByte);
381 #if ATK_CHECK_VERSION(2, 10, 0)
382 gchar *ScintillaGTKAccessible::GetStringAtOffset(int charOffset,
383 AtkTextGranularity granularity, int *startChar, int *endChar) {
384 g_return_val_if_fail(charOffset >= 0, NULL);
386 Position startByte, endByte;
387 Position byteOffset = ByteOffsetFromCharacterOffset(charOffset);
389 switch (granularity) {
390 case ATK_TEXT_GRANULARITY_CHAR:
391 startByte = byteOffset;
392 endByte = sci->WndProc(SCI_POSITIONAFTER, byteOffset, 0);
393 break;
394 case ATK_TEXT_GRANULARITY_WORD:
395 startByte = sci->WndProc(SCI_WORDSTARTPOSITION, byteOffset, 1);
396 endByte = sci->WndProc(SCI_WORDENDPOSITION, byteOffset, 1);
397 break;
398 case ATK_TEXT_GRANULARITY_LINE: {
399 gint line = sci->WndProc(SCI_LINEFROMPOSITION, byteOffset, 0);
400 startByte = sci->WndProc(SCI_POSITIONFROMLINE, line, 0);
401 endByte = sci->WndProc(SCI_GETLINEENDPOSITION, line, 0);
402 break;
404 default:
405 *startChar = *endChar = -1;
406 return NULL;
409 CharacterRangeFromByteRange(startByte, endByte, startChar, endChar);
410 return GetTextRangeUTF8(startByte, endByte);
412 #endif
414 gunichar ScintillaGTKAccessible::GetCharacterAtOffset(int charOffset) {
415 g_return_val_if_fail(charOffset >= 0, 0);
417 Position startByte = ByteOffsetFromCharacterOffset(charOffset);
418 Position endByte = PositionAfter(startByte);
419 gchar *ch = GetTextRangeUTF8(startByte, endByte);
420 gunichar unichar = g_utf8_get_char_validated(ch, -1);
421 g_free(ch);
423 return unichar;
426 gint ScintillaGTKAccessible::GetCharacterCount() {
427 return sci->pdoc->CountCharacters(0, sci->pdoc->Length());
430 gint ScintillaGTKAccessible::GetCaretOffset() {
431 return CharacterOffsetFromByteOffset(sci->WndProc(SCI_GETCURRENTPOS, 0, 0));
434 gboolean ScintillaGTKAccessible::SetCaretOffset(int charOffset) {
435 sci->WndProc(SCI_GOTOPOS, ByteOffsetFromCharacterOffset(charOffset), 0);
436 return TRUE;
439 gint ScintillaGTKAccessible::GetOffsetAtPoint(gint x, gint y, AtkCoordType coords) {
440 gint x_widget, y_widget, x_window, y_window;
441 GtkWidget *widget = gtk_accessible_get_widget(accessible);
443 GdkWindow *window = gtk_widget_get_window(widget);
444 gdk_window_get_origin(window, &x_widget, &y_widget);
445 if (coords == ATK_XY_SCREEN) {
446 x = x - x_widget;
447 y = y - y_widget;
448 } else if (coords == ATK_XY_WINDOW) {
449 window = gdk_window_get_toplevel(window);
450 gdk_window_get_origin(window, &x_window, &y_window);
452 x = x - x_widget + x_window;
453 y = y - y_widget + y_window;
454 } else {
455 return -1;
458 // FIXME: should we handle scrolling?
459 return CharacterOffsetFromByteOffset(sci->WndProc(SCI_CHARPOSITIONFROMPOINTCLOSE, x, y));
462 void ScintillaGTKAccessible::GetCharacterExtents(int charOffset,
463 gint *x, gint *y, gint *width, gint *height, AtkCoordType coords) {
464 *x = *y = *height = *width = 0;
466 Position byteOffset = ByteOffsetFromCharacterOffset(charOffset);
468 // FIXME: should we handle scrolling?
469 *x = sci->WndProc(SCI_POINTXFROMPOSITION, 0, byteOffset);
470 *y = sci->WndProc(SCI_POINTYFROMPOSITION, 0, byteOffset);
472 int line = sci->WndProc(SCI_LINEFROMPOSITION, byteOffset, 0);
473 *height = sci->WndProc(SCI_TEXTHEIGHT, line, 0);
475 int nextByteOffset = PositionAfter(byteOffset);
476 int next_x = sci->WndProc(SCI_POINTXFROMPOSITION, 0, nextByteOffset);
477 if (next_x > *x) {
478 *width = next_x - *x;
479 } else if (nextByteOffset > byteOffset) {
480 /* maybe next position was on the next line or something.
481 * just compute the expected character width */
482 int style = StyleAt(byteOffset, true);
483 int len = nextByteOffset - byteOffset;
484 char *ch = new char[len + 1];
485 sci->pdoc->GetCharRange(ch, byteOffset, len);
486 ch[len] = '\0';
487 *width = sci->TextWidth(style, ch);
488 delete[] ch;
491 GtkWidget *widget = gtk_accessible_get_widget(accessible);
492 GdkWindow *window = gtk_widget_get_window(widget);
493 int x_widget, y_widget;
494 gdk_window_get_origin(window, &x_widget, &y_widget);
495 if (coords == ATK_XY_SCREEN) {
496 *x += x_widget;
497 *y += y_widget;
498 } else if (coords == ATK_XY_WINDOW) {
499 window = gdk_window_get_toplevel(window);
500 int x_window, y_window;
501 gdk_window_get_origin(window, &x_window, &y_window);
503 *x += x_widget - x_window;
504 *y += y_widget - y_window;
505 } else {
506 *x = *y = *height = *width = 0;
510 static AtkAttributeSet *AddTextAttribute(AtkAttributeSet *attributes, AtkTextAttribute attr, gchar *value) {
511 AtkAttribute *at = g_new(AtkAttribute, 1);
512 at->name = g_strdup(atk_text_attribute_get_name(attr));
513 at->value = value;
515 return g_slist_prepend(attributes, at);
518 static AtkAttributeSet *AddTextIntAttribute(AtkAttributeSet *attributes, AtkTextAttribute attr, gint i) {
519 return AddTextAttribute(attributes, attr, g_strdup(atk_text_attribute_get_value(attr, i)));
522 static AtkAttributeSet *AddTextColorAttribute(AtkAttributeSet *attributes, AtkTextAttribute attr, const ColourDesired &colour) {
523 return AddTextAttribute(attributes, attr,
524 g_strdup_printf("%u,%u,%u", colour.GetRed() * 257, colour.GetGreen() * 257, colour.GetBlue() * 257));
527 AtkAttributeSet *ScintillaGTKAccessible::GetAttributesForStyle(unsigned int styleNum) {
528 AtkAttributeSet *attr_set = NULL;
530 if (styleNum >= sci->vs.styles.size())
531 return NULL;
532 Style &style = sci->vs.styles[styleNum];
534 attr_set = AddTextAttribute(attr_set, ATK_TEXT_ATTR_FAMILY_NAME, g_strdup(style.fontName));
535 attr_set = AddTextAttribute(attr_set, ATK_TEXT_ATTR_SIZE, g_strdup_printf("%d", style.size / SC_FONT_SIZE_MULTIPLIER));
536 attr_set = AddTextIntAttribute(attr_set, ATK_TEXT_ATTR_WEIGHT, CLAMP(style.weight, 100, 1000));
537 attr_set = AddTextIntAttribute(attr_set, ATK_TEXT_ATTR_STYLE, style.italic ? PANGO_STYLE_ITALIC : PANGO_STYLE_NORMAL);
538 attr_set = AddTextIntAttribute(attr_set, ATK_TEXT_ATTR_UNDERLINE, style.underline ? PANGO_UNDERLINE_SINGLE : PANGO_UNDERLINE_NONE);
539 attr_set = AddTextColorAttribute(attr_set, ATK_TEXT_ATTR_FG_COLOR, style.fore);
540 attr_set = AddTextColorAttribute(attr_set, ATK_TEXT_ATTR_BG_COLOR, style.back);
541 attr_set = AddTextIntAttribute(attr_set, ATK_TEXT_ATTR_INVISIBLE, style.visible ? 0 : 1);
542 attr_set = AddTextIntAttribute(attr_set, ATK_TEXT_ATTR_EDITABLE, style.changeable ? 1 : 0);
544 return attr_set;
547 AtkAttributeSet *ScintillaGTKAccessible::GetRunAttributes(int charOffset, int *startChar, int *endChar) {
548 g_return_val_if_fail(charOffset >= -1, NULL);
550 Position byteOffset;
551 if (charOffset == -1) {
552 byteOffset = sci->WndProc(SCI_GETCURRENTPOS, 0, 0);
553 } else {
554 byteOffset = ByteOffsetFromCharacterOffset(charOffset);
556 int length = sci->pdoc->Length();
558 g_return_val_if_fail(byteOffset <= length, NULL);
560 const char style = StyleAt(byteOffset, true);
561 // compute the range for this style
562 Position startByte = byteOffset;
563 // when going backwards, we know the style is already computed
564 while (startByte > 0 && sci->pdoc->StyleAt((startByte) - 1) == style)
565 (startByte)--;
566 Position endByte = byteOffset + 1;
567 while (endByte < length && StyleAt(endByte, true) == style)
568 (endByte)++;
570 CharacterRangeFromByteRange(startByte, endByte, startChar, endChar);
571 return GetAttributesForStyle((unsigned int) style);
574 AtkAttributeSet *ScintillaGTKAccessible::GetDefaultAttributes() {
575 return GetAttributesForStyle(0);
578 gint ScintillaGTKAccessible::GetNSelections() {
579 return sci->sel.Empty() ? 0 : sci->sel.Count();
582 gchar *ScintillaGTKAccessible::GetSelection(gint selection_num, int *startChar, int *endChar) {
583 if (selection_num < 0 || (unsigned int) selection_num >= sci->sel.Count())
584 return NULL;
586 Position startByte = sci->sel.Range(selection_num).Start().Position();
587 Position endByte = sci->sel.Range(selection_num).End().Position();
589 CharacterRangeFromByteRange(startByte, endByte, startChar, endChar);
590 return GetTextRangeUTF8(startByte, endByte);
593 gboolean ScintillaGTKAccessible::AddSelection(int startChar, int endChar) {
594 size_t n_selections = sci->sel.Count();
595 Position startByte, endByte;
596 ByteRangeFromCharacterRange(startChar, endChar, startByte, endByte);
597 // use WndProc() to set the selections so it notifies as needed
598 if (n_selections > 1 || ! sci->sel.Empty()) {
599 sci->WndProc(SCI_ADDSELECTION, startByte, endByte);
600 } else {
601 sci->WndProc(SCI_SETSELECTION, startByte, endByte);
604 return TRUE;
607 gboolean ScintillaGTKAccessible::RemoveSelection(gint selection_num) {
608 size_t n_selections = sci->sel.Count();
609 if (selection_num < 0 || (unsigned int) selection_num >= n_selections)
610 return FALSE;
612 if (n_selections > 1) {
613 sci->WndProc(SCI_DROPSELECTIONN, selection_num, 0);
614 } else if (sci->sel.Empty()) {
615 return FALSE;
616 } else {
617 sci->WndProc(SCI_CLEARSELECTIONS, 0, 0);
620 return TRUE;
623 gboolean ScintillaGTKAccessible::SetSelection(gint selection_num, int startChar, int endChar) {
624 if (selection_num < 0 || (unsigned int) selection_num >= sci->sel.Count())
625 return FALSE;
627 Position startByte, endByte;
628 ByteRangeFromCharacterRange(startChar, endChar, startByte, endByte);
630 sci->WndProc(SCI_SETSELECTIONNSTART, selection_num, startByte);
631 sci->WndProc(SCI_SETSELECTIONNEND, selection_num, endByte);
633 return TRUE;
636 void ScintillaGTKAccessible::AtkTextIface::init(::AtkTextIface *iface) {
637 iface->get_text = GetText;
638 iface->get_text_after_offset = GetTextAfterOffset;
639 iface->get_text_at_offset = GetTextAtOffset;
640 iface->get_text_before_offset = GetTextBeforeOffset;
641 #if ATK_CHECK_VERSION(2, 10, 0)
642 iface->get_string_at_offset = GetStringAtOffset;
643 #endif
644 iface->get_character_at_offset = GetCharacterAtOffset;
645 iface->get_character_count = GetCharacterCount;
646 iface->get_caret_offset = GetCaretOffset;
647 iface->set_caret_offset = SetCaretOffset;
648 iface->get_offset_at_point = GetOffsetAtPoint;
649 iface->get_character_extents = GetCharacterExtents;
650 iface->get_n_selections = GetNSelections;
651 iface->get_selection = GetSelection;
652 iface->add_selection = AddSelection;
653 iface->remove_selection = RemoveSelection;
654 iface->set_selection = SetSelection;
655 iface->get_run_attributes = GetRunAttributes;
656 iface->get_default_attributes = GetDefaultAttributes;
659 /* atkeditabletext.h */
661 void ScintillaGTKAccessible::SetTextContents(const gchar *contents) {
662 // FIXME: it's probably useless to check for READONLY here, SETTEXT probably does it just fine?
663 if (! sci->pdoc->IsReadOnly()) {
664 sci->WndProc(SCI_SETTEXT, 0, (sptr_t) contents);
668 bool ScintillaGTKAccessible::InsertStringUTF8(Position bytePos, const gchar *utf8, int lengthBytes) {
669 if (sci->pdoc->IsReadOnly()) {
670 return false;
673 // like EncodedFromUTF8(), but avoids an extra copy
674 // FIXME: update target?
675 const char *charSetBuffer;
676 if (sci->IsUnicodeMode() || ! *(charSetBuffer = sci->CharacterSetID())) {
677 sci->pdoc->InsertString(bytePos, utf8, lengthBytes);
678 } else {
679 // conversion needed
680 std::string encoded = ConvertText(utf8, lengthBytes, charSetBuffer, "UTF-8", true);
681 sci->pdoc->InsertString(bytePos, encoded.c_str(), encoded.length());
684 return true;
687 void ScintillaGTKAccessible::InsertText(const gchar *text, int lengthBytes, int *charPosition) {
688 Position bytePosition = ByteOffsetFromCharacterOffset(*charPosition);
690 // FIXME: should we update the target?
691 if (InsertStringUTF8(bytePosition, text, lengthBytes)) {
692 (*charPosition) += sci->pdoc->CountCharacters(bytePosition, lengthBytes);
696 void ScintillaGTKAccessible::CopyText(int startChar, int endChar) {
697 Position startByte, endByte;
698 ByteRangeFromCharacterRange(startChar, endChar, startByte, endByte);
699 sci->CopyRangeToClipboard(startByte, endByte);
702 void ScintillaGTKAccessible::CutText(int startChar, int endChar) {
703 g_return_if_fail(endChar >= startChar);
705 if (! sci->pdoc->IsReadOnly()) {
706 // FIXME: have a byte variant of those and convert only once?
707 CopyText(startChar, endChar);
708 DeleteText(startChar, endChar);
712 void ScintillaGTKAccessible::DeleteText(int startChar, int endChar) {
713 g_return_if_fail(endChar >= startChar);
715 if (! sci->pdoc->IsReadOnly()) {
716 Position startByte, endByte;
717 ByteRangeFromCharacterRange(startChar, endChar, startByte, endByte);
719 if (! sci->RangeContainsProtected(startByte, endByte)) {
720 // FIXME: restore the target?
721 sci->pdoc->DeleteChars(startByte, endByte - startByte);
726 void ScintillaGTKAccessible::PasteText(int charPosition) {
727 if (sci->pdoc->IsReadOnly())
728 return;
730 // Helper class holding the position for the asynchronous paste operation.
731 // We can only hope that when the callback gets called scia is still valid, but ScintillaGTK
732 // has always done that without problems, so let's guess it's a fairly safe bet.
733 struct Helper : GObjectWatcher {
734 ScintillaGTKAccessible *scia;
735 Position bytePosition;
737 virtual void Destroyed() {
738 scia = 0;
741 Helper(ScintillaGTKAccessible *scia_, Position bytePos_) :
742 GObjectWatcher(G_OBJECT(scia_->sci->sci)),
743 scia(scia_),
744 bytePosition(bytePos_) {
747 void TextReceived(GtkClipboard *, const gchar *text) {
748 if (text) {
749 size_t len = strlen(text);
750 std::string convertedText;
751 if (len > 0 && scia->sci->convertPastes) {
752 // Convert line endings of the paste into our local line-endings mode
753 convertedText = Document::TransformLineEnds(text, len, scia->sci->pdoc->eolMode);
754 len = convertedText.length();
755 text = convertedText.c_str();
757 scia->InsertStringUTF8(bytePosition, text, static_cast<int>(len));
761 static void TextReceivedCallback(GtkClipboard *clipboard, const gchar *text, gpointer data) {
762 Helper *helper = reinterpret_cast<Helper*>(data);
763 try {
764 if (helper->scia != 0) {
765 helper->TextReceived(clipboard, text);
767 } catch (...) {}
768 delete helper;
772 Helper *helper = new Helper(this, ByteOffsetFromCharacterOffset(charPosition));
773 GtkWidget *widget = gtk_accessible_get_widget(accessible);
774 GtkClipboard *clipboard = gtk_widget_get_clipboard(widget, GDK_SELECTION_CLIPBOARD);
775 gtk_clipboard_request_text(clipboard, helper->TextReceivedCallback, helper);
778 void ScintillaGTKAccessible::AtkEditableTextIface::init(::AtkEditableTextIface *iface) {
779 iface->set_text_contents = SetTextContents;
780 iface->insert_text = InsertText;
781 iface->copy_text = CopyText;
782 iface->cut_text = CutText;
783 iface->delete_text = DeleteText;
784 iface->paste_text = PasteText;
785 //~ iface->set_run_attributes = SetRunAttributes;
788 // Callbacks
790 void ScintillaGTKAccessible::UpdateCursor() {
791 Position pos = sci->WndProc(SCI_GETCURRENTPOS, 0, 0);
792 if (old_pos != pos) {
793 int charPosition = CharacterOffsetFromByteOffset(pos);
794 g_signal_emit_by_name(accessible, "text-caret-moved", charPosition);
795 old_pos = pos;
798 size_t n_selections = sci->sel.Count();
799 size_t prev_n_selections = old_sels.size();
800 bool selection_changed = n_selections != prev_n_selections;
802 old_sels.resize(n_selections);
803 for (size_t i = 0; i < n_selections; i++) {
804 SelectionRange &sel = sci->sel.Range(i);
806 if (i < prev_n_selections && ! selection_changed) {
807 SelectionRange &old_sel = old_sels[i];
808 // do not consider a caret move to be a selection change
809 selection_changed = ((! old_sel.Empty() || ! sel.Empty()) && ! (old_sel == sel));
812 old_sels[i] = sel;
815 if (selection_changed)
816 g_signal_emit_by_name(accessible, "text-selection-changed");
819 void ScintillaGTKAccessible::ChangeDocument(Document *oldDoc, Document *newDoc) {
820 if (oldDoc == newDoc) {
821 return;
824 if (oldDoc) {
825 int charLength = oldDoc->CountCharacters(0, oldDoc->Length());
826 g_signal_emit_by_name(accessible, "text-changed::delete", 0, charLength);
829 if (newDoc) {
830 PLATFORM_ASSERT(newDoc == sci->pdoc);
832 int charLength = newDoc->CountCharacters(0, newDoc->Length());
833 g_signal_emit_by_name(accessible, "text-changed::insert", 0, charLength);
835 if ((oldDoc ? oldDoc->IsReadOnly() : false) != newDoc->IsReadOnly()) {
836 NotifyReadOnly();
839 // update cursor and selection
840 old_pos = -1;
841 old_sels.clear();
842 UpdateCursor();
846 void ScintillaGTKAccessible::NotifyReadOnly() {
847 bool readonly = sci->pdoc->IsReadOnly();
848 atk_object_notify_state_change(ATK_OBJECT(accessible), ATK_STATE_EDITABLE, ! readonly);
849 #if ATK_CHECK_VERSION(2, 16, 0)
850 atk_object_notify_state_change(ATK_OBJECT(accessible), ATK_STATE_READ_ONLY, readonly);
851 #endif
854 void ScintillaGTKAccessible::Notify(GtkWidget *, gint, SCNotification *nt) {
855 switch (nt->nmhdr.code) {
856 case SCN_MODIFIED: {
857 if (nt->modificationType & SC_MOD_INSERTTEXT) {
858 int startChar = CharacterOffsetFromByteOffset(nt->position);
859 int lengthChar = sci->pdoc->CountCharacters(nt->position, nt->position + nt->length);
860 g_signal_emit_by_name(accessible, "text-changed::insert", startChar, lengthChar);
861 UpdateCursor();
863 if (nt->modificationType & SC_MOD_BEFOREDELETE) {
864 // We cannot compute the deletion length in DELETETEXT as it requires accessing the
865 // buffer, so that the character are still present. So, we cache the value here,
866 // and use it in DELETETEXT that fires quickly after.
867 deletionLengthChar = sci->pdoc->CountCharacters(nt->position, nt->position + nt->length);
869 if (nt->modificationType & SC_MOD_DELETETEXT) {
870 int startChar = CharacterOffsetFromByteOffset(nt->position);
871 g_signal_emit_by_name(accessible, "text-changed::delete", startChar, deletionLengthChar);
872 UpdateCursor();
874 if (nt->modificationType & SC_MOD_CHANGESTYLE) {
875 g_signal_emit_by_name(accessible, "text-attributes-changed");
877 } break;
878 case SCN_UPDATEUI: {
879 if (nt->updated & SC_UPDATE_SELECTION) {
880 UpdateCursor();
882 } break;
886 // ATK method wrappers
888 // wraps a call from the accessible object to the ScintillaGTKAccessible, and avoid leaking any exception
889 #define WRAPPER_METHOD_BODY(accessible, call, defret) \
890 try { \
891 ScintillaGTKAccessible *thisAccessible = FromAccessible(reinterpret_cast<GtkAccessible*>(accessible)); \
892 if (thisAccessible) { \
893 return thisAccessible->call; \
894 } else { \
895 return defret; \
897 } catch (...) { \
898 return defret; \
901 // AtkText
902 gchar *ScintillaGTKAccessible::AtkTextIface::GetText(AtkText *text, int start_offset, int end_offset) {
903 WRAPPER_METHOD_BODY(text, GetText(start_offset, end_offset), NULL);
905 gchar *ScintillaGTKAccessible::AtkTextIface::GetTextAfterOffset(AtkText *text, int offset, AtkTextBoundary boundary_type, int *start_offset, int *end_offset) {
906 WRAPPER_METHOD_BODY(text, GetTextAfterOffset(offset, boundary_type, start_offset, end_offset), NULL)
908 gchar *ScintillaGTKAccessible::AtkTextIface::GetTextBeforeOffset(AtkText *text, int offset, AtkTextBoundary boundary_type, int *start_offset, int *end_offset) {
909 WRAPPER_METHOD_BODY(text, GetTextBeforeOffset(offset, boundary_type, start_offset, end_offset), NULL)
911 gchar *ScintillaGTKAccessible::AtkTextIface::GetTextAtOffset(AtkText *text, gint offset, AtkTextBoundary boundary_type, gint *start_offset, gint *end_offset) {
912 WRAPPER_METHOD_BODY(text, GetTextAtOffset(offset, boundary_type, start_offset, end_offset), NULL)
914 #if ATK_CHECK_VERSION(2, 10, 0)
915 gchar *ScintillaGTKAccessible::AtkTextIface::GetStringAtOffset(AtkText *text, gint offset, AtkTextGranularity granularity, gint *start_offset, gint *end_offset) {
916 WRAPPER_METHOD_BODY(text, GetStringAtOffset(offset, granularity, start_offset, end_offset), NULL)
918 #endif
919 gunichar ScintillaGTKAccessible::AtkTextIface::GetCharacterAtOffset(AtkText *text, gint offset) {
920 WRAPPER_METHOD_BODY(text, GetCharacterAtOffset(offset), 0)
922 gint ScintillaGTKAccessible::AtkTextIface::GetCharacterCount(AtkText *text) {
923 WRAPPER_METHOD_BODY(text, GetCharacterCount(), 0)
925 gint ScintillaGTKAccessible::AtkTextIface::GetCaretOffset(AtkText *text) {
926 WRAPPER_METHOD_BODY(text, GetCaretOffset(), 0)
928 gboolean ScintillaGTKAccessible::AtkTextIface::SetCaretOffset(AtkText *text, gint offset) {
929 WRAPPER_METHOD_BODY(text, SetCaretOffset(offset), FALSE)
931 gint ScintillaGTKAccessible::AtkTextIface::GetOffsetAtPoint(AtkText *text, gint x, gint y, AtkCoordType coords) {
932 WRAPPER_METHOD_BODY(text, GetOffsetAtPoint(x, y, coords), -1)
934 void ScintillaGTKAccessible::AtkTextIface::GetCharacterExtents(AtkText *text, gint offset, gint *x, gint *y, gint *width, gint *height, AtkCoordType coords) {
935 WRAPPER_METHOD_BODY(text, GetCharacterExtents(offset, x, y, width, height, coords), )
937 AtkAttributeSet *ScintillaGTKAccessible::AtkTextIface::GetRunAttributes(AtkText *text, gint offset, gint *start_offset, gint *end_offset) {
938 WRAPPER_METHOD_BODY(text, GetRunAttributes(offset, start_offset, end_offset), NULL)
940 AtkAttributeSet *ScintillaGTKAccessible::AtkTextIface::GetDefaultAttributes(AtkText *text) {
941 WRAPPER_METHOD_BODY(text, GetDefaultAttributes(), NULL)
943 gint ScintillaGTKAccessible::AtkTextIface::GetNSelections(AtkText *text) {
944 WRAPPER_METHOD_BODY(text, GetNSelections(), 0)
946 gchar *ScintillaGTKAccessible::AtkTextIface::GetSelection(AtkText *text, gint selection_num, gint *start_pos, gint *end_pos) {
947 WRAPPER_METHOD_BODY(text, GetSelection(selection_num, start_pos, end_pos), NULL)
949 gboolean ScintillaGTKAccessible::AtkTextIface::AddSelection(AtkText *text, gint start, gint end) {
950 WRAPPER_METHOD_BODY(text, AddSelection(start, end), FALSE)
952 gboolean ScintillaGTKAccessible::AtkTextIface::RemoveSelection(AtkText *text, gint selection_num) {
953 WRAPPER_METHOD_BODY(text, RemoveSelection(selection_num), FALSE)
955 gboolean ScintillaGTKAccessible::AtkTextIface::SetSelection(AtkText *text, gint selection_num, gint start, gint end) {
956 WRAPPER_METHOD_BODY(text, SetSelection(selection_num, start, end), FALSE)
958 // AtkEditableText
959 void ScintillaGTKAccessible::AtkEditableTextIface::SetTextContents(AtkEditableText *text, const gchar *contents) {
960 WRAPPER_METHOD_BODY(text, SetTextContents(contents), )
962 void ScintillaGTKAccessible::AtkEditableTextIface::InsertText(AtkEditableText *text, const gchar *contents, gint length, gint *position) {
963 WRAPPER_METHOD_BODY(text, InsertText(contents, length, position), )
965 void ScintillaGTKAccessible::AtkEditableTextIface::CopyText(AtkEditableText *text, gint start, gint end) {
966 WRAPPER_METHOD_BODY(text, CopyText(start, end), )
968 void ScintillaGTKAccessible::AtkEditableTextIface::CutText(AtkEditableText *text, gint start, gint end) {
969 WRAPPER_METHOD_BODY(text, CutText(start, end), )
971 void ScintillaGTKAccessible::AtkEditableTextIface::DeleteText(AtkEditableText *text, gint start, gint end) {
972 WRAPPER_METHOD_BODY(text, DeleteText(start, end), )
974 void ScintillaGTKAccessible::AtkEditableTextIface::PasteText(AtkEditableText *text, gint position) {
975 WRAPPER_METHOD_BODY(text, PasteText(position), )
978 // GObject glue
980 #if HAVE_GTK_FACTORY
981 static GType scintilla_object_accessible_factory_get_type(void);
982 #endif
984 static void scintilla_object_accessible_init(ScintillaObjectAccessible *accessible);
985 static void scintilla_object_accessible_class_init(ScintillaObjectAccessibleClass *klass);
986 static gpointer scintilla_object_accessible_parent_class = NULL;
989 // @p parent_type is only required on GTK 3.2 to 3.6, and only on the first call
990 static GType scintilla_object_accessible_get_type(GType parent_type G_GNUC_UNUSED) {
991 static volatile gsize type_id_result = 0;
993 if (g_once_init_enter(&type_id_result)) {
994 GTypeInfo tinfo = {
995 0, /* class size */
996 (GBaseInitFunc) NULL, /* base init */
997 (GBaseFinalizeFunc) NULL, /* base finalize */
998 (GClassInitFunc) scintilla_object_accessible_class_init, /* class init */
999 (GClassFinalizeFunc) NULL, /* class finalize */
1000 NULL, /* class data */
1001 0, /* instance size */
1002 0, /* nb preallocs */
1003 (GInstanceInitFunc) scintilla_object_accessible_init, /* instance init */
1004 NULL /* value table */
1007 const GInterfaceInfo atk_text_info = {
1008 (GInterfaceInitFunc) ScintillaGTKAccessible::AtkTextIface::init,
1009 (GInterfaceFinalizeFunc) NULL,
1010 NULL
1013 const GInterfaceInfo atk_editable_text_info = {
1014 (GInterfaceInitFunc) ScintillaGTKAccessible::AtkEditableTextIface::init,
1015 (GInterfaceFinalizeFunc) NULL,
1016 NULL
1019 #if HAVE_GTK_A11Y_H
1020 // good, we have gtk-a11y.h, we can use that
1021 GType derived_atk_type = GTK_TYPE_CONTAINER_ACCESSIBLE;
1022 tinfo.class_size = sizeof (GtkContainerAccessibleClass);
1023 tinfo.instance_size = sizeof (GtkContainerAccessible);
1024 #else // ! HAVE_GTK_A11Y_H
1025 # if HAVE_GTK_FACTORY
1026 // Figure out the size of the class and instance we are deriving from through the registry
1027 GType derived_type = g_type_parent(SCINTILLA_TYPE_OBJECT);
1028 AtkObjectFactory *factory = atk_registry_get_factory(atk_get_default_registry(), derived_type);
1029 GType derived_atk_type = atk_object_factory_get_accessible_type(factory);
1030 # else // ! HAVE_GTK_FACTORY
1031 // We're kind of screwed and can't determine the parent (no registry, and no public type)
1032 // Hack your way around by requiring the caller to give us our parent type. The caller
1033 // might be able to trick its way into doing that, by e.g. instantiating the parent's
1034 // accessible type and get its GType. It's ugly but we can't do better on GTK 3.2 to 3.6.
1035 g_assert(parent_type != 0);
1037 GType derived_atk_type = parent_type;
1038 # endif // ! HAVE_GTK_FACTORY
1040 GTypeQuery query;
1041 g_type_query(derived_atk_type, &query);
1042 tinfo.class_size = query.class_size;
1043 tinfo.instance_size = query.instance_size;
1044 #endif // ! HAVE_GTK_A11Y_H
1046 GType type_id = g_type_register_static(derived_atk_type, "ScintillaObjectAccessible", &tinfo, (GTypeFlags) 0);
1047 g_type_add_interface_static(type_id, ATK_TYPE_TEXT, &atk_text_info);
1048 g_type_add_interface_static(type_id, ATK_TYPE_EDITABLE_TEXT, &atk_editable_text_info);
1050 g_once_init_leave(&type_id_result, type_id);
1053 return type_id_result;
1056 static AtkObject *scintilla_object_accessible_new(GType parent_type, GObject *obj) {
1057 g_return_val_if_fail(SCINTILLA_IS_OBJECT(obj), NULL);
1059 AtkObject *accessible = (AtkObject *) g_object_new(scintilla_object_accessible_get_type(parent_type),
1060 #if HAVE_WIDGET_SET_UNSET
1061 "widget", obj,
1062 #endif
1063 NULL);
1064 atk_object_initialize(accessible, obj);
1066 return accessible;
1069 // implementation for gtk_widget_get_accessible().
1070 // See the comment at the top of the file for details on the implementation
1071 // @p widget the widget.
1072 // @p cache pointer to store the AtkObject between repeated calls. Might or might not be filled.
1073 // @p widget_parent_class pointer to the widget's parent class (to chain up method calls).
1074 AtkObject *ScintillaGTKAccessible::WidgetGetAccessibleImpl(GtkWidget *widget, AtkObject **cache, gpointer widget_parent_class G_GNUC_UNUSED) {
1075 if (*cache != NULL) {
1076 return *cache;
1079 #if HAVE_GTK_A11Y_H // just instantiate the accessible
1080 *cache = scintilla_object_accessible_new(0, G_OBJECT(widget));
1081 #elif HAVE_GTK_FACTORY // register in the factory and let GTK instantiate
1082 static volatile gsize registered = 0;
1084 if (g_once_init_enter(&registered)) {
1085 // Figure out whether accessibility is enabled by looking at the type of the accessible
1086 // object which would be created for the parent type of ScintillaObject.
1087 GType derived_type = g_type_parent(SCINTILLA_TYPE_OBJECT);
1089 AtkRegistry *registry = atk_get_default_registry();
1090 AtkObjectFactory *factory = atk_registry_get_factory(registry, derived_type);
1091 GType derived_atk_type = atk_object_factory_get_accessible_type(factory);
1092 if (g_type_is_a(derived_atk_type, GTK_TYPE_ACCESSIBLE)) {
1093 atk_registry_set_factory_type(registry, SCINTILLA_TYPE_OBJECT,
1094 scintilla_object_accessible_factory_get_type());
1096 g_once_init_leave(&registered, 1);
1098 AtkObject *obj = GTK_WIDGET_CLASS(widget_parent_class)->get_accessible(widget);
1099 *cache = static_cast<AtkObject*>(g_object_ref(obj));
1100 #else // no public API, no factory, so guess from the parent and instantiate
1101 static GType parent_atk_type = 0;
1103 if (parent_atk_type == 0) {
1104 AtkObject *parent_obj = GTK_WIDGET_CLASS(widget_parent_class)->get_accessible(widget);
1105 if (parent_obj) {
1106 GType parent_atk_type = G_OBJECT_TYPE(parent_obj);
1108 // Figure out whether accessibility is enabled by looking at the type of the accessible
1109 // object which would be created for the parent type of ScintillaObject.
1110 if (g_type_is_a(parent_atk_type, GTK_TYPE_ACCESSIBLE)) {
1111 *cache = scintilla_object_accessible_new(parent_atk_type, G_OBJECT(widget));
1112 } else {
1113 *cache = static_cast<AtkObject*>(g_object_ref(parent_obj));
1117 #endif
1118 return *cache;
1121 static AtkStateSet *scintilla_object_accessible_ref_state_set(AtkObject *accessible) {
1122 AtkStateSet *state_set = ATK_OBJECT_CLASS(scintilla_object_accessible_parent_class)->ref_state_set(accessible);
1124 GtkWidget *widget = gtk_accessible_get_widget(GTK_ACCESSIBLE(accessible));
1125 if (widget == NULL) {
1126 atk_state_set_add_state(state_set, ATK_STATE_DEFUNCT);
1127 } else {
1128 if (! scintilla_send_message(SCINTILLA_OBJECT(widget), SCI_GETREADONLY, 0, 0))
1129 atk_state_set_add_state(state_set, ATK_STATE_EDITABLE);
1130 #if ATK_CHECK_VERSION(2, 16, 0)
1131 else
1132 atk_state_set_add_state(state_set, ATK_STATE_READ_ONLY);
1133 #endif
1134 atk_state_set_add_state(state_set, ATK_STATE_MULTI_LINE);
1135 atk_state_set_add_state(state_set, ATK_STATE_MULTISELECTABLE);
1136 atk_state_set_add_state(state_set, ATK_STATE_SELECTABLE_TEXT);
1137 /*atk_state_set_add_state(state_set, ATK_STATE_SUPPORTS_AUTOCOMPLETION);*/
1140 return state_set;
1143 static void scintilla_object_accessible_widget_set(GtkAccessible *accessible) {
1144 GtkWidget *widget = gtk_accessible_get_widget(accessible);
1145 if (widget == NULL)
1146 return;
1148 ScintillaObjectAccessiblePrivate *priv = SCINTILLA_OBJECT_ACCESSIBLE_GET_PRIVATE(accessible);
1149 if (priv->pscin != 0)
1150 delete priv->pscin;
1151 priv->pscin = new ScintillaGTKAccessible(accessible, widget);
1154 #if HAVE_WIDGET_SET_UNSET
1155 static void scintilla_object_accessible_widget_unset(GtkAccessible *accessible) {
1156 GtkWidget *widget = gtk_accessible_get_widget(accessible);
1157 if (widget == NULL)
1158 return;
1160 ScintillaObjectAccessiblePrivate *priv = SCINTILLA_OBJECT_ACCESSIBLE_GET_PRIVATE(accessible);
1161 delete priv->pscin;
1162 priv->pscin = 0;
1164 #endif
1166 static void scintilla_object_accessible_initialize(AtkObject *obj, gpointer data) {
1167 ATK_OBJECT_CLASS(scintilla_object_accessible_parent_class)->initialize(obj, data);
1169 #if ! HAVE_WIDGET_SET_UNSET
1170 scintilla_object_accessible_widget_set(GTK_ACCESSIBLE(obj));
1171 #endif
1173 obj->role = ATK_ROLE_TEXT;
1176 static void scintilla_object_accessible_finalize(GObject *object) {
1177 ScintillaObjectAccessiblePrivate *priv = SCINTILLA_OBJECT_ACCESSIBLE_GET_PRIVATE(object);
1179 if (priv->pscin) {
1180 delete priv->pscin;
1181 priv->pscin = 0;
1184 G_OBJECT_CLASS(scintilla_object_accessible_parent_class)->finalize(object);
1187 static void scintilla_object_accessible_class_init(ScintillaObjectAccessibleClass *klass) {
1188 GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
1189 AtkObjectClass *object_class = ATK_OBJECT_CLASS(klass);
1191 #if HAVE_WIDGET_SET_UNSET
1192 GtkAccessibleClass *accessible_class = GTK_ACCESSIBLE_CLASS(klass);
1193 accessible_class->widget_set = scintilla_object_accessible_widget_set;
1194 accessible_class->widget_unset = scintilla_object_accessible_widget_unset;
1195 #endif
1197 object_class->ref_state_set = scintilla_object_accessible_ref_state_set;
1198 object_class->initialize = scintilla_object_accessible_initialize;
1200 gobject_class->finalize = scintilla_object_accessible_finalize;
1202 scintilla_object_accessible_parent_class = g_type_class_peek_parent(klass);
1204 g_type_class_add_private(klass, sizeof (ScintillaObjectAccessiblePrivate));
1207 static void scintilla_object_accessible_init(ScintillaObjectAccessible *accessible) {
1208 ScintillaObjectAccessiblePrivate *priv = SCINTILLA_OBJECT_ACCESSIBLE_GET_PRIVATE(accessible);
1210 priv->pscin = 0;
1213 #if HAVE_GTK_FACTORY
1214 // Object factory
1215 typedef AtkObjectFactory ScintillaObjectAccessibleFactory;
1216 typedef AtkObjectFactoryClass ScintillaObjectAccessibleFactoryClass;
1218 G_DEFINE_TYPE(ScintillaObjectAccessibleFactory, scintilla_object_accessible_factory, ATK_TYPE_OBJECT_FACTORY)
1220 static void scintilla_object_accessible_factory_init(ScintillaObjectAccessibleFactory *) {
1223 static GType scintilla_object_accessible_factory_get_accessible_type(void) {
1224 return SCINTILLA_TYPE_OBJECT_ACCESSIBLE;
1227 static AtkObject *scintilla_object_accessible_factory_create_accessible(GObject *obj) {
1228 return scintilla_object_accessible_new(0, obj);
1231 static void scintilla_object_accessible_factory_class_init(AtkObjectFactoryClass * klass) {
1232 klass->create_accessible = scintilla_object_accessible_factory_create_accessible;
1233 klass->get_accessible_type = scintilla_object_accessible_factory_get_accessible_type;
1235 #endif