Adjust 'fall through' comments to be recognized by GCC
[geany-mirror.git] / scintilla / gtk / ScintillaGTKAccessible.cxx
blobfdcc20dbdfec53be5c228bae51e0a5a67906d9b0
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 <cstddef>
55 #include <cstdlib>
56 #include <cstring>
58 #include <stdexcept>
59 #include <new>
60 #include <string>
61 #include <vector>
62 #include <map>
63 #include <algorithm>
64 #include <memory>
66 #include <glib.h>
67 #include <gtk/gtk.h>
69 // whether we have widget_set() and widget_unset()
70 #define HAVE_WIDGET_SET_UNSET (GTK_CHECK_VERSION(3, 3, 6))
71 // whether GTK accessibility is available through the ATK factory
72 #define HAVE_GTK_FACTORY (! GTK_CHECK_VERSION(3, 1, 9))
73 // whether we have gtk-a11y.h and the public GTK accessible types
74 #define HAVE_GTK_A11Y_H (GTK_CHECK_VERSION(3, 7, 6))
76 #if HAVE_GTK_A11Y_H
77 # include <gtk/gtk-a11y.h>
78 #endif
80 #if defined(__WIN32__) || defined(_MSC_VER)
81 #include <windows.h>
82 #endif
84 // ScintillaGTK.h and stuff it needs
85 #include "Platform.h"
87 #include "ILexer.h"
88 #include "Scintilla.h"
89 #include "ScintillaWidget.h"
90 #ifdef SCI_LEXER
91 #include "SciLexer.h"
92 #endif
93 #include "StringCopy.h"
94 #ifdef SCI_LEXER
95 #include "LexerModule.h"
96 #endif
97 #include "Position.h"
98 #include "UniqueString.h"
99 #include "SplitVector.h"
100 #include "Partitioning.h"
101 #include "RunStyles.h"
102 #include "ContractionState.h"
103 #include "CellBuffer.h"
104 #include "CallTip.h"
105 #include "KeyMap.h"
106 #include "Indicator.h"
107 #include "XPM.h"
108 #include "LineMarker.h"
109 #include "Style.h"
110 #include "ViewStyle.h"
111 #include "CharClassify.h"
112 #include "Decoration.h"
113 #include "CaseFolder.h"
114 #include "Document.h"
115 #include "CaseConvert.h"
116 #include "UniConversion.h"
117 #include "UnicodeFromUTF8.h"
118 #include "Selection.h"
119 #include "PositionCache.h"
120 #include "EditModel.h"
121 #include "MarginView.h"
122 #include "EditView.h"
123 #include "Editor.h"
124 #include "AutoComplete.h"
125 #include "ScintillaBase.h"
127 #include "ScintillaGTK.h"
128 #include "ScintillaGTKAccessible.h"
130 #ifdef SCI_NAMESPACE
131 using namespace Scintilla;
132 #endif
134 struct ScintillaObjectAccessiblePrivate {
135 ScintillaGTKAccessible *pscin;
138 typedef GtkAccessible ScintillaObjectAccessible;
139 typedef GtkAccessibleClass ScintillaObjectAccessibleClass;
141 #define SCINTILLA_OBJECT_ACCESSIBLE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), SCINTILLA_TYPE_OBJECT_ACCESSIBLE, ScintillaObjectAccessible))
142 #define SCINTILLA_TYPE_OBJECT_ACCESSIBLE (scintilla_object_accessible_get_type(0))
144 // We can't use priv member because of dynamic inheritance, so we don't actually know the offset. Meh.
145 #define SCINTILLA_OBJECT_ACCESSIBLE_GET_PRIVATE(inst) (G_TYPE_INSTANCE_GET_PRIVATE((inst), SCINTILLA_TYPE_OBJECT_ACCESSIBLE, ScintillaObjectAccessiblePrivate))
147 static GType scintilla_object_accessible_get_type(GType parent_type);
149 ScintillaGTKAccessible *ScintillaGTKAccessible::FromAccessible(GtkAccessible *accessible) {
150 // FIXME: do we need the check below? GTK checks that in all methods, so maybe
151 GtkWidget *widget = gtk_accessible_get_widget(accessible);
152 if (! widget) {
153 return 0;
156 return SCINTILLA_OBJECT_ACCESSIBLE_GET_PRIVATE(accessible)->pscin;
159 ScintillaGTKAccessible::ScintillaGTKAccessible(GtkAccessible *accessible_, GtkWidget *widget_) :
160 accessible(accessible_),
161 sci(ScintillaGTK::FromWidget(widget_)),
162 deletionLengthChar(0),
163 old_pos(-1) {
164 g_signal_connect(widget_, "sci-notify", G_CALLBACK(SciNotify), this);
167 ScintillaGTKAccessible::~ScintillaGTKAccessible() {
168 if (gtk_accessible_get_widget(accessible)) {
169 g_signal_handlers_disconnect_matched(sci->sci, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, this);
173 gchar *ScintillaGTKAccessible::GetTextRangeUTF8(Sci::Position startByte, Sci::Position endByte) {
174 g_return_val_if_fail(startByte >= 0, NULL);
175 // FIXME: should we swap start/end if necessary?
176 g_return_val_if_fail(endByte >= startByte, NULL);
178 gchar *utf8Text = NULL;
179 const char *charSetBuffer;
181 // like TargetAsUTF8, but avoids a double conversion
182 if (sci->IsUnicodeMode() || ! *(charSetBuffer = sci->CharacterSetID())) {
183 int len = endByte - startByte;
184 utf8Text = (char *) g_malloc(len + 1);
185 sci->pdoc->GetCharRange(utf8Text, startByte, len);
186 utf8Text[len] = '\0';
187 } else {
188 // Need to convert
189 std::string s = sci->RangeText(startByte, endByte);
190 std::string tmputf = ConvertText(&s[0], s.length(), "UTF-8", charSetBuffer, false);
191 size_t len = tmputf.length();
192 utf8Text = (char *) g_malloc(len + 1);
193 memcpy(utf8Text, tmputf.c_str(), len);
194 utf8Text[len] = '\0';
197 return utf8Text;
200 gchar *ScintillaGTKAccessible::GetText(int startChar, int endChar) {
201 Sci::Position startByte, endByte;
202 if (endChar == -1) {
203 startByte = ByteOffsetFromCharacterOffset(startChar);
204 endByte = sci->pdoc->Length();
205 } else {
206 ByteRangeFromCharacterRange(startChar, endChar, startByte, endByte);
208 return GetTextRangeUTF8(startByte, endByte);
211 gchar *ScintillaGTKAccessible::GetTextAfterOffset(int charOffset,
212 AtkTextBoundary boundaryType, int *startChar, int *endChar) {
213 g_return_val_if_fail(charOffset >= 0, NULL);
215 Sci::Position startByte, endByte;
216 Sci::Position byteOffset = ByteOffsetFromCharacterOffset(charOffset);
218 switch (boundaryType) {
219 case ATK_TEXT_BOUNDARY_CHAR:
220 startByte = PositionAfter(byteOffset);
221 endByte = PositionAfter(startByte);
222 // FIXME: optimize conversion back, as we can reasonably assume +1 char?
223 break;
225 case ATK_TEXT_BOUNDARY_WORD_START:
226 startByte = sci->WndProc(SCI_WORDENDPOSITION, byteOffset, 1);
227 startByte = sci->WndProc(SCI_WORDENDPOSITION, startByte, 0);
228 endByte = sci->WndProc(SCI_WORDENDPOSITION, startByte, 1);
229 endByte = sci->WndProc(SCI_WORDENDPOSITION, endByte, 0);
230 break;
232 case ATK_TEXT_BOUNDARY_WORD_END:
233 startByte = sci->WndProc(SCI_WORDENDPOSITION, byteOffset, 0);
234 startByte = sci->WndProc(SCI_WORDENDPOSITION, startByte, 1);
235 endByte = sci->WndProc(SCI_WORDENDPOSITION, startByte, 0);
236 endByte = sci->WndProc(SCI_WORDENDPOSITION, endByte, 1);
237 break;
239 case ATK_TEXT_BOUNDARY_LINE_START: {
240 int line = sci->WndProc(SCI_LINEFROMPOSITION, byteOffset, 0);
241 startByte = sci->WndProc(SCI_POSITIONFROMLINE, line + 1, 0);
242 endByte = sci->WndProc(SCI_POSITIONFROMLINE, line + 2, 0);
243 break;
246 case ATK_TEXT_BOUNDARY_LINE_END: {
247 int line = sci->WndProc(SCI_LINEFROMPOSITION, byteOffset, 0);
248 startByte = sci->WndProc(SCI_GETLINEENDPOSITION, line, 0);
249 endByte = sci->WndProc(SCI_GETLINEENDPOSITION, line + 1, 0);
250 break;
253 default:
254 *startChar = *endChar = -1;
255 return NULL;
258 CharacterRangeFromByteRange(startByte, endByte, startChar, endChar);
259 return GetTextRangeUTF8(startByte, endByte);
262 gchar *ScintillaGTKAccessible::GetTextBeforeOffset(int charOffset,
263 AtkTextBoundary boundaryType, int *startChar, int *endChar) {
264 g_return_val_if_fail(charOffset >= 0, NULL);
266 Sci::Position startByte, endByte;
267 Sci::Position byteOffset = ByteOffsetFromCharacterOffset(charOffset);
269 switch (boundaryType) {
270 case ATK_TEXT_BOUNDARY_CHAR:
271 endByte = PositionBefore(byteOffset);
272 startByte = PositionBefore(endByte);
273 break;
275 case ATK_TEXT_BOUNDARY_WORD_START:
276 endByte = sci->WndProc(SCI_WORDSTARTPOSITION, byteOffset, 0);
277 endByte = sci->WndProc(SCI_WORDSTARTPOSITION, endByte, 1);
278 startByte = sci->WndProc(SCI_WORDSTARTPOSITION, endByte, 0);
279 startByte = sci->WndProc(SCI_WORDSTARTPOSITION, startByte, 1);
280 break;
282 case ATK_TEXT_BOUNDARY_WORD_END:
283 endByte = sci->WndProc(SCI_WORDSTARTPOSITION, byteOffset, 1);
284 endByte = sci->WndProc(SCI_WORDSTARTPOSITION, endByte, 0);
285 startByte = sci->WndProc(SCI_WORDSTARTPOSITION, endByte, 1);
286 startByte = sci->WndProc(SCI_WORDSTARTPOSITION, startByte, 0);
287 break;
289 case ATK_TEXT_BOUNDARY_LINE_START: {
290 int line = sci->WndProc(SCI_LINEFROMPOSITION, byteOffset, 0);
291 endByte = sci->WndProc(SCI_POSITIONFROMLINE, line, 0);
292 if (line > 0) {
293 startByte = sci->WndProc(SCI_POSITIONFROMLINE, line - 1, 0);
294 } else {
295 startByte = endByte;
297 break;
300 case ATK_TEXT_BOUNDARY_LINE_END: {
301 int line = sci->WndProc(SCI_LINEFROMPOSITION, byteOffset, 0);
302 if (line > 0) {
303 endByte = sci->WndProc(SCI_GETLINEENDPOSITION, line - 1, 0);
304 } else {
305 endByte = 0;
307 if (line > 1) {
308 startByte = sci->WndProc(SCI_GETLINEENDPOSITION, line - 2, 0);
309 } else {
310 startByte = endByte;
312 break;
315 default:
316 *startChar = *endChar = -1;
317 return NULL;
320 CharacterRangeFromByteRange(startByte, endByte, startChar, endChar);
321 return GetTextRangeUTF8(startByte, endByte);
324 gchar *ScintillaGTKAccessible::GetTextAtOffset(int charOffset,
325 AtkTextBoundary boundaryType, int *startChar, int *endChar) {
326 g_return_val_if_fail(charOffset >= 0, NULL);
328 Sci::Position startByte, endByte;
329 Sci::Position byteOffset = ByteOffsetFromCharacterOffset(charOffset);
331 switch (boundaryType) {
332 case ATK_TEXT_BOUNDARY_CHAR:
333 startByte = byteOffset;
334 endByte = sci->WndProc(SCI_POSITIONAFTER, byteOffset, 0);
335 break;
337 case ATK_TEXT_BOUNDARY_WORD_START:
338 startByte = sci->WndProc(SCI_WORDSTARTPOSITION, byteOffset, 1);
339 endByte = sci->WndProc(SCI_WORDENDPOSITION, byteOffset, 1);
340 if (! sci->WndProc(SCI_ISRANGEWORD, startByte, endByte)) {
341 // if the cursor was not on a word, forward back
342 startByte = sci->WndProc(SCI_WORDSTARTPOSITION, startByte, 0);
343 startByte = sci->WndProc(SCI_WORDSTARTPOSITION, startByte, 1);
345 endByte = sci->WndProc(SCI_WORDENDPOSITION, endByte, 0);
346 break;
348 case ATK_TEXT_BOUNDARY_WORD_END:
349 startByte = sci->WndProc(SCI_WORDSTARTPOSITION, byteOffset, 1);
350 endByte = sci->WndProc(SCI_WORDENDPOSITION, byteOffset, 1);
351 if (! sci->WndProc(SCI_ISRANGEWORD, startByte, endByte)) {
352 // if the cursor was not on a word, forward back
353 endByte = sci->WndProc(SCI_WORDENDPOSITION, endByte, 0);
354 endByte = sci->WndProc(SCI_WORDENDPOSITION, endByte, 1);
356 startByte = sci->WndProc(SCI_WORDSTARTPOSITION, startByte, 0);
357 break;
359 case ATK_TEXT_BOUNDARY_LINE_START: {
360 int line = sci->WndProc(SCI_LINEFROMPOSITION, byteOffset, 0);
361 startByte = sci->WndProc(SCI_POSITIONFROMLINE, line, 0);
362 endByte = sci->WndProc(SCI_POSITIONFROMLINE, line + 1, 0);
363 break;
366 case ATK_TEXT_BOUNDARY_LINE_END: {
367 int line = sci->WndProc(SCI_LINEFROMPOSITION, byteOffset, 0);
368 if (line > 0) {
369 startByte = sci->WndProc(SCI_GETLINEENDPOSITION, line - 1, 0);
370 } else {
371 startByte = 0;
373 endByte = sci->WndProc(SCI_GETLINEENDPOSITION, line, 0);
374 break;
377 default:
378 *startChar = *endChar = -1;
379 return NULL;
382 CharacterRangeFromByteRange(startByte, endByte, startChar, endChar);
383 return GetTextRangeUTF8(startByte, endByte);
386 #if ATK_CHECK_VERSION(2, 10, 0)
387 gchar *ScintillaGTKAccessible::GetStringAtOffset(int charOffset,
388 AtkTextGranularity granularity, int *startChar, int *endChar) {
389 g_return_val_if_fail(charOffset >= 0, NULL);
391 Sci::Position startByte, endByte;
392 Sci::Position byteOffset = ByteOffsetFromCharacterOffset(charOffset);
394 switch (granularity) {
395 case ATK_TEXT_GRANULARITY_CHAR:
396 startByte = byteOffset;
397 endByte = sci->WndProc(SCI_POSITIONAFTER, byteOffset, 0);
398 break;
399 case ATK_TEXT_GRANULARITY_WORD:
400 startByte = sci->WndProc(SCI_WORDSTARTPOSITION, byteOffset, 1);
401 endByte = sci->WndProc(SCI_WORDENDPOSITION, byteOffset, 1);
402 break;
403 case ATK_TEXT_GRANULARITY_LINE: {
404 gint line = sci->WndProc(SCI_LINEFROMPOSITION, byteOffset, 0);
405 startByte = sci->WndProc(SCI_POSITIONFROMLINE, line, 0);
406 endByte = sci->WndProc(SCI_GETLINEENDPOSITION, line, 0);
407 break;
409 default:
410 *startChar = *endChar = -1;
411 return NULL;
414 CharacterRangeFromByteRange(startByte, endByte, startChar, endChar);
415 return GetTextRangeUTF8(startByte, endByte);
417 #endif
419 gunichar ScintillaGTKAccessible::GetCharacterAtOffset(int charOffset) {
420 g_return_val_if_fail(charOffset >= 0, 0);
422 Sci::Position startByte = ByteOffsetFromCharacterOffset(charOffset);
423 Sci::Position endByte = PositionAfter(startByte);
424 gchar *ch = GetTextRangeUTF8(startByte, endByte);
425 gunichar unichar = g_utf8_get_char_validated(ch, -1);
426 g_free(ch);
428 return unichar;
431 gint ScintillaGTKAccessible::GetCharacterCount() {
432 return sci->pdoc->CountCharacters(0, sci->pdoc->Length());
435 gint ScintillaGTKAccessible::GetCaretOffset() {
436 return CharacterOffsetFromByteOffset(sci->WndProc(SCI_GETCURRENTPOS, 0, 0));
439 gboolean ScintillaGTKAccessible::SetCaretOffset(int charOffset) {
440 sci->WndProc(SCI_GOTOPOS, ByteOffsetFromCharacterOffset(charOffset), 0);
441 return TRUE;
444 gint ScintillaGTKAccessible::GetOffsetAtPoint(gint x, gint y, AtkCoordType coords) {
445 gint x_widget, y_widget, x_window, y_window;
446 GtkWidget *widget = gtk_accessible_get_widget(accessible);
448 GdkWindow *window = gtk_widget_get_window(widget);
449 gdk_window_get_origin(window, &x_widget, &y_widget);
450 if (coords == ATK_XY_SCREEN) {
451 x = x - x_widget;
452 y = y - y_widget;
453 } else if (coords == ATK_XY_WINDOW) {
454 window = gdk_window_get_toplevel(window);
455 gdk_window_get_origin(window, &x_window, &y_window);
457 x = x - x_widget + x_window;
458 y = y - y_widget + y_window;
459 } else {
460 return -1;
463 // FIXME: should we handle scrolling?
464 return CharacterOffsetFromByteOffset(sci->WndProc(SCI_CHARPOSITIONFROMPOINTCLOSE, x, y));
467 void ScintillaGTKAccessible::GetCharacterExtents(int charOffset,
468 gint *x, gint *y, gint *width, gint *height, AtkCoordType coords) {
469 *x = *y = *height = *width = 0;
471 Sci::Position byteOffset = ByteOffsetFromCharacterOffset(charOffset);
473 // FIXME: should we handle scrolling?
474 *x = sci->WndProc(SCI_POINTXFROMPOSITION, 0, byteOffset);
475 *y = sci->WndProc(SCI_POINTYFROMPOSITION, 0, byteOffset);
477 int line = sci->WndProc(SCI_LINEFROMPOSITION, byteOffset, 0);
478 *height = sci->WndProc(SCI_TEXTHEIGHT, line, 0);
480 int nextByteOffset = PositionAfter(byteOffset);
481 int next_x = sci->WndProc(SCI_POINTXFROMPOSITION, 0, nextByteOffset);
482 if (next_x > *x) {
483 *width = next_x - *x;
484 } else if (nextByteOffset > byteOffset) {
485 /* maybe next position was on the next line or something.
486 * just compute the expected character width */
487 int style = StyleAt(byteOffset, true);
488 int len = nextByteOffset - byteOffset;
489 char *ch = new char[len + 1];
490 sci->pdoc->GetCharRange(ch, byteOffset, len);
491 ch[len] = '\0';
492 *width = sci->TextWidth(style, ch);
493 delete[] ch;
496 GtkWidget *widget = gtk_accessible_get_widget(accessible);
497 GdkWindow *window = gtk_widget_get_window(widget);
498 int x_widget, y_widget;
499 gdk_window_get_origin(window, &x_widget, &y_widget);
500 if (coords == ATK_XY_SCREEN) {
501 *x += x_widget;
502 *y += y_widget;
503 } else if (coords == ATK_XY_WINDOW) {
504 window = gdk_window_get_toplevel(window);
505 int x_window, y_window;
506 gdk_window_get_origin(window, &x_window, &y_window);
508 *x += x_widget - x_window;
509 *y += y_widget - y_window;
510 } else {
511 *x = *y = *height = *width = 0;
515 static AtkAttributeSet *AddTextAttribute(AtkAttributeSet *attributes, AtkTextAttribute attr, gchar *value) {
516 AtkAttribute *at = g_new(AtkAttribute, 1);
517 at->name = g_strdup(atk_text_attribute_get_name(attr));
518 at->value = value;
520 return g_slist_prepend(attributes, at);
523 static AtkAttributeSet *AddTextIntAttribute(AtkAttributeSet *attributes, AtkTextAttribute attr, gint i) {
524 return AddTextAttribute(attributes, attr, g_strdup(atk_text_attribute_get_value(attr, i)));
527 static AtkAttributeSet *AddTextColorAttribute(AtkAttributeSet *attributes, AtkTextAttribute attr, const ColourDesired &colour) {
528 return AddTextAttribute(attributes, attr,
529 g_strdup_printf("%u,%u,%u", colour.GetRed() * 257, colour.GetGreen() * 257, colour.GetBlue() * 257));
532 AtkAttributeSet *ScintillaGTKAccessible::GetAttributesForStyle(unsigned int styleNum) {
533 AtkAttributeSet *attr_set = NULL;
535 if (styleNum >= sci->vs.styles.size())
536 return NULL;
537 Style &style = sci->vs.styles[styleNum];
539 attr_set = AddTextAttribute(attr_set, ATK_TEXT_ATTR_FAMILY_NAME, g_strdup(style.fontName));
540 attr_set = AddTextAttribute(attr_set, ATK_TEXT_ATTR_SIZE, g_strdup_printf("%d", style.size / SC_FONT_SIZE_MULTIPLIER));
541 attr_set = AddTextIntAttribute(attr_set, ATK_TEXT_ATTR_WEIGHT, CLAMP(style.weight, 100, 1000));
542 attr_set = AddTextIntAttribute(attr_set, ATK_TEXT_ATTR_STYLE, style.italic ? PANGO_STYLE_ITALIC : PANGO_STYLE_NORMAL);
543 attr_set = AddTextIntAttribute(attr_set, ATK_TEXT_ATTR_UNDERLINE, style.underline ? PANGO_UNDERLINE_SINGLE : PANGO_UNDERLINE_NONE);
544 attr_set = AddTextColorAttribute(attr_set, ATK_TEXT_ATTR_FG_COLOR, style.fore);
545 attr_set = AddTextColorAttribute(attr_set, ATK_TEXT_ATTR_BG_COLOR, style.back);
546 attr_set = AddTextIntAttribute(attr_set, ATK_TEXT_ATTR_INVISIBLE, style.visible ? 0 : 1);
547 attr_set = AddTextIntAttribute(attr_set, ATK_TEXT_ATTR_EDITABLE, style.changeable ? 1 : 0);
549 return attr_set;
552 AtkAttributeSet *ScintillaGTKAccessible::GetRunAttributes(int charOffset, int *startChar, int *endChar) {
553 g_return_val_if_fail(charOffset >= -1, NULL);
555 Sci::Position byteOffset;
556 if (charOffset == -1) {
557 byteOffset = sci->WndProc(SCI_GETCURRENTPOS, 0, 0);
558 } else {
559 byteOffset = ByteOffsetFromCharacterOffset(charOffset);
561 int length = sci->pdoc->Length();
563 g_return_val_if_fail(byteOffset <= length, NULL);
565 const char style = StyleAt(byteOffset, true);
566 // compute the range for this style
567 Sci::Position startByte = byteOffset;
568 // when going backwards, we know the style is already computed
569 while (startByte > 0 && sci->pdoc->StyleAt((startByte) - 1) == style)
570 (startByte)--;
571 Sci::Position endByte = byteOffset + 1;
572 while (endByte < length && StyleAt(endByte, true) == style)
573 (endByte)++;
575 CharacterRangeFromByteRange(startByte, endByte, startChar, endChar);
576 return GetAttributesForStyle((unsigned int) style);
579 AtkAttributeSet *ScintillaGTKAccessible::GetDefaultAttributes() {
580 return GetAttributesForStyle(0);
583 gint ScintillaGTKAccessible::GetNSelections() {
584 return sci->sel.Empty() ? 0 : sci->sel.Count();
587 gchar *ScintillaGTKAccessible::GetSelection(gint selection_num, int *startChar, int *endChar) {
588 if (selection_num < 0 || (unsigned int) selection_num >= sci->sel.Count())
589 return NULL;
591 Sci::Position startByte = sci->sel.Range(selection_num).Start().Position();
592 Sci::Position endByte = sci->sel.Range(selection_num).End().Position();
594 CharacterRangeFromByteRange(startByte, endByte, startChar, endChar);
595 return GetTextRangeUTF8(startByte, endByte);
598 gboolean ScintillaGTKAccessible::AddSelection(int startChar, int endChar) {
599 size_t n_selections = sci->sel.Count();
600 Sci::Position startByte, endByte;
601 ByteRangeFromCharacterRange(startChar, endChar, startByte, endByte);
602 // use WndProc() to set the selections so it notifies as needed
603 if (n_selections > 1 || ! sci->sel.Empty()) {
604 sci->WndProc(SCI_ADDSELECTION, startByte, endByte);
605 } else {
606 sci->WndProc(SCI_SETSELECTION, startByte, endByte);
609 return TRUE;
612 gboolean ScintillaGTKAccessible::RemoveSelection(gint selection_num) {
613 size_t n_selections = sci->sel.Count();
614 if (selection_num < 0 || (unsigned int) selection_num >= n_selections)
615 return FALSE;
617 if (n_selections > 1) {
618 sci->WndProc(SCI_DROPSELECTIONN, selection_num, 0);
619 } else if (sci->sel.Empty()) {
620 return FALSE;
621 } else {
622 sci->WndProc(SCI_CLEARSELECTIONS, 0, 0);
625 return TRUE;
628 gboolean ScintillaGTKAccessible::SetSelection(gint selection_num, int startChar, int endChar) {
629 if (selection_num < 0 || (unsigned int) selection_num >= sci->sel.Count())
630 return FALSE;
632 Sci::Position startByte, endByte;
633 ByteRangeFromCharacterRange(startChar, endChar, startByte, endByte);
635 sci->WndProc(SCI_SETSELECTIONNSTART, selection_num, startByte);
636 sci->WndProc(SCI_SETSELECTIONNEND, selection_num, endByte);
638 return TRUE;
641 void ScintillaGTKAccessible::AtkTextIface::init(::AtkTextIface *iface) {
642 iface->get_text = GetText;
643 iface->get_text_after_offset = GetTextAfterOffset;
644 iface->get_text_at_offset = GetTextAtOffset;
645 iface->get_text_before_offset = GetTextBeforeOffset;
646 #if ATK_CHECK_VERSION(2, 10, 0)
647 iface->get_string_at_offset = GetStringAtOffset;
648 #endif
649 iface->get_character_at_offset = GetCharacterAtOffset;
650 iface->get_character_count = GetCharacterCount;
651 iface->get_caret_offset = GetCaretOffset;
652 iface->set_caret_offset = SetCaretOffset;
653 iface->get_offset_at_point = GetOffsetAtPoint;
654 iface->get_character_extents = GetCharacterExtents;
655 iface->get_n_selections = GetNSelections;
656 iface->get_selection = GetSelection;
657 iface->add_selection = AddSelection;
658 iface->remove_selection = RemoveSelection;
659 iface->set_selection = SetSelection;
660 iface->get_run_attributes = GetRunAttributes;
661 iface->get_default_attributes = GetDefaultAttributes;
664 /* atkeditabletext.h */
666 void ScintillaGTKAccessible::SetTextContents(const gchar *contents) {
667 // FIXME: it's probably useless to check for READONLY here, SETTEXT probably does it just fine?
668 if (! sci->pdoc->IsReadOnly()) {
669 sci->WndProc(SCI_SETTEXT, 0, (sptr_t) contents);
673 bool ScintillaGTKAccessible::InsertStringUTF8(Sci::Position bytePos, const gchar *utf8, Sci::Position lengthBytes) {
674 if (sci->pdoc->IsReadOnly()) {
675 return false;
678 // like EncodedFromUTF8(), but avoids an extra copy
679 // FIXME: update target?
680 const char *charSetBuffer;
681 if (sci->IsUnicodeMode() || ! *(charSetBuffer = sci->CharacterSetID())) {
682 sci->pdoc->InsertString(bytePos, utf8, lengthBytes);
683 } else {
684 // conversion needed
685 std::string encoded = ConvertText(utf8, lengthBytes, charSetBuffer, "UTF-8", true);
686 sci->pdoc->InsertString(bytePos, encoded.c_str(), encoded.length());
689 return true;
692 void ScintillaGTKAccessible::InsertText(const gchar *text, int lengthBytes, int *charPosition) {
693 Sci::Position bytePosition = ByteOffsetFromCharacterOffset(*charPosition);
695 // FIXME: should we update the target?
696 if (InsertStringUTF8(bytePosition, text, lengthBytes)) {
697 (*charPosition) += sci->pdoc->CountCharacters(bytePosition, lengthBytes);
701 void ScintillaGTKAccessible::CopyText(int startChar, int endChar) {
702 Sci::Position startByte, endByte;
703 ByteRangeFromCharacterRange(startChar, endChar, startByte, endByte);
704 sci->CopyRangeToClipboard(startByte, endByte);
707 void ScintillaGTKAccessible::CutText(int startChar, int endChar) {
708 g_return_if_fail(endChar >= startChar);
710 if (! sci->pdoc->IsReadOnly()) {
711 // FIXME: have a byte variant of those and convert only once?
712 CopyText(startChar, endChar);
713 DeleteText(startChar, endChar);
717 void ScintillaGTKAccessible::DeleteText(int startChar, int endChar) {
718 g_return_if_fail(endChar >= startChar);
720 if (! sci->pdoc->IsReadOnly()) {
721 Sci::Position startByte, endByte;
722 ByteRangeFromCharacterRange(startChar, endChar, startByte, endByte);
724 if (! sci->RangeContainsProtected(startByte, endByte)) {
725 // FIXME: restore the target?
726 sci->pdoc->DeleteChars(startByte, endByte - startByte);
731 void ScintillaGTKAccessible::PasteText(int charPosition) {
732 if (sci->pdoc->IsReadOnly())
733 return;
735 // Helper class holding the position for the asynchronous paste operation.
736 // We can only hope that when the callback gets called scia is still valid, but ScintillaGTK
737 // has always done that without problems, so let's guess it's a fairly safe bet.
738 struct Helper : GObjectWatcher {
739 ScintillaGTKAccessible *scia;
740 Sci::Position bytePosition;
742 void Destroyed() override {
743 scia = 0;
746 Helper(ScintillaGTKAccessible *scia_, Sci::Position bytePos_) :
747 GObjectWatcher(G_OBJECT(scia_->sci->sci)),
748 scia(scia_),
749 bytePosition(bytePos_) {
752 void TextReceived(GtkClipboard *, const gchar *text) {
753 if (text) {
754 size_t len = strlen(text);
755 std::string convertedText;
756 if (len > 0 && scia->sci->convertPastes) {
757 // Convert line endings of the paste into our local line-endings mode
758 convertedText = Document::TransformLineEnds(text, len, scia->sci->pdoc->eolMode);
759 len = convertedText.length();
760 text = convertedText.c_str();
762 scia->InsertStringUTF8(bytePosition, text, static_cast<Sci::Position>(len));
766 static void TextReceivedCallback(GtkClipboard *clipboard, const gchar *text, gpointer data) {
767 Helper *helper = reinterpret_cast<Helper*>(data);
768 try {
769 if (helper->scia != 0) {
770 helper->TextReceived(clipboard, text);
772 } catch (...) {}
773 delete helper;
777 Helper *helper = new Helper(this, ByteOffsetFromCharacterOffset(charPosition));
778 GtkWidget *widget = gtk_accessible_get_widget(accessible);
779 GtkClipboard *clipboard = gtk_widget_get_clipboard(widget, GDK_SELECTION_CLIPBOARD);
780 gtk_clipboard_request_text(clipboard, helper->TextReceivedCallback, helper);
783 void ScintillaGTKAccessible::AtkEditableTextIface::init(::AtkEditableTextIface *iface) {
784 iface->set_text_contents = SetTextContents;
785 iface->insert_text = InsertText;
786 iface->copy_text = CopyText;
787 iface->cut_text = CutText;
788 iface->delete_text = DeleteText;
789 iface->paste_text = PasteText;
790 //~ iface->set_run_attributes = SetRunAttributes;
793 bool ScintillaGTKAccessible::Enabled() const {
794 return sci->accessibilityEnabled == SC_ACCESSIBILITY_ENABLED;
797 // Callbacks
799 void ScintillaGTKAccessible::UpdateCursor() {
800 Sci::Position pos = sci->WndProc(SCI_GETCURRENTPOS, 0, 0);
801 if (old_pos != pos) {
802 int charPosition = CharacterOffsetFromByteOffset(pos);
803 g_signal_emit_by_name(accessible, "text-caret-moved", charPosition);
804 old_pos = pos;
807 size_t n_selections = sci->sel.Count();
808 size_t prev_n_selections = old_sels.size();
809 bool selection_changed = n_selections != prev_n_selections;
811 old_sels.resize(n_selections);
812 for (size_t i = 0; i < n_selections; i++) {
813 SelectionRange &sel = sci->sel.Range(i);
815 if (i < prev_n_selections && ! selection_changed) {
816 SelectionRange &old_sel = old_sels[i];
817 // do not consider a caret move to be a selection change
818 selection_changed = ((! old_sel.Empty() || ! sel.Empty()) && ! (old_sel == sel));
821 old_sels[i] = sel;
824 if (selection_changed)
825 g_signal_emit_by_name(accessible, "text-selection-changed");
828 void ScintillaGTKAccessible::ChangeDocument(Document *oldDoc, Document *newDoc) {
829 if (!Enabled()) {
830 return;
833 if (oldDoc == newDoc) {
834 return;
837 if (oldDoc) {
838 int charLength = oldDoc->CountCharacters(0, oldDoc->Length());
839 g_signal_emit_by_name(accessible, "text-changed::delete", 0, charLength);
842 if (newDoc) {
843 PLATFORM_ASSERT(newDoc == sci->pdoc);
845 int charLength = newDoc->CountCharacters(0, newDoc->Length());
846 g_signal_emit_by_name(accessible, "text-changed::insert", 0, charLength);
848 if ((oldDoc ? oldDoc->IsReadOnly() : false) != newDoc->IsReadOnly()) {
849 NotifyReadOnly();
852 // update cursor and selection
853 old_pos = -1;
854 old_sels.clear();
855 UpdateCursor();
859 void ScintillaGTKAccessible::NotifyReadOnly() {
860 bool readonly = sci->pdoc->IsReadOnly();
861 atk_object_notify_state_change(ATK_OBJECT(accessible), ATK_STATE_EDITABLE, ! readonly);
862 #if ATK_CHECK_VERSION(2, 16, 0)
863 atk_object_notify_state_change(ATK_OBJECT(accessible), ATK_STATE_READ_ONLY, readonly);
864 #endif
867 void ScintillaGTKAccessible::SetAccessibility() {
868 // Called by ScintillaGTK when application has enabled or disabled accessibility
869 character_offsets.resize(0);
870 character_offsets.push_back(0);
873 void ScintillaGTKAccessible::Notify(GtkWidget *, gint, SCNotification *nt) {
874 if (!Enabled())
875 return;
876 switch (nt->nmhdr.code) {
877 case SCN_MODIFIED: {
878 if (nt->modificationType & (SC_MOD_INSERTTEXT | SC_MOD_DELETETEXT)) {
879 // invalidate character offset cache if applicable
880 const Sci::Line line = sci->pdoc->LineFromPosition(nt->position);
881 if (character_offsets.size() > static_cast<size_t>(line + 1)) {
882 character_offsets.resize(line + 1);
885 if (nt->modificationType & SC_MOD_INSERTTEXT) {
886 int startChar = CharacterOffsetFromByteOffset(nt->position);
887 int lengthChar = sci->pdoc->CountCharacters(nt->position, nt->position + nt->length);
888 g_signal_emit_by_name(accessible, "text-changed::insert", startChar, lengthChar);
889 UpdateCursor();
891 if (nt->modificationType & SC_MOD_BEFOREDELETE) {
892 // We cannot compute the deletion length in DELETETEXT as it requires accessing the
893 // buffer, so that the character are still present. So, we cache the value here,
894 // and use it in DELETETEXT that fires quickly after.
895 deletionLengthChar = sci->pdoc->CountCharacters(nt->position, nt->position + nt->length);
897 if (nt->modificationType & SC_MOD_DELETETEXT) {
898 int startChar = CharacterOffsetFromByteOffset(nt->position);
899 g_signal_emit_by_name(accessible, "text-changed::delete", startChar, deletionLengthChar);
900 UpdateCursor();
902 if (nt->modificationType & SC_MOD_CHANGESTYLE) {
903 g_signal_emit_by_name(accessible, "text-attributes-changed");
905 } break;
906 case SCN_UPDATEUI: {
907 if (nt->updated & SC_UPDATE_SELECTION) {
908 UpdateCursor();
910 } break;
914 // ATK method wrappers
916 // wraps a call from the accessible object to the ScintillaGTKAccessible, and avoid leaking any exception
917 #define WRAPPER_METHOD_BODY(accessible, call, defret) \
918 try { \
919 ScintillaGTKAccessible *thisAccessible = FromAccessible(reinterpret_cast<GtkAccessible*>(accessible)); \
920 if (thisAccessible) { \
921 return thisAccessible->call; \
922 } else { \
923 return defret; \
925 } catch (...) { \
926 return defret; \
929 // AtkText
930 gchar *ScintillaGTKAccessible::AtkTextIface::GetText(AtkText *text, int start_offset, int end_offset) {
931 WRAPPER_METHOD_BODY(text, GetText(start_offset, end_offset), NULL);
933 gchar *ScintillaGTKAccessible::AtkTextIface::GetTextAfterOffset(AtkText *text, int offset, AtkTextBoundary boundary_type, int *start_offset, int *end_offset) {
934 WRAPPER_METHOD_BODY(text, GetTextAfterOffset(offset, boundary_type, start_offset, end_offset), NULL)
936 gchar *ScintillaGTKAccessible::AtkTextIface::GetTextBeforeOffset(AtkText *text, int offset, AtkTextBoundary boundary_type, int *start_offset, int *end_offset) {
937 WRAPPER_METHOD_BODY(text, GetTextBeforeOffset(offset, boundary_type, start_offset, end_offset), NULL)
939 gchar *ScintillaGTKAccessible::AtkTextIface::GetTextAtOffset(AtkText *text, gint offset, AtkTextBoundary boundary_type, gint *start_offset, gint *end_offset) {
940 WRAPPER_METHOD_BODY(text, GetTextAtOffset(offset, boundary_type, start_offset, end_offset), NULL)
942 #if ATK_CHECK_VERSION(2, 10, 0)
943 gchar *ScintillaGTKAccessible::AtkTextIface::GetStringAtOffset(AtkText *text, gint offset, AtkTextGranularity granularity, gint *start_offset, gint *end_offset) {
944 WRAPPER_METHOD_BODY(text, GetStringAtOffset(offset, granularity, start_offset, end_offset), NULL)
946 #endif
947 gunichar ScintillaGTKAccessible::AtkTextIface::GetCharacterAtOffset(AtkText *text, gint offset) {
948 WRAPPER_METHOD_BODY(text, GetCharacterAtOffset(offset), 0)
950 gint ScintillaGTKAccessible::AtkTextIface::GetCharacterCount(AtkText *text) {
951 WRAPPER_METHOD_BODY(text, GetCharacterCount(), 0)
953 gint ScintillaGTKAccessible::AtkTextIface::GetCaretOffset(AtkText *text) {
954 WRAPPER_METHOD_BODY(text, GetCaretOffset(), 0)
956 gboolean ScintillaGTKAccessible::AtkTextIface::SetCaretOffset(AtkText *text, gint offset) {
957 WRAPPER_METHOD_BODY(text, SetCaretOffset(offset), FALSE)
959 gint ScintillaGTKAccessible::AtkTextIface::GetOffsetAtPoint(AtkText *text, gint x, gint y, AtkCoordType coords) {
960 WRAPPER_METHOD_BODY(text, GetOffsetAtPoint(x, y, coords), -1)
962 void ScintillaGTKAccessible::AtkTextIface::GetCharacterExtents(AtkText *text, gint offset, gint *x, gint *y, gint *width, gint *height, AtkCoordType coords) {
963 WRAPPER_METHOD_BODY(text, GetCharacterExtents(offset, x, y, width, height, coords), )
965 AtkAttributeSet *ScintillaGTKAccessible::AtkTextIface::GetRunAttributes(AtkText *text, gint offset, gint *start_offset, gint *end_offset) {
966 WRAPPER_METHOD_BODY(text, GetRunAttributes(offset, start_offset, end_offset), NULL)
968 AtkAttributeSet *ScintillaGTKAccessible::AtkTextIface::GetDefaultAttributes(AtkText *text) {
969 WRAPPER_METHOD_BODY(text, GetDefaultAttributes(), NULL)
971 gint ScintillaGTKAccessible::AtkTextIface::GetNSelections(AtkText *text) {
972 WRAPPER_METHOD_BODY(text, GetNSelections(), 0)
974 gchar *ScintillaGTKAccessible::AtkTextIface::GetSelection(AtkText *text, gint selection_num, gint *start_pos, gint *end_pos) {
975 WRAPPER_METHOD_BODY(text, GetSelection(selection_num, start_pos, end_pos), NULL)
977 gboolean ScintillaGTKAccessible::AtkTextIface::AddSelection(AtkText *text, gint start, gint end) {
978 WRAPPER_METHOD_BODY(text, AddSelection(start, end), FALSE)
980 gboolean ScintillaGTKAccessible::AtkTextIface::RemoveSelection(AtkText *text, gint selection_num) {
981 WRAPPER_METHOD_BODY(text, RemoveSelection(selection_num), FALSE)
983 gboolean ScintillaGTKAccessible::AtkTextIface::SetSelection(AtkText *text, gint selection_num, gint start, gint end) {
984 WRAPPER_METHOD_BODY(text, SetSelection(selection_num, start, end), FALSE)
986 // AtkEditableText
987 void ScintillaGTKAccessible::AtkEditableTextIface::SetTextContents(AtkEditableText *text, const gchar *contents) {
988 WRAPPER_METHOD_BODY(text, SetTextContents(contents), )
990 void ScintillaGTKAccessible::AtkEditableTextIface::InsertText(AtkEditableText *text, const gchar *contents, gint length, gint *position) {
991 WRAPPER_METHOD_BODY(text, InsertText(contents, length, position), )
993 void ScintillaGTKAccessible::AtkEditableTextIface::CopyText(AtkEditableText *text, gint start, gint end) {
994 WRAPPER_METHOD_BODY(text, CopyText(start, end), )
996 void ScintillaGTKAccessible::AtkEditableTextIface::CutText(AtkEditableText *text, gint start, gint end) {
997 WRAPPER_METHOD_BODY(text, CutText(start, end), )
999 void ScintillaGTKAccessible::AtkEditableTextIface::DeleteText(AtkEditableText *text, gint start, gint end) {
1000 WRAPPER_METHOD_BODY(text, DeleteText(start, end), )
1002 void ScintillaGTKAccessible::AtkEditableTextIface::PasteText(AtkEditableText *text, gint position) {
1003 WRAPPER_METHOD_BODY(text, PasteText(position), )
1006 // GObject glue
1008 #if HAVE_GTK_FACTORY
1009 static GType scintilla_object_accessible_factory_get_type(void);
1010 #endif
1012 static void scintilla_object_accessible_init(ScintillaObjectAccessible *accessible);
1013 static void scintilla_object_accessible_class_init(ScintillaObjectAccessibleClass *klass);
1014 static gpointer scintilla_object_accessible_parent_class = NULL;
1017 // @p parent_type is only required on GTK 3.2 to 3.6, and only on the first call
1018 static GType scintilla_object_accessible_get_type(GType parent_type G_GNUC_UNUSED) {
1019 static volatile gsize type_id_result = 0;
1021 if (g_once_init_enter(&type_id_result)) {
1022 GTypeInfo tinfo = {
1023 0, /* class size */
1024 (GBaseInitFunc) NULL, /* base init */
1025 (GBaseFinalizeFunc) NULL, /* base finalize */
1026 (GClassInitFunc) scintilla_object_accessible_class_init, /* class init */
1027 (GClassFinalizeFunc) NULL, /* class finalize */
1028 NULL, /* class data */
1029 0, /* instance size */
1030 0, /* nb preallocs */
1031 (GInstanceInitFunc) scintilla_object_accessible_init, /* instance init */
1032 NULL /* value table */
1035 const GInterfaceInfo atk_text_info = {
1036 (GInterfaceInitFunc) ScintillaGTKAccessible::AtkTextIface::init,
1037 (GInterfaceFinalizeFunc) NULL,
1038 NULL
1041 const GInterfaceInfo atk_editable_text_info = {
1042 (GInterfaceInitFunc) ScintillaGTKAccessible::AtkEditableTextIface::init,
1043 (GInterfaceFinalizeFunc) NULL,
1044 NULL
1047 #if HAVE_GTK_A11Y_H
1048 // good, we have gtk-a11y.h, we can use that
1049 GType derived_atk_type = GTK_TYPE_CONTAINER_ACCESSIBLE;
1050 tinfo.class_size = sizeof (GtkContainerAccessibleClass);
1051 tinfo.instance_size = sizeof (GtkContainerAccessible);
1052 #else // ! HAVE_GTK_A11Y_H
1053 # if HAVE_GTK_FACTORY
1054 // Figure out the size of the class and instance we are deriving from through the registry
1055 GType derived_type = g_type_parent(SCINTILLA_TYPE_OBJECT);
1056 AtkObjectFactory *factory = atk_registry_get_factory(atk_get_default_registry(), derived_type);
1057 GType derived_atk_type = atk_object_factory_get_accessible_type(factory);
1058 # else // ! HAVE_GTK_FACTORY
1059 // We're kind of screwed and can't determine the parent (no registry, and no public type)
1060 // Hack your way around by requiring the caller to give us our parent type. The caller
1061 // might be able to trick its way into doing that, by e.g. instantiating the parent's
1062 // accessible type and get its GType. It's ugly but we can't do better on GTK 3.2 to 3.6.
1063 g_assert(parent_type != 0);
1065 GType derived_atk_type = parent_type;
1066 # endif // ! HAVE_GTK_FACTORY
1068 GTypeQuery query;
1069 g_type_query(derived_atk_type, &query);
1070 tinfo.class_size = query.class_size;
1071 tinfo.instance_size = query.instance_size;
1072 #endif // ! HAVE_GTK_A11Y_H
1074 GType type_id = g_type_register_static(derived_atk_type, "ScintillaObjectAccessible", &tinfo, (GTypeFlags) 0);
1075 g_type_add_interface_static(type_id, ATK_TYPE_TEXT, &atk_text_info);
1076 g_type_add_interface_static(type_id, ATK_TYPE_EDITABLE_TEXT, &atk_editable_text_info);
1078 g_once_init_leave(&type_id_result, type_id);
1081 return type_id_result;
1084 static AtkObject *scintilla_object_accessible_new(GType parent_type, GObject *obj) {
1085 g_return_val_if_fail(SCINTILLA_IS_OBJECT(obj), NULL);
1087 AtkObject *accessible = (AtkObject *) g_object_new(scintilla_object_accessible_get_type(parent_type),
1088 #if HAVE_WIDGET_SET_UNSET
1089 "widget", obj,
1090 #endif
1091 NULL);
1092 atk_object_initialize(accessible, obj);
1094 return accessible;
1097 // implementation for gtk_widget_get_accessible().
1098 // See the comment at the top of the file for details on the implementation
1099 // @p widget the widget.
1100 // @p cache pointer to store the AtkObject between repeated calls. Might or might not be filled.
1101 // @p widget_parent_class pointer to the widget's parent class (to chain up method calls).
1102 AtkObject *ScintillaGTKAccessible::WidgetGetAccessibleImpl(GtkWidget *widget, AtkObject **cache, gpointer widget_parent_class G_GNUC_UNUSED) {
1103 if (*cache != NULL) {
1104 return *cache;
1107 #if HAVE_GTK_A11Y_H // just instantiate the accessible
1108 *cache = scintilla_object_accessible_new(0, G_OBJECT(widget));
1109 #elif HAVE_GTK_FACTORY // register in the factory and let GTK instantiate
1110 static volatile gsize registered = 0;
1112 if (g_once_init_enter(&registered)) {
1113 // Figure out whether accessibility is enabled by looking at the type of the accessible
1114 // object which would be created for the parent type of ScintillaObject.
1115 GType derived_type = g_type_parent(SCINTILLA_TYPE_OBJECT);
1117 AtkRegistry *registry = atk_get_default_registry();
1118 AtkObjectFactory *factory = atk_registry_get_factory(registry, derived_type);
1119 GType derived_atk_type = atk_object_factory_get_accessible_type(factory);
1120 if (g_type_is_a(derived_atk_type, GTK_TYPE_ACCESSIBLE)) {
1121 atk_registry_set_factory_type(registry, SCINTILLA_TYPE_OBJECT,
1122 scintilla_object_accessible_factory_get_type());
1124 g_once_init_leave(&registered, 1);
1126 AtkObject *obj = GTK_WIDGET_CLASS(widget_parent_class)->get_accessible(widget);
1127 *cache = static_cast<AtkObject*>(g_object_ref(obj));
1128 #else // no public API, no factory, so guess from the parent and instantiate
1129 static GType parent_atk_type = 0;
1131 if (parent_atk_type == 0) {
1132 AtkObject *parent_obj = GTK_WIDGET_CLASS(widget_parent_class)->get_accessible(widget);
1133 if (parent_obj) {
1134 GType parent_atk_type = G_OBJECT_TYPE(parent_obj);
1136 // Figure out whether accessibility is enabled by looking at the type of the accessible
1137 // object which would be created for the parent type of ScintillaObject.
1138 if (g_type_is_a(parent_atk_type, GTK_TYPE_ACCESSIBLE)) {
1139 *cache = scintilla_object_accessible_new(parent_atk_type, G_OBJECT(widget));
1140 } else {
1141 *cache = static_cast<AtkObject*>(g_object_ref(parent_obj));
1145 #endif
1146 return *cache;
1149 static AtkStateSet *scintilla_object_accessible_ref_state_set(AtkObject *accessible) {
1150 AtkStateSet *state_set = ATK_OBJECT_CLASS(scintilla_object_accessible_parent_class)->ref_state_set(accessible);
1152 GtkWidget *widget = gtk_accessible_get_widget(GTK_ACCESSIBLE(accessible));
1153 if (widget == NULL) {
1154 atk_state_set_add_state(state_set, ATK_STATE_DEFUNCT);
1155 } else {
1156 if (! scintilla_send_message(SCINTILLA_OBJECT(widget), SCI_GETREADONLY, 0, 0))
1157 atk_state_set_add_state(state_set, ATK_STATE_EDITABLE);
1158 #if ATK_CHECK_VERSION(2, 16, 0)
1159 else
1160 atk_state_set_add_state(state_set, ATK_STATE_READ_ONLY);
1161 #endif
1162 atk_state_set_add_state(state_set, ATK_STATE_MULTI_LINE);
1163 atk_state_set_add_state(state_set, ATK_STATE_MULTISELECTABLE);
1164 atk_state_set_add_state(state_set, ATK_STATE_SELECTABLE_TEXT);
1165 /*atk_state_set_add_state(state_set, ATK_STATE_SUPPORTS_AUTOCOMPLETION);*/
1168 return state_set;
1171 static void scintilla_object_accessible_widget_set(GtkAccessible *accessible) {
1172 GtkWidget *widget = gtk_accessible_get_widget(accessible);
1173 if (widget == NULL)
1174 return;
1176 ScintillaObjectAccessiblePrivate *priv = SCINTILLA_OBJECT_ACCESSIBLE_GET_PRIVATE(accessible);
1177 if (priv->pscin != 0)
1178 delete priv->pscin;
1179 priv->pscin = new ScintillaGTKAccessible(accessible, widget);
1182 #if HAVE_WIDGET_SET_UNSET
1183 static void scintilla_object_accessible_widget_unset(GtkAccessible *accessible) {
1184 GtkWidget *widget = gtk_accessible_get_widget(accessible);
1185 if (widget == NULL)
1186 return;
1188 ScintillaObjectAccessiblePrivate *priv = SCINTILLA_OBJECT_ACCESSIBLE_GET_PRIVATE(accessible);
1189 delete priv->pscin;
1190 priv->pscin = 0;
1192 #endif
1194 static void scintilla_object_accessible_initialize(AtkObject *obj, gpointer data) {
1195 ATK_OBJECT_CLASS(scintilla_object_accessible_parent_class)->initialize(obj, data);
1197 #if ! HAVE_WIDGET_SET_UNSET
1198 scintilla_object_accessible_widget_set(GTK_ACCESSIBLE(obj));
1199 #endif
1201 obj->role = ATK_ROLE_TEXT;
1204 static void scintilla_object_accessible_finalize(GObject *object) {
1205 ScintillaObjectAccessiblePrivate *priv = SCINTILLA_OBJECT_ACCESSIBLE_GET_PRIVATE(object);
1207 if (priv->pscin) {
1208 delete priv->pscin;
1209 priv->pscin = 0;
1212 G_OBJECT_CLASS(scintilla_object_accessible_parent_class)->finalize(object);
1215 static void scintilla_object_accessible_class_init(ScintillaObjectAccessibleClass *klass) {
1216 GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
1217 AtkObjectClass *object_class = ATK_OBJECT_CLASS(klass);
1219 #if HAVE_WIDGET_SET_UNSET
1220 GtkAccessibleClass *accessible_class = GTK_ACCESSIBLE_CLASS(klass);
1221 accessible_class->widget_set = scintilla_object_accessible_widget_set;
1222 accessible_class->widget_unset = scintilla_object_accessible_widget_unset;
1223 #endif
1225 object_class->ref_state_set = scintilla_object_accessible_ref_state_set;
1226 object_class->initialize = scintilla_object_accessible_initialize;
1228 gobject_class->finalize = scintilla_object_accessible_finalize;
1230 scintilla_object_accessible_parent_class = g_type_class_peek_parent(klass);
1232 g_type_class_add_private(klass, sizeof (ScintillaObjectAccessiblePrivate));
1235 static void scintilla_object_accessible_init(ScintillaObjectAccessible *accessible) {
1236 ScintillaObjectAccessiblePrivate *priv = SCINTILLA_OBJECT_ACCESSIBLE_GET_PRIVATE(accessible);
1238 priv->pscin = 0;
1241 #if HAVE_GTK_FACTORY
1242 // Object factory
1243 typedef AtkObjectFactory ScintillaObjectAccessibleFactory;
1244 typedef AtkObjectFactoryClass ScintillaObjectAccessibleFactoryClass;
1246 G_DEFINE_TYPE(ScintillaObjectAccessibleFactory, scintilla_object_accessible_factory, ATK_TYPE_OBJECT_FACTORY)
1248 static void scintilla_object_accessible_factory_init(ScintillaObjectAccessibleFactory *) {
1251 static GType scintilla_object_accessible_factory_get_accessible_type(void) {
1252 return SCINTILLA_TYPE_OBJECT_ACCESSIBLE;
1255 static AtkObject *scintilla_object_accessible_factory_create_accessible(GObject *obj) {
1256 return scintilla_object_accessible_new(0, obj);
1259 static void scintilla_object_accessible_factory_class_init(AtkObjectFactoryClass * klass) {
1260 klass->create_accessible = scintilla_object_accessible_factory_create_accessible;
1261 klass->get_accessible_type = scintilla_object_accessible_factory_get_accessible_type;
1263 #endif