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
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
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?)
63 #include <string_view>
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))
82 # include <gtk/gtk-a11y.h>
86 // On Win32 use windows.h to access CLIPFORMAT
92 // ScintillaGTK.h and stuff it needs
93 #include "ScintillaTypes.h"
94 #include "ScintillaMessages.h"
95 #include "ScintillaStructures.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"
115 #include "Indicator.h"
116 #include "LineMarker.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"
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
);
163 return SCINTILLA_OBJECT_ACCESSIBLE_GET_PRIVATE(accessible
)->pscin
;
166 ScintillaGTKAccessible::ScintillaGTKAccessible(GtkAccessible
*accessible_
, GtkWidget
*widget_
) :
167 accessible(accessible_
),
168 sci(ScintillaGTK::FromWidget(widget_
)),
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';
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';
207 gchar
*ScintillaGTKAccessible::GetText(int startChar
, int endChar
) {
208 Sci::Position startByte
, endByte
;
210 startByte
= ByteOffsetFromCharacterOffset(startChar
);
211 endByte
= sci
->pdoc
->Length();
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?
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);
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);
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);
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);
261 *startChar
= *endChar
= -1;
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
);
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);
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);
296 case ATK_TEXT_BOUNDARY_LINE_START
: {
297 int line
= sci
->WndProc(Message::LineFromPosition
, byteOffset
, 0);
298 endByte
= sci
->WndProc(Message::PositionFromLine
, line
, 0);
300 startByte
= sci
->WndProc(Message::PositionFromLine
, line
- 1, 0);
307 case ATK_TEXT_BOUNDARY_LINE_END
: {
308 int line
= sci
->WndProc(Message::LineFromPosition
, byteOffset
, 0);
310 endByte
= sci
->WndProc(Message::GetLineEndPosition
, line
- 1, 0);
315 startByte
= sci
->WndProc(Message::GetLineEndPosition
, line
- 2, 0);
323 *startChar
= *endChar
= -1;
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);
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);
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);
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);
373 case ATK_TEXT_BOUNDARY_LINE_END
: {
374 int line
= sci
->WndProc(Message::LineFromPosition
, byteOffset
, 0);
376 startByte
= sci
->WndProc(Message::GetLineEndPosition
, line
- 1, 0);
380 endByte
= sci
->WndProc(Message::GetLineEndPosition
, line
, 0);
385 *startChar
= *endChar
= -1;
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);
406 case ATK_TEXT_GRANULARITY_WORD
:
407 startByte
= sci
->WndProc(Message::WordStartPosition
, byteOffset
, 1);
408 endByte
= sci
->WndProc(Message::WordEndPosition
, byteOffset
, 1);
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);
417 *startChar
= *endChar
= -1;
421 CharacterRangeFromByteRange(startByte
, endByte
, startChar
, endChar
);
422 return GetTextRangeUTF8(startByte
, endByte
);
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);
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);
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
) {
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
;
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
);
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
);
499 *width
= sci
->TextWidth(style
, 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
) {
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
;
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
));
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())
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);
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);
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
)
578 Sci::Position endByte
= byteOffset
+ 1;
579 while (endByte
< length
&& StyleAt(endByte
, true) == style
)
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())
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
);
613 sci
->WndProc(Message::SetSelection
, startByte
, endByte
);
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
)
624 if (n_selections
> 1) {
625 sci
->WndProc(Message::DropSelectionN
, selection_num
, 0);
626 } else if (sci
->sel
.Empty()) {
629 sci
->WndProc(Message::ClearSelections
, 0, 0);
635 gboolean
ScintillaGTKAccessible::SetSelection(gint selection_num
, int startChar
, int endChar
) {
636 if (selection_num
< 0 || (unsigned int) selection_num
>= sci
->sel
.Count())
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
);
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
;
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()) {
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
);
692 std::string encoded
= ConvertText(utf8
, lengthBytes
, charSetBuffer
, "UTF-8", true);
693 sci
->pdoc
->InsertString(bytePos
, encoded
.c_str(), encoded
.length());
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())
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
{
753 Helper(ScintillaGTKAccessible
*scia_
, Sci::Position bytePos_
) :
754 GObjectWatcher(G_OBJECT(scia_
->sci
->sci
)),
756 bytePosition(bytePos_
) {
759 void TextReceived(GtkClipboard
*, const gchar
*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
);
776 if (helper
->scia
!= nullptr) {
777 helper
->TextReceived(clipboard
, text
);
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
;
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
);
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
));
831 if (selection_changed
)
832 g_signal_emit_by_name(accessible
, "text-selection-changed");
835 void ScintillaGTKAccessible::ChangeDocument(Document
*oldDoc
, Document
*newDoc
) {
840 if (oldDoc
== newDoc
) {
845 int charLength
= oldDoc
->CountCharacters(0, oldDoc
->Length());
846 g_signal_emit_by_name(accessible
, "text-changed::delete", 0, charLength
);
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()) {
859 // update cursor and selection
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
);
874 void ScintillaGTKAccessible::SetAccessibility(bool enabled
) {
875 // Called by ScintillaGTK when application has enabled or disabled accessibility
877 sci
->pdoc
->AllocateLineCharacterIndex(LineCharacterIndexType::Utf32
);
879 sci
->pdoc
->ReleaseLineCharacterIndex(LineCharacterIndexType::Utf32
);
882 void ScintillaGTKAccessible::Notify(GtkWidget
*, gint
, NotificationData
*nt
) {
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
);
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
)) {
901 if (FlagSet(nt
->modificationType
, ModificationFlags::ChangeStyle
)) {
902 g_signal_emit_by_name(accessible
, "text-attributes-changed");
905 case Notification::UpdateUI
: {
906 if (FlagSet(nt
->updated
, Update::Selection
)) {
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) \
920 ScintillaGTKAccessible *thisAccessible = FromAccessible(reinterpret_cast<GtkAccessible*>(accessible)); \
921 if (thisAccessible) { \
922 return thisAccessible->call; \
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)
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
)
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
), )
1009 #if HAVE_GTK_FACTORY
1010 static GType
scintilla_object_accessible_factory_get_type(void);
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
)) {
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,
1042 const GInterfaceInfo atk_editable_text_info
= {
1043 (GInterfaceInitFunc
) ScintillaGTKAccessible::AtkEditableTextIface::init
,
1044 (GInterfaceFinalizeFunc
) nullptr,
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
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
1093 atk_object_initialize(accessible
, obj
);
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) {
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(®istered
)) {
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(®istered
, 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
);
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
));
1142 *cache
= static_cast<AtkObject
*>(g_object_ref(parent_obj
));
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
);
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)
1161 atk_state_set_add_state(state_set
, ATK_STATE_READ_ONLY
);
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);*/
1172 static void scintilla_object_accessible_widget_set(GtkAccessible
*accessible
) {
1173 GtkWidget
*widget
= gtk_accessible_get_widget(accessible
);
1174 if (widget
== nullptr)
1177 ScintillaObjectAccessiblePrivate
*priv
= SCINTILLA_OBJECT_ACCESSIBLE_GET_PRIVATE(accessible
);
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)
1189 ScintillaObjectAccessiblePrivate
*priv
= SCINTILLA_OBJECT_ACCESSIBLE_GET_PRIVATE(accessible
);
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
));
1202 obj
->role
= ATK_ROLE_TEXT
;
1205 static void scintilla_object_accessible_finalize(GObject
*object
) {
1206 ScintillaObjectAccessiblePrivate
*priv
= SCINTILLA_OBJECT_ACCESSIBLE_GET_PRIVATE(object
);
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
;
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
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
;