Merge pull request #3720 from b4n/encodings-ui-improvements
[geany-mirror.git] / scintilla / gtk / ScintillaGTKAccessible.cxx
blobae6b0fb353825722392047709fdcc8c85999a071
1 /* Scintilla source code edit control */
2 /* ScintillaGTKAccessible.cxx - 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 <cstdint>
57 #include <cassert>
58 #include <cstring>
60 #include <stdexcept>
61 #include <new>
62 #include <string>
63 #include <string_view>
64 #include <vector>
65 #include <map>
66 #include <set>
67 #include <optional>
68 #include <algorithm>
69 #include <memory>
71 #include <glib.h>
72 #include <gtk/gtk.h>
74 // whether we have widget_set() and widget_unset()
75 #define HAVE_WIDGET_SET_UNSET (GTK_CHECK_VERSION(3, 3, 6))
76 // whether GTK accessibility is available through the ATK factory
77 #define HAVE_GTK_FACTORY (! GTK_CHECK_VERSION(3, 1, 9))
78 // whether we have gtk-a11y.h and the public GTK accessible types
79 #define HAVE_GTK_A11Y_H (GTK_CHECK_VERSION(3, 7, 6))
81 #if HAVE_GTK_A11Y_H
82 # include <gtk/gtk-a11y.h>
83 #endif
85 #if defined(_WIN32)
86 // On Win32 use windows.h to access CLIPFORMAT
87 #undef NOMINMAX
88 #define NOMINMAX
89 #include <windows.h>
90 #endif
92 // ScintillaGTK.h and stuff it needs
93 #include "ScintillaTypes.h"
94 #include "ScintillaMessages.h"
95 #include "ScintillaStructures.h"
96 #include "ILoader.h"
97 #include "ILexer.h"
99 #include "Debugging.h"
100 #include "Geometry.h"
101 #include "Platform.h"
103 #include "Scintilla.h"
104 #include "ScintillaWidget.h"
105 #include "CharacterCategoryMap.h"
106 #include "Position.h"
107 #include "UniqueString.h"
108 #include "SplitVector.h"
109 #include "Partitioning.h"
110 #include "RunStyles.h"
111 #include "ContractionState.h"
112 #include "CellBuffer.h"
113 #include "CallTip.h"
114 #include "KeyMap.h"
115 #include "Indicator.h"
116 #include "LineMarker.h"
117 #include "Style.h"
118 #include "ViewStyle.h"
119 #include "CharClassify.h"
120 #include "Decoration.h"
121 #include "CaseFolder.h"
122 #include "Document.h"
123 #include "CaseConvert.h"
124 #include "UniConversion.h"
125 #include "Selection.h"
126 #include "PositionCache.h"
127 #include "EditModel.h"
128 #include "MarginView.h"
129 #include "EditView.h"
130 #include "Editor.h"
131 #include "AutoComplete.h"
132 #include "ScintillaBase.h"
134 #include "Wrappers.h"
135 #include "ScintillaGTK.h"
136 #include "ScintillaGTKAccessible.h"
138 using namespace Scintilla;
139 using namespace Scintilla::Internal;
141 struct ScintillaObjectAccessiblePrivate {
142 ScintillaGTKAccessible *pscin;
145 typedef GtkAccessible ScintillaObjectAccessible;
146 typedef GtkAccessibleClass ScintillaObjectAccessibleClass;
148 #define SCINTILLA_OBJECT_ACCESSIBLE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), SCINTILLA_TYPE_OBJECT_ACCESSIBLE, ScintillaObjectAccessible))
149 #define SCINTILLA_TYPE_OBJECT_ACCESSIBLE (scintilla_object_accessible_get_type(0))
151 // We can't use priv member because of dynamic inheritance, so we don't actually know the offset. Meh.
152 #define SCINTILLA_OBJECT_ACCESSIBLE_GET_PRIVATE(inst) (G_TYPE_INSTANCE_GET_PRIVATE((inst), SCINTILLA_TYPE_OBJECT_ACCESSIBLE, ScintillaObjectAccessiblePrivate))
154 static GType scintilla_object_accessible_get_type(GType parent_type);
156 ScintillaGTKAccessible *ScintillaGTKAccessible::FromAccessible(GtkAccessible *accessible) {
157 // FIXME: do we need the check below? GTK checks that in all methods, so maybe
158 GtkWidget *widget = gtk_accessible_get_widget(accessible);
159 if (! widget) {
160 return nullptr;
163 return SCINTILLA_OBJECT_ACCESSIBLE_GET_PRIVATE(accessible)->pscin;
166 ScintillaGTKAccessible::ScintillaGTKAccessible(GtkAccessible *accessible_, GtkWidget *widget_) :
167 accessible(accessible_),
168 sci(ScintillaGTK::FromWidget(widget_)),
169 old_pos(-1) {
170 SetAccessibility(true);
171 g_signal_connect(widget_, "sci-notify", G_CALLBACK(SciNotify), this);
174 ScintillaGTKAccessible::~ScintillaGTKAccessible() {
175 if (gtk_accessible_get_widget(accessible)) {
176 g_signal_handlers_disconnect_matched(sci->sci, G_SIGNAL_MATCH_DATA, 0, 0, nullptr, nullptr, this);
180 gchar *ScintillaGTKAccessible::GetTextRangeUTF8(Sci::Position startByte, Sci::Position endByte) {
181 g_return_val_if_fail(startByte >= 0, nullptr);
182 // FIXME: should we swap start/end if necessary?
183 g_return_val_if_fail(endByte >= startByte, nullptr);
185 gchar *utf8Text = nullptr;
186 const char *charSetBuffer;
188 // like TargetAsUTF8, but avoids a double conversion
189 if (sci->IsUnicodeMode() || ! *(charSetBuffer = sci->CharacterSetID())) {
190 int len = endByte - startByte;
191 utf8Text = static_cast<gchar *>(g_malloc(len + 1));
192 sci->pdoc->GetCharRange(utf8Text, startByte, len);
193 utf8Text[len] = '\0';
194 } else {
195 // Need to convert
196 std::string s = sci->RangeText(startByte, endByte);
197 std::string tmputf = ConvertText(&s[0], s.length(), "UTF-8", charSetBuffer, false);
198 size_t len = tmputf.length();
199 utf8Text = static_cast<gchar *>(g_malloc(len + 1));
200 memcpy(utf8Text, tmputf.c_str(), len);
201 utf8Text[len] = '\0';
204 return utf8Text;
207 gchar *ScintillaGTKAccessible::GetText(int startChar, int endChar) {
208 Sci::Position startByte, endByte;
209 if (endChar == -1) {
210 startByte = ByteOffsetFromCharacterOffset(startChar);
211 endByte = sci->pdoc->Length();
212 } else {
213 ByteRangeFromCharacterRange(startChar, endChar, startByte, endByte);
215 return GetTextRangeUTF8(startByte, endByte);
218 gchar *ScintillaGTKAccessible::GetTextAfterOffset(int charOffset,
219 AtkTextBoundary boundaryType, int *startChar, int *endChar) {
220 g_return_val_if_fail(charOffset >= 0, nullptr);
222 Sci::Position startByte, endByte;
223 Sci::Position byteOffset = ByteOffsetFromCharacterOffset(charOffset);
225 switch (boundaryType) {
226 case ATK_TEXT_BOUNDARY_CHAR:
227 startByte = PositionAfter(byteOffset);
228 endByte = PositionAfter(startByte);
229 // FIXME: optimize conversion back, as we can reasonably assume +1 char?
230 break;
232 case ATK_TEXT_BOUNDARY_WORD_START:
233 startByte = sci->WndProc(Message::WordEndPosition, byteOffset, 1);
234 startByte = sci->WndProc(Message::WordEndPosition, startByte, 0);
235 endByte = sci->WndProc(Message::WordEndPosition, startByte, 1);
236 endByte = sci->WndProc(Message::WordEndPosition, endByte, 0);
237 break;
239 case ATK_TEXT_BOUNDARY_WORD_END:
240 startByte = sci->WndProc(Message::WordEndPosition, byteOffset, 0);
241 startByte = sci->WndProc(Message::WordEndPosition, startByte, 1);
242 endByte = sci->WndProc(Message::WordEndPosition, startByte, 0);
243 endByte = sci->WndProc(Message::WordEndPosition, endByte, 1);
244 break;
246 case ATK_TEXT_BOUNDARY_LINE_START: {
247 int line = sci->WndProc(Message::LineFromPosition, byteOffset, 0);
248 startByte = sci->WndProc(Message::PositionFromLine, line + 1, 0);
249 endByte = sci->WndProc(Message::PositionFromLine, line + 2, 0);
250 break;
253 case ATK_TEXT_BOUNDARY_LINE_END: {
254 int line = sci->WndProc(Message::LineFromPosition, byteOffset, 0);
255 startByte = sci->WndProc(Message::GetLineEndPosition, line, 0);
256 endByte = sci->WndProc(Message::GetLineEndPosition, line + 1, 0);
257 break;
260 default:
261 *startChar = *endChar = -1;
262 return nullptr;
265 CharacterRangeFromByteRange(startByte, endByte, startChar, endChar);
266 return GetTextRangeUTF8(startByte, endByte);
269 gchar *ScintillaGTKAccessible::GetTextBeforeOffset(int charOffset,
270 AtkTextBoundary boundaryType, int *startChar, int *endChar) {
271 g_return_val_if_fail(charOffset >= 0, nullptr);
273 Sci::Position startByte, endByte;
274 Sci::Position byteOffset = ByteOffsetFromCharacterOffset(charOffset);
276 switch (boundaryType) {
277 case ATK_TEXT_BOUNDARY_CHAR:
278 endByte = PositionBefore(byteOffset);
279 startByte = PositionBefore(endByte);
280 break;
282 case ATK_TEXT_BOUNDARY_WORD_START:
283 endByte = sci->WndProc(Message::WordStartPosition, byteOffset, 0);
284 endByte = sci->WndProc(Message::WordStartPosition, endByte, 1);
285 startByte = sci->WndProc(Message::WordStartPosition, endByte, 0);
286 startByte = sci->WndProc(Message::WordStartPosition, startByte, 1);
287 break;
289 case ATK_TEXT_BOUNDARY_WORD_END:
290 endByte = sci->WndProc(Message::WordStartPosition, byteOffset, 1);
291 endByte = sci->WndProc(Message::WordStartPosition, endByte, 0);
292 startByte = sci->WndProc(Message::WordStartPosition, endByte, 1);
293 startByte = sci->WndProc(Message::WordStartPosition, startByte, 0);
294 break;
296 case ATK_TEXT_BOUNDARY_LINE_START: {
297 int line = sci->WndProc(Message::LineFromPosition, byteOffset, 0);
298 endByte = sci->WndProc(Message::PositionFromLine, line, 0);
299 if (line > 0) {
300 startByte = sci->WndProc(Message::PositionFromLine, line - 1, 0);
301 } else {
302 startByte = endByte;
304 break;
307 case ATK_TEXT_BOUNDARY_LINE_END: {
308 int line = sci->WndProc(Message::LineFromPosition, byteOffset, 0);
309 if (line > 0) {
310 endByte = sci->WndProc(Message::GetLineEndPosition, line - 1, 0);
311 } else {
312 endByte = 0;
314 if (line > 1) {
315 startByte = sci->WndProc(Message::GetLineEndPosition, line - 2, 0);
316 } else {
317 startByte = endByte;
319 break;
322 default:
323 *startChar = *endChar = -1;
324 return nullptr;
327 CharacterRangeFromByteRange(startByte, endByte, startChar, endChar);
328 return GetTextRangeUTF8(startByte, endByte);
331 gchar *ScintillaGTKAccessible::GetTextAtOffset(int charOffset,
332 AtkTextBoundary boundaryType, int *startChar, int *endChar) {
333 g_return_val_if_fail(charOffset >= 0, nullptr);
335 Sci::Position startByte, endByte;
336 Sci::Position byteOffset = ByteOffsetFromCharacterOffset(charOffset);
338 switch (boundaryType) {
339 case ATK_TEXT_BOUNDARY_CHAR:
340 startByte = byteOffset;
341 endByte = sci->WndProc(Message::PositionAfter, byteOffset, 0);
342 break;
344 case ATK_TEXT_BOUNDARY_WORD_START:
345 startByte = sci->WndProc(Message::WordStartPosition, byteOffset, 1);
346 endByte = sci->WndProc(Message::WordEndPosition, byteOffset, 1);
347 if (! sci->WndProc(Message::IsRangeWord, startByte, endByte)) {
348 // if the cursor was not on a word, forward back
349 startByte = sci->WndProc(Message::WordStartPosition, startByte, 0);
350 startByte = sci->WndProc(Message::WordStartPosition, startByte, 1);
352 endByte = sci->WndProc(Message::WordEndPosition, endByte, 0);
353 break;
355 case ATK_TEXT_BOUNDARY_WORD_END:
356 startByte = sci->WndProc(Message::WordStartPosition, byteOffset, 1);
357 endByte = sci->WndProc(Message::WordEndPosition, byteOffset, 1);
358 if (! sci->WndProc(Message::IsRangeWord, startByte, endByte)) {
359 // if the cursor was not on a word, forward back
360 endByte = sci->WndProc(Message::WordEndPosition, endByte, 0);
361 endByte = sci->WndProc(Message::WordEndPosition, endByte, 1);
363 startByte = sci->WndProc(Message::WordStartPosition, startByte, 0);
364 break;
366 case ATK_TEXT_BOUNDARY_LINE_START: {
367 int line = sci->WndProc(Message::LineFromPosition, byteOffset, 0);
368 startByte = sci->WndProc(Message::PositionFromLine, line, 0);
369 endByte = sci->WndProc(Message::PositionFromLine, line + 1, 0);
370 break;
373 case ATK_TEXT_BOUNDARY_LINE_END: {
374 int line = sci->WndProc(Message::LineFromPosition, byteOffset, 0);
375 if (line > 0) {
376 startByte = sci->WndProc(Message::GetLineEndPosition, line - 1, 0);
377 } else {
378 startByte = 0;
380 endByte = sci->WndProc(Message::GetLineEndPosition, line, 0);
381 break;
384 default:
385 *startChar = *endChar = -1;
386 return nullptr;
389 CharacterRangeFromByteRange(startByte, endByte, startChar, endChar);
390 return GetTextRangeUTF8(startByte, endByte);
393 #if ATK_CHECK_VERSION(2, 10, 0)
394 gchar *ScintillaGTKAccessible::GetStringAtOffset(int charOffset,
395 AtkTextGranularity granularity, int *startChar, int *endChar) {
396 g_return_val_if_fail(charOffset >= 0, nullptr);
398 Sci::Position startByte, endByte;
399 Sci::Position byteOffset = ByteOffsetFromCharacterOffset(charOffset);
401 switch (granularity) {
402 case ATK_TEXT_GRANULARITY_CHAR:
403 startByte = byteOffset;
404 endByte = sci->WndProc(Message::PositionAfter, byteOffset, 0);
405 break;
406 case ATK_TEXT_GRANULARITY_WORD:
407 startByte = sci->WndProc(Message::WordStartPosition, byteOffset, 1);
408 endByte = sci->WndProc(Message::WordEndPosition, byteOffset, 1);
409 break;
410 case ATK_TEXT_GRANULARITY_LINE: {
411 gint line = sci->WndProc(Message::LineFromPosition, byteOffset, 0);
412 startByte = sci->WndProc(Message::PositionFromLine, line, 0);
413 endByte = sci->WndProc(Message::GetLineEndPosition, line, 0);
414 break;
416 default:
417 *startChar = *endChar = -1;
418 return nullptr;
421 CharacterRangeFromByteRange(startByte, endByte, startChar, endChar);
422 return GetTextRangeUTF8(startByte, endByte);
424 #endif
426 gunichar ScintillaGTKAccessible::GetCharacterAtOffset(int charOffset) {
427 g_return_val_if_fail(charOffset >= 0, 0);
429 Sci::Position startByte = ByteOffsetFromCharacterOffset(charOffset);
430 Sci::Position endByte = PositionAfter(startByte);
431 gchar *ch = GetTextRangeUTF8(startByte, endByte);
432 gunichar unichar = g_utf8_get_char_validated(ch, -1);
433 g_free(ch);
435 return unichar;
438 gint ScintillaGTKAccessible::GetCharacterCount() {
439 return sci->pdoc->CountCharacters(0, sci->pdoc->Length());
442 gint ScintillaGTKAccessible::GetCaretOffset() {
443 return CharacterOffsetFromByteOffset(sci->WndProc(Message::GetCurrentPos, 0, 0));
446 gboolean ScintillaGTKAccessible::SetCaretOffset(int charOffset) {
447 sci->WndProc(Message::GotoPos, ByteOffsetFromCharacterOffset(charOffset), 0);
448 return TRUE;
451 gint ScintillaGTKAccessible::GetOffsetAtPoint(gint x, gint y, AtkCoordType coords) {
452 gint x_widget, y_widget, x_window, y_window;
453 GtkWidget *widget = gtk_accessible_get_widget(accessible);
455 GdkWindow *window = gtk_widget_get_window(widget);
456 gdk_window_get_origin(window, &x_widget, &y_widget);
457 if (coords == ATK_XY_SCREEN) {
458 x = x - x_widget;
459 y = y - y_widget;
460 } else if (coords == ATK_XY_WINDOW) {
461 window = gdk_window_get_toplevel(window);
462 gdk_window_get_origin(window, &x_window, &y_window);
464 x = x - x_widget + x_window;
465 y = y - y_widget + y_window;
466 } else {
467 return -1;
470 // FIXME: should we handle scrolling?
471 return CharacterOffsetFromByteOffset(sci->WndProc(Message::CharPositionFromPointClose, x, y));
474 void ScintillaGTKAccessible::GetCharacterExtents(int charOffset,
475 gint *x, gint *y, gint *width, gint *height, AtkCoordType coords) {
476 *x = *y = *height = *width = 0;
478 Sci::Position byteOffset = ByteOffsetFromCharacterOffset(charOffset);
480 // FIXME: should we handle scrolling?
481 *x = sci->WndProc(Message::PointXFromPosition, 0, byteOffset);
482 *y = sci->WndProc(Message::PointYFromPosition, 0, byteOffset);
484 int line = sci->WndProc(Message::LineFromPosition, byteOffset, 0);
485 *height = sci->WndProc(Message::TextHeight, line, 0);
487 int nextByteOffset = PositionAfter(byteOffset);
488 int next_x = sci->WndProc(Message::PointXFromPosition, 0, nextByteOffset);
489 if (next_x > *x) {
490 *width = next_x - *x;
491 } else if (nextByteOffset > byteOffset) {
492 /* maybe next position was on the next line or something.
493 * just compute the expected character width */
494 int style = StyleAt(byteOffset, true);
495 int len = nextByteOffset - byteOffset;
496 char *ch = new char[len + 1];
497 sci->pdoc->GetCharRange(ch, byteOffset, len);
498 ch[len] = '\0';
499 *width = sci->TextWidth(style, ch);
500 delete[] ch;
503 GtkWidget *widget = gtk_accessible_get_widget(accessible);
504 GdkWindow *window = gtk_widget_get_window(widget);
505 int x_widget, y_widget;
506 gdk_window_get_origin(window, &x_widget, &y_widget);
507 if (coords == ATK_XY_SCREEN) {
508 *x += x_widget;
509 *y += y_widget;
510 } else if (coords == ATK_XY_WINDOW) {
511 window = gdk_window_get_toplevel(window);
512 int x_window, y_window;
513 gdk_window_get_origin(window, &x_window, &y_window);
515 *x += x_widget - x_window;
516 *y += y_widget - y_window;
517 } else {
518 *x = *y = *height = *width = 0;
522 static AtkAttributeSet *AddTextAttribute(AtkAttributeSet *attributes, AtkTextAttribute attr, gchar *value) {
523 AtkAttribute *at = g_new(AtkAttribute, 1);
524 at->name = g_strdup(atk_text_attribute_get_name(attr));
525 at->value = value;
527 return g_slist_prepend(attributes, at);
530 static AtkAttributeSet *AddTextIntAttribute(AtkAttributeSet *attributes, AtkTextAttribute attr, gint i) {
531 return AddTextAttribute(attributes, attr, g_strdup(atk_text_attribute_get_value(attr, i)));
534 static AtkAttributeSet *AddTextColorAttribute(AtkAttributeSet *attributes, AtkTextAttribute attr, ColourRGBA colour) {
535 return AddTextAttribute(attributes, attr,
536 g_strdup_printf("%u,%u,%u", colour.GetRed() * 257, colour.GetGreen() * 257, colour.GetBlue() * 257));
539 AtkAttributeSet *ScintillaGTKAccessible::GetAttributesForStyle(unsigned int styleNum) {
540 AtkAttributeSet *attr_set = nullptr;
542 if (styleNum >= sci->vs.styles.size())
543 return nullptr;
544 Style &style = sci->vs.styles[styleNum];
546 attr_set = AddTextAttribute(attr_set, ATK_TEXT_ATTR_FAMILY_NAME, g_strdup(style.fontName));
547 attr_set = AddTextAttribute(attr_set, ATK_TEXT_ATTR_SIZE, g_strdup_printf("%d", style.size / SC_FONT_SIZE_MULTIPLIER));
548 attr_set = AddTextIntAttribute(attr_set, ATK_TEXT_ATTR_WEIGHT, CLAMP(static_cast<int>(style.weight), 100, 1000));
549 attr_set = AddTextIntAttribute(attr_set, ATK_TEXT_ATTR_STYLE, style.italic ? PANGO_STYLE_ITALIC : PANGO_STYLE_NORMAL);
550 attr_set = AddTextIntAttribute(attr_set, ATK_TEXT_ATTR_UNDERLINE, style.underline ? PANGO_UNDERLINE_SINGLE : PANGO_UNDERLINE_NONE);
551 attr_set = AddTextColorAttribute(attr_set, ATK_TEXT_ATTR_FG_COLOR, style.fore);
552 attr_set = AddTextColorAttribute(attr_set, ATK_TEXT_ATTR_BG_COLOR, style.back);
553 attr_set = AddTextIntAttribute(attr_set, ATK_TEXT_ATTR_INVISIBLE, style.visible ? 0 : 1);
554 attr_set = AddTextIntAttribute(attr_set, ATK_TEXT_ATTR_EDITABLE, style.changeable ? 1 : 0);
556 return attr_set;
559 AtkAttributeSet *ScintillaGTKAccessible::GetRunAttributes(int charOffset, int *startChar, int *endChar) {
560 g_return_val_if_fail(charOffset >= -1, nullptr);
562 Sci::Position byteOffset;
563 if (charOffset == -1) {
564 byteOffset = sci->WndProc(Message::GetCurrentPos, 0, 0);
565 } else {
566 byteOffset = ByteOffsetFromCharacterOffset(charOffset);
568 int length = sci->pdoc->Length();
570 g_return_val_if_fail(byteOffset <= length, nullptr);
572 const char style = StyleAt(byteOffset, true);
573 // compute the range for this style
574 Sci::Position startByte = byteOffset;
575 // when going backwards, we know the style is already computed
576 while (startByte > 0 && sci->pdoc->StyleAt((startByte) - 1) == style)
577 (startByte)--;
578 Sci::Position endByte = byteOffset + 1;
579 while (endByte < length && StyleAt(endByte, true) == style)
580 (endByte)++;
582 CharacterRangeFromByteRange(startByte, endByte, startChar, endChar);
583 return GetAttributesForStyle((unsigned int) style);
586 AtkAttributeSet *ScintillaGTKAccessible::GetDefaultAttributes() {
587 return GetAttributesForStyle(0);
590 gint ScintillaGTKAccessible::GetNSelections() {
591 return sci->sel.Empty() ? 0 : sci->sel.Count();
594 gchar *ScintillaGTKAccessible::GetSelection(gint selection_num, int *startChar, int *endChar) {
595 if (selection_num < 0 || (unsigned int) selection_num >= sci->sel.Count())
596 return nullptr;
598 Sci::Position startByte = sci->sel.Range(selection_num).Start().Position();
599 Sci::Position endByte = sci->sel.Range(selection_num).End().Position();
601 CharacterRangeFromByteRange(startByte, endByte, startChar, endChar);
602 return GetTextRangeUTF8(startByte, endByte);
605 gboolean ScintillaGTKAccessible::AddSelection(int startChar, int endChar) {
606 size_t n_selections = sci->sel.Count();
607 Sci::Position startByte, endByte;
608 ByteRangeFromCharacterRange(startChar, endChar, startByte, endByte);
609 // use WndProc() to set the selections so it notifies as needed
610 if (n_selections > 1 || ! sci->sel.Empty()) {
611 sci->WndProc(Message::AddSelection, startByte, endByte);
612 } else {
613 sci->WndProc(Message::SetSelection, startByte, endByte);
616 return TRUE;
619 gboolean ScintillaGTKAccessible::RemoveSelection(gint selection_num) {
620 size_t n_selections = sci->sel.Count();
621 if (selection_num < 0 || (unsigned int) selection_num >= n_selections)
622 return FALSE;
624 if (n_selections > 1) {
625 sci->WndProc(Message::DropSelectionN, selection_num, 0);
626 } else if (sci->sel.Empty()) {
627 return FALSE;
628 } else {
629 sci->WndProc(Message::ClearSelections, 0, 0);
632 return TRUE;
635 gboolean ScintillaGTKAccessible::SetSelection(gint selection_num, int startChar, int endChar) {
636 if (selection_num < 0 || (unsigned int) selection_num >= sci->sel.Count())
637 return FALSE;
639 Sci::Position startByte, endByte;
640 ByteRangeFromCharacterRange(startChar, endChar, startByte, endByte);
642 sci->WndProc(Message::SetSelectionNStart, selection_num, startByte);
643 sci->WndProc(Message::SetSelectionNEnd, selection_num, endByte);
645 return TRUE;
648 void ScintillaGTKAccessible::AtkTextIface::init(::AtkTextIface *iface) {
649 iface->get_text = GetText;
650 iface->get_text_after_offset = GetTextAfterOffset;
651 iface->get_text_at_offset = GetTextAtOffset;
652 iface->get_text_before_offset = GetTextBeforeOffset;
653 #if ATK_CHECK_VERSION(2, 10, 0)
654 iface->get_string_at_offset = GetStringAtOffset;
655 #endif
656 iface->get_character_at_offset = GetCharacterAtOffset;
657 iface->get_character_count = GetCharacterCount;
658 iface->get_caret_offset = GetCaretOffset;
659 iface->set_caret_offset = SetCaretOffset;
660 iface->get_offset_at_point = GetOffsetAtPoint;
661 iface->get_character_extents = GetCharacterExtents;
662 iface->get_n_selections = GetNSelections;
663 iface->get_selection = GetSelection;
664 iface->add_selection = AddSelection;
665 iface->remove_selection = RemoveSelection;
666 iface->set_selection = SetSelection;
667 iface->get_run_attributes = GetRunAttributes;
668 iface->get_default_attributes = GetDefaultAttributes;
671 /* atkeditabletext.h */
673 void ScintillaGTKAccessible::SetTextContents(const gchar *contents) {
674 // FIXME: it's probably useless to check for READONLY here, SETTEXT probably does it just fine?
675 if (! sci->pdoc->IsReadOnly()) {
676 sci->WndProc(Message::SetText, 0, (sptr_t) contents);
680 bool ScintillaGTKAccessible::InsertStringUTF8(Sci::Position bytePos, const gchar *utf8, Sci::Position lengthBytes) {
681 if (sci->pdoc->IsReadOnly()) {
682 return false;
685 // like EncodedFromUTF8(), but avoids an extra copy
686 // FIXME: update target?
687 const char *charSetBuffer;
688 if (sci->IsUnicodeMode() || ! *(charSetBuffer = sci->CharacterSetID())) {
689 sci->pdoc->InsertString(bytePos, utf8, lengthBytes);
690 } else {
691 // conversion needed
692 std::string encoded = ConvertText(utf8, lengthBytes, charSetBuffer, "UTF-8", true);
693 sci->pdoc->InsertString(bytePos, encoded.c_str(), encoded.length());
696 return true;
699 void ScintillaGTKAccessible::InsertText(const gchar *text, int lengthBytes, int *charPosition) {
700 Sci::Position bytePosition = ByteOffsetFromCharacterOffset(*charPosition);
702 // FIXME: should we update the target?
703 if (InsertStringUTF8(bytePosition, text, lengthBytes)) {
704 (*charPosition) += sci->pdoc->CountCharacters(bytePosition, lengthBytes);
708 void ScintillaGTKAccessible::CopyText(int startChar, int endChar) {
709 Sci::Position startByte, endByte;
710 ByteRangeFromCharacterRange(startChar, endChar, startByte, endByte);
711 sci->CopyRangeToClipboard(startByte, endByte);
714 void ScintillaGTKAccessible::CutText(int startChar, int endChar) {
715 g_return_if_fail(endChar >= startChar);
717 if (! sci->pdoc->IsReadOnly()) {
718 // FIXME: have a byte variant of those and convert only once?
719 CopyText(startChar, endChar);
720 DeleteText(startChar, endChar);
724 void ScintillaGTKAccessible::DeleteText(int startChar, int endChar) {
725 g_return_if_fail(endChar >= startChar);
727 if (! sci->pdoc->IsReadOnly()) {
728 Sci::Position startByte, endByte;
729 ByteRangeFromCharacterRange(startChar, endChar, startByte, endByte);
731 if (! sci->RangeContainsProtected(startByte, endByte)) {
732 // FIXME: restore the target?
733 sci->pdoc->DeleteChars(startByte, endByte - startByte);
738 void ScintillaGTKAccessible::PasteText(int charPosition) {
739 if (sci->pdoc->IsReadOnly())
740 return;
742 // Helper class holding the position for the asynchronous paste operation.
743 // We can only hope that when the callback gets called scia is still valid, but ScintillaGTK
744 // has always done that without problems, so let's guess it's a fairly safe bet.
745 struct Helper : GObjectWatcher {
746 ScintillaGTKAccessible *scia;
747 Sci::Position bytePosition;
749 void Destroyed() override {
750 scia = nullptr;
753 Helper(ScintillaGTKAccessible *scia_, Sci::Position bytePos_) :
754 GObjectWatcher(G_OBJECT(scia_->sci->sci)),
755 scia(scia_),
756 bytePosition(bytePos_) {
759 void TextReceived(GtkClipboard *, const gchar *text) {
760 if (text) {
761 size_t len = strlen(text);
762 std::string convertedText;
763 if (len > 0 && scia->sci->convertPastes) {
764 // Convert line endings of the paste into our local line-endings mode
765 convertedText = Document::TransformLineEnds(text, len, scia->sci->pdoc->eolMode);
766 len = convertedText.length();
767 text = convertedText.c_str();
769 scia->InsertStringUTF8(bytePosition, text, static_cast<Sci::Position>(len));
773 static void TextReceivedCallback(GtkClipboard *clipboard, const gchar *text, gpointer data) {
774 Helper *helper = static_cast<Helper*>(data);
775 try {
776 if (helper->scia != nullptr) {
777 helper->TextReceived(clipboard, text);
779 } catch (...) {}
780 delete helper;
784 Helper *helper = new Helper(this, ByteOffsetFromCharacterOffset(charPosition));
785 GtkWidget *widget = gtk_accessible_get_widget(accessible);
786 GtkClipboard *clipboard = gtk_widget_get_clipboard(widget, GDK_SELECTION_CLIPBOARD);
787 gtk_clipboard_request_text(clipboard, helper->TextReceivedCallback, helper);
790 void ScintillaGTKAccessible::AtkEditableTextIface::init(::AtkEditableTextIface *iface) {
791 iface->set_text_contents = SetTextContents;
792 iface->insert_text = InsertText;
793 iface->copy_text = CopyText;
794 iface->cut_text = CutText;
795 iface->delete_text = DeleteText;
796 iface->paste_text = PasteText;
797 //~ iface->set_run_attributes = SetRunAttributes;
800 bool ScintillaGTKAccessible::Enabled() const {
801 return sci->accessibilityEnabled == SC_ACCESSIBILITY_ENABLED;
804 // Callbacks
806 void ScintillaGTKAccessible::UpdateCursor() {
807 Sci::Position pos = sci->WndProc(Message::GetCurrentPos, 0, 0);
808 if (old_pos != pos) {
809 int charPosition = CharacterOffsetFromByteOffset(pos);
810 g_signal_emit_by_name(accessible, "text-caret-moved", charPosition);
811 old_pos = pos;
814 size_t n_selections = sci->sel.Count();
815 size_t prev_n_selections = old_sels.size();
816 bool selection_changed = n_selections != prev_n_selections;
818 old_sels.resize(n_selections);
819 for (size_t i = 0; i < n_selections; i++) {
820 SelectionRange &sel = sci->sel.Range(i);
822 if (i < prev_n_selections && ! selection_changed) {
823 SelectionRange &old_sel = old_sels[i];
824 // do not consider a caret move to be a selection change
825 selection_changed = ((! old_sel.Empty() || ! sel.Empty()) && ! (old_sel == sel));
828 old_sels[i] = sel;
831 if (selection_changed)
832 g_signal_emit_by_name(accessible, "text-selection-changed");
835 void ScintillaGTKAccessible::ChangeDocument(Document *oldDoc, Document *newDoc) {
836 if (!Enabled()) {
837 return;
840 if (oldDoc == newDoc) {
841 return;
844 if (oldDoc) {
845 int charLength = oldDoc->CountCharacters(0, oldDoc->Length());
846 g_signal_emit_by_name(accessible, "text-changed::delete", 0, charLength);
849 if (newDoc) {
850 PLATFORM_ASSERT(newDoc == sci->pdoc);
852 int charLength = newDoc->CountCharacters(0, newDoc->Length());
853 g_signal_emit_by_name(accessible, "text-changed::insert", 0, charLength);
855 if ((oldDoc ? oldDoc->IsReadOnly() : false) != newDoc->IsReadOnly()) {
856 NotifyReadOnly();
859 // update cursor and selection
860 old_pos = -1;
861 old_sels.clear();
862 UpdateCursor();
866 void ScintillaGTKAccessible::NotifyReadOnly() {
867 bool readonly = sci->pdoc->IsReadOnly();
868 atk_object_notify_state_change(ATK_OBJECT(accessible), ATK_STATE_EDITABLE, ! readonly);
869 #if ATK_CHECK_VERSION(2, 16, 0)
870 atk_object_notify_state_change(ATK_OBJECT(accessible), ATK_STATE_READ_ONLY, readonly);
871 #endif
874 void ScintillaGTKAccessible::SetAccessibility(bool enabled) {
875 // Called by ScintillaGTK when application has enabled or disabled accessibility
876 if (enabled)
877 sci->pdoc->AllocateLineCharacterIndex(LineCharacterIndexType::Utf32);
878 else
879 sci->pdoc->ReleaseLineCharacterIndex(LineCharacterIndexType::Utf32);
882 void ScintillaGTKAccessible::Notify(GtkWidget *, gint, NotificationData *nt) {
883 if (!Enabled())
884 return;
885 switch (nt->nmhdr.code) {
886 case Notification::Modified: {
887 if (FlagSet(nt->modificationType, ModificationFlags::InsertText)) {
888 int startChar = CharacterOffsetFromByteOffset(nt->position);
889 int lengthChar = sci->pdoc->CountCharacters(nt->position, nt->position + nt->length);
890 g_signal_emit_by_name(accessible, "text-changed::insert", startChar, lengthChar);
891 UpdateCursor();
893 if (FlagSet(nt->modificationType, ModificationFlags::BeforeDelete)) {
894 int startChar = CharacterOffsetFromByteOffset(nt->position);
895 int lengthChar = sci->pdoc->CountCharacters(nt->position, nt->position + nt->length);
896 g_signal_emit_by_name(accessible, "text-changed::delete", startChar, lengthChar);
898 if (FlagSet(nt->modificationType, ModificationFlags::DeleteText)) {
899 UpdateCursor();
901 if (FlagSet(nt->modificationType, ModificationFlags::ChangeStyle)) {
902 g_signal_emit_by_name(accessible, "text-attributes-changed");
904 } break;
905 case Notification::UpdateUI: {
906 if (FlagSet(nt->updated, Update::Selection)) {
907 UpdateCursor();
909 } break;
910 default:
911 break;
915 // ATK method wrappers
917 // wraps a call from the accessible object to the ScintillaGTKAccessible, and avoid leaking any exception
918 #define WRAPPER_METHOD_BODY(accessible, call, defret) \
919 try { \
920 ScintillaGTKAccessible *thisAccessible = FromAccessible(reinterpret_cast<GtkAccessible*>(accessible)); \
921 if (thisAccessible) { \
922 return thisAccessible->call; \
923 } else { \
924 return defret; \
926 } catch (...) { \
927 return defret; \
930 // AtkText
931 gchar *ScintillaGTKAccessible::AtkTextIface::GetText(AtkText *text, int start_offset, int end_offset) {
932 WRAPPER_METHOD_BODY(text, GetText(start_offset, end_offset), nullptr);
934 gchar *ScintillaGTKAccessible::AtkTextIface::GetTextAfterOffset(AtkText *text, int offset, AtkTextBoundary boundary_type, int *start_offset, int *end_offset) {
935 WRAPPER_METHOD_BODY(text, GetTextAfterOffset(offset, boundary_type, start_offset, end_offset), nullptr)
937 gchar *ScintillaGTKAccessible::AtkTextIface::GetTextBeforeOffset(AtkText *text, int offset, AtkTextBoundary boundary_type, int *start_offset, int *end_offset) {
938 WRAPPER_METHOD_BODY(text, GetTextBeforeOffset(offset, boundary_type, start_offset, end_offset), nullptr)
940 gchar *ScintillaGTKAccessible::AtkTextIface::GetTextAtOffset(AtkText *text, gint offset, AtkTextBoundary boundary_type, gint *start_offset, gint *end_offset) {
941 WRAPPER_METHOD_BODY(text, GetTextAtOffset(offset, boundary_type, start_offset, end_offset), nullptr)
943 #if ATK_CHECK_VERSION(2, 10, 0)
944 gchar *ScintillaGTKAccessible::AtkTextIface::GetStringAtOffset(AtkText *text, gint offset, AtkTextGranularity granularity, gint *start_offset, gint *end_offset) {
945 WRAPPER_METHOD_BODY(text, GetStringAtOffset(offset, granularity, start_offset, end_offset), nullptr)
947 #endif
948 gunichar ScintillaGTKAccessible::AtkTextIface::GetCharacterAtOffset(AtkText *text, gint offset) {
949 WRAPPER_METHOD_BODY(text, GetCharacterAtOffset(offset), 0)
951 gint ScintillaGTKAccessible::AtkTextIface::GetCharacterCount(AtkText *text) {
952 WRAPPER_METHOD_BODY(text, GetCharacterCount(), 0)
954 gint ScintillaGTKAccessible::AtkTextIface::GetCaretOffset(AtkText *text) {
955 WRAPPER_METHOD_BODY(text, GetCaretOffset(), 0)
957 gboolean ScintillaGTKAccessible::AtkTextIface::SetCaretOffset(AtkText *text, gint offset) {
958 WRAPPER_METHOD_BODY(text, SetCaretOffset(offset), FALSE)
960 gint ScintillaGTKAccessible::AtkTextIface::GetOffsetAtPoint(AtkText *text, gint x, gint y, AtkCoordType coords) {
961 WRAPPER_METHOD_BODY(text, GetOffsetAtPoint(x, y, coords), -1)
963 void ScintillaGTKAccessible::AtkTextIface::GetCharacterExtents(AtkText *text, gint offset, gint *x, gint *y, gint *width, gint *height, AtkCoordType coords) {
964 WRAPPER_METHOD_BODY(text, GetCharacterExtents(offset, x, y, width, height, coords), )
966 AtkAttributeSet *ScintillaGTKAccessible::AtkTextIface::GetRunAttributes(AtkText *text, gint offset, gint *start_offset, gint *end_offset) {
967 WRAPPER_METHOD_BODY(text, GetRunAttributes(offset, start_offset, end_offset), nullptr)
969 AtkAttributeSet *ScintillaGTKAccessible::AtkTextIface::GetDefaultAttributes(AtkText *text) {
970 WRAPPER_METHOD_BODY(text, GetDefaultAttributes(), nullptr)
972 gint ScintillaGTKAccessible::AtkTextIface::GetNSelections(AtkText *text) {
973 WRAPPER_METHOD_BODY(text, GetNSelections(), 0)
975 gchar *ScintillaGTKAccessible::AtkTextIface::GetSelection(AtkText *text, gint selection_num, gint *start_pos, gint *end_pos) {
976 WRAPPER_METHOD_BODY(text, GetSelection(selection_num, start_pos, end_pos), nullptr)
978 gboolean ScintillaGTKAccessible::AtkTextIface::AddSelection(AtkText *text, gint start, gint end) {
979 WRAPPER_METHOD_BODY(text, AddSelection(start, end), FALSE)
981 gboolean ScintillaGTKAccessible::AtkTextIface::RemoveSelection(AtkText *text, gint selection_num) {
982 WRAPPER_METHOD_BODY(text, RemoveSelection(selection_num), FALSE)
984 gboolean ScintillaGTKAccessible::AtkTextIface::SetSelection(AtkText *text, gint selection_num, gint start, gint end) {
985 WRAPPER_METHOD_BODY(text, SetSelection(selection_num, start, end), FALSE)
987 // AtkEditableText
988 void ScintillaGTKAccessible::AtkEditableTextIface::SetTextContents(AtkEditableText *text, const gchar *contents) {
989 WRAPPER_METHOD_BODY(text, SetTextContents(contents), )
991 void ScintillaGTKAccessible::AtkEditableTextIface::InsertText(AtkEditableText *text, const gchar *contents, gint length, gint *position) {
992 WRAPPER_METHOD_BODY(text, InsertText(contents, length, position), )
994 void ScintillaGTKAccessible::AtkEditableTextIface::CopyText(AtkEditableText *text, gint start, gint end) {
995 WRAPPER_METHOD_BODY(text, CopyText(start, end), )
997 void ScintillaGTKAccessible::AtkEditableTextIface::CutText(AtkEditableText *text, gint start, gint end) {
998 WRAPPER_METHOD_BODY(text, CutText(start, end), )
1000 void ScintillaGTKAccessible::AtkEditableTextIface::DeleteText(AtkEditableText *text, gint start, gint end) {
1001 WRAPPER_METHOD_BODY(text, DeleteText(start, end), )
1003 void ScintillaGTKAccessible::AtkEditableTextIface::PasteText(AtkEditableText *text, gint position) {
1004 WRAPPER_METHOD_BODY(text, PasteText(position), )
1007 // GObject glue
1009 #if HAVE_GTK_FACTORY
1010 static GType scintilla_object_accessible_factory_get_type(void);
1011 #endif
1013 static void scintilla_object_accessible_init(ScintillaObjectAccessible *accessible);
1014 static void scintilla_object_accessible_class_init(ScintillaObjectAccessibleClass *klass);
1015 static gpointer scintilla_object_accessible_parent_class = nullptr;
1018 // @p parent_type is only required on GTK 3.2 to 3.6, and only on the first call
1019 static GType scintilla_object_accessible_get_type(GType parent_type G_GNUC_UNUSED) {
1020 static gsize type_id_result = 0;
1022 if (g_once_init_enter(&type_id_result)) {
1023 GTypeInfo tinfo = {
1024 0, /* class size */
1025 (GBaseInitFunc) nullptr, /* base init */
1026 (GBaseFinalizeFunc) nullptr, /* base finalize */
1027 (GClassInitFunc) scintilla_object_accessible_class_init, /* class init */
1028 (GClassFinalizeFunc) nullptr, /* class finalize */
1029 nullptr, /* class data */
1030 0, /* instance size */
1031 0, /* nb preallocs */
1032 (GInstanceInitFunc) scintilla_object_accessible_init, /* instance init */
1033 nullptr /* value table */
1036 const GInterfaceInfo atk_text_info = {
1037 (GInterfaceInitFunc) ScintillaGTKAccessible::AtkTextIface::init,
1038 (GInterfaceFinalizeFunc) nullptr,
1039 nullptr
1042 const GInterfaceInfo atk_editable_text_info = {
1043 (GInterfaceInitFunc) ScintillaGTKAccessible::AtkEditableTextIface::init,
1044 (GInterfaceFinalizeFunc) nullptr,
1045 nullptr
1048 #if HAVE_GTK_A11Y_H
1049 // good, we have gtk-a11y.h, we can use that
1050 GType derived_atk_type = GTK_TYPE_CONTAINER_ACCESSIBLE;
1051 tinfo.class_size = sizeof (GtkContainerAccessibleClass);
1052 tinfo.instance_size = sizeof (GtkContainerAccessible);
1053 #else // ! HAVE_GTK_A11Y_H
1054 # if HAVE_GTK_FACTORY
1055 // Figure out the size of the class and instance we are deriving from through the registry
1056 GType derived_type = g_type_parent(SCINTILLA_TYPE_OBJECT);
1057 AtkObjectFactory *factory = atk_registry_get_factory(atk_get_default_registry(), derived_type);
1058 GType derived_atk_type = atk_object_factory_get_accessible_type(factory);
1059 # else // ! HAVE_GTK_FACTORY
1060 // We're kind of screwed and can't determine the parent (no registry, and no public type)
1061 // Hack your way around by requiring the caller to give us our parent type. The caller
1062 // might be able to trick its way into doing that, by e.g. instantiating the parent's
1063 // accessible type and get its GType. It's ugly but we can't do better on GTK 3.2 to 3.6.
1064 g_assert(parent_type != 0);
1066 GType derived_atk_type = parent_type;
1067 # endif // ! HAVE_GTK_FACTORY
1069 GTypeQuery query;
1070 g_type_query(derived_atk_type, &query);
1071 tinfo.class_size = query.class_size;
1072 tinfo.instance_size = query.instance_size;
1073 #endif // ! HAVE_GTK_A11Y_H
1075 GType type_id = g_type_register_static(derived_atk_type, "ScintillaObjectAccessible", &tinfo, (GTypeFlags) 0);
1076 g_type_add_interface_static(type_id, ATK_TYPE_TEXT, &atk_text_info);
1077 g_type_add_interface_static(type_id, ATK_TYPE_EDITABLE_TEXT, &atk_editable_text_info);
1079 g_once_init_leave(&type_id_result, type_id);
1082 return type_id_result;
1085 static AtkObject *scintilla_object_accessible_new(GType parent_type, GObject *obj) {
1086 g_return_val_if_fail(SCINTILLA_IS_OBJECT(obj), nullptr);
1088 AtkObject *accessible = static_cast<AtkObject *>(g_object_new(scintilla_object_accessible_get_type(parent_type),
1089 #if HAVE_WIDGET_SET_UNSET
1090 "widget", obj,
1091 #endif
1092 nullptr));
1093 atk_object_initialize(accessible, obj);
1095 return accessible;
1098 // implementation for gtk_widget_get_accessible().
1099 // See the comment at the top of the file for details on the implementation
1100 // @p widget the widget.
1101 // @p cache pointer to store the AtkObject between repeated calls. Might or might not be filled.
1102 // @p widget_parent_class pointer to the widget's parent class (to chain up method calls).
1103 AtkObject *ScintillaGTKAccessible::WidgetGetAccessibleImpl(GtkWidget *widget, AtkObject **cache, gpointer widget_parent_class G_GNUC_UNUSED) {
1104 if (*cache != nullptr) {
1105 return *cache;
1108 #if HAVE_GTK_A11Y_H // just instantiate the accessible
1109 *cache = scintilla_object_accessible_new(0, G_OBJECT(widget));
1110 #elif HAVE_GTK_FACTORY // register in the factory and let GTK instantiate
1111 static gsize registered = 0;
1113 if (g_once_init_enter(&registered)) {
1114 // Figure out whether accessibility is enabled by looking at the type of the accessible
1115 // object which would be created for the parent type of ScintillaObject.
1116 GType derived_type = g_type_parent(SCINTILLA_TYPE_OBJECT);
1118 AtkRegistry *registry = atk_get_default_registry();
1119 AtkObjectFactory *factory = atk_registry_get_factory(registry, derived_type);
1120 GType derived_atk_type = atk_object_factory_get_accessible_type(factory);
1121 if (g_type_is_a(derived_atk_type, GTK_TYPE_ACCESSIBLE)) {
1122 atk_registry_set_factory_type(registry, SCINTILLA_TYPE_OBJECT,
1123 scintilla_object_accessible_factory_get_type());
1125 g_once_init_leave(&registered, 1);
1127 AtkObject *obj = GTK_WIDGET_CLASS(widget_parent_class)->get_accessible(widget);
1128 *cache = static_cast<AtkObject*>(g_object_ref(obj));
1129 #else // no public API, no factory, so guess from the parent and instantiate
1130 static GType parent_atk_type = 0;
1132 if (parent_atk_type == 0) {
1133 AtkObject *parent_obj = GTK_WIDGET_CLASS(widget_parent_class)->get_accessible(widget);
1134 if (parent_obj) {
1135 parent_atk_type = G_OBJECT_TYPE(parent_obj);
1137 // Figure out whether accessibility is enabled by looking at the type of the accessible
1138 // object which would be created for the parent type of ScintillaObject.
1139 if (g_type_is_a(parent_atk_type, GTK_TYPE_ACCESSIBLE)) {
1140 *cache = scintilla_object_accessible_new(parent_atk_type, G_OBJECT(widget));
1141 } else {
1142 *cache = static_cast<AtkObject*>(g_object_ref(parent_obj));
1146 #endif
1147 return *cache;
1150 static AtkStateSet *scintilla_object_accessible_ref_state_set(AtkObject *accessible) {
1151 AtkStateSet *state_set = ATK_OBJECT_CLASS(scintilla_object_accessible_parent_class)->ref_state_set(accessible);
1153 GtkWidget *widget = gtk_accessible_get_widget(GTK_ACCESSIBLE(accessible));
1154 if (widget == nullptr) {
1155 atk_state_set_add_state(state_set, ATK_STATE_DEFUNCT);
1156 } else {
1157 if (! scintilla_send_message(SCINTILLA_OBJECT(widget), static_cast<int>(Message::GetReadOnly), 0, 0))
1158 atk_state_set_add_state(state_set, ATK_STATE_EDITABLE);
1159 #if ATK_CHECK_VERSION(2, 16, 0)
1160 else
1161 atk_state_set_add_state(state_set, ATK_STATE_READ_ONLY);
1162 #endif
1163 atk_state_set_add_state(state_set, ATK_STATE_MULTI_LINE);
1164 atk_state_set_add_state(state_set, ATK_STATE_MULTISELECTABLE);
1165 atk_state_set_add_state(state_set, ATK_STATE_SELECTABLE_TEXT);
1166 /*atk_state_set_add_state(state_set, ATK_STATE_SUPPORTS_AUTOCOMPLETION);*/
1169 return state_set;
1172 static void scintilla_object_accessible_widget_set(GtkAccessible *accessible) {
1173 GtkWidget *widget = gtk_accessible_get_widget(accessible);
1174 if (widget == nullptr)
1175 return;
1177 ScintillaObjectAccessiblePrivate *priv = SCINTILLA_OBJECT_ACCESSIBLE_GET_PRIVATE(accessible);
1178 if (priv->pscin)
1179 delete priv->pscin;
1180 priv->pscin = new ScintillaGTKAccessible(accessible, widget);
1183 #if HAVE_WIDGET_SET_UNSET
1184 static void scintilla_object_accessible_widget_unset(GtkAccessible *accessible) {
1185 GtkWidget *widget = gtk_accessible_get_widget(accessible);
1186 if (widget == nullptr)
1187 return;
1189 ScintillaObjectAccessiblePrivate *priv = SCINTILLA_OBJECT_ACCESSIBLE_GET_PRIVATE(accessible);
1190 delete priv->pscin;
1191 priv->pscin = 0;
1193 #endif
1195 static void scintilla_object_accessible_initialize(AtkObject *obj, gpointer data) {
1196 ATK_OBJECT_CLASS(scintilla_object_accessible_parent_class)->initialize(obj, data);
1198 #if ! HAVE_WIDGET_SET_UNSET
1199 scintilla_object_accessible_widget_set(GTK_ACCESSIBLE(obj));
1200 #endif
1202 obj->role = ATK_ROLE_TEXT;
1205 static void scintilla_object_accessible_finalize(GObject *object) {
1206 ScintillaObjectAccessiblePrivate *priv = SCINTILLA_OBJECT_ACCESSIBLE_GET_PRIVATE(object);
1208 if (priv->pscin) {
1209 delete priv->pscin;
1210 priv->pscin = nullptr;
1213 G_OBJECT_CLASS(scintilla_object_accessible_parent_class)->finalize(object);
1216 static void scintilla_object_accessible_class_init(ScintillaObjectAccessibleClass *klass) {
1217 GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
1218 AtkObjectClass *object_class = ATK_OBJECT_CLASS(klass);
1220 #if HAVE_WIDGET_SET_UNSET
1221 GtkAccessibleClass *accessible_class = GTK_ACCESSIBLE_CLASS(klass);
1222 accessible_class->widget_set = scintilla_object_accessible_widget_set;
1223 accessible_class->widget_unset = scintilla_object_accessible_widget_unset;
1224 #endif
1226 object_class->ref_state_set = scintilla_object_accessible_ref_state_set;
1227 object_class->initialize = scintilla_object_accessible_initialize;
1229 gobject_class->finalize = scintilla_object_accessible_finalize;
1231 scintilla_object_accessible_parent_class = g_type_class_peek_parent(klass);
1233 g_type_class_add_private(klass, sizeof (ScintillaObjectAccessiblePrivate));
1236 static void scintilla_object_accessible_init(ScintillaObjectAccessible *accessible) {
1237 ScintillaObjectAccessiblePrivate *priv = SCINTILLA_OBJECT_ACCESSIBLE_GET_PRIVATE(accessible);
1239 priv->pscin = nullptr;
1242 #if HAVE_GTK_FACTORY
1243 // Object factory
1244 typedef AtkObjectFactory ScintillaObjectAccessibleFactory;
1245 typedef AtkObjectFactoryClass ScintillaObjectAccessibleFactoryClass;
1247 G_DEFINE_TYPE(ScintillaObjectAccessibleFactory, scintilla_object_accessible_factory, ATK_TYPE_OBJECT_FACTORY)
1249 static void scintilla_object_accessible_factory_init(ScintillaObjectAccessibleFactory *) {
1252 static GType scintilla_object_accessible_factory_get_accessible_type(void) {
1253 return SCINTILLA_TYPE_OBJECT_ACCESSIBLE;
1256 static AtkObject *scintilla_object_accessible_factory_create_accessible(GObject *obj) {
1257 return scintilla_object_accessible_new(0, obj);
1260 static void scintilla_object_accessible_factory_class_init(AtkObjectFactoryClass * klass) {
1261 klass->create_accessible = scintilla_object_accessible_factory_create_accessible;
1262 klass->get_accessible_type = scintilla_object_accessible_factory_get_accessible_type;
1264 #endif