aboutsummaryrefslogtreecommitdiffhomepage
path: root/gtk/ScintillaGTKAccessible.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'gtk/ScintillaGTKAccessible.cxx')
-rw-r--r--gtk/ScintillaGTKAccessible.cxx1197
1 files changed, 1197 insertions, 0 deletions
diff --git a/gtk/ScintillaGTKAccessible.cxx b/gtk/ScintillaGTKAccessible.cxx
new file mode 100644
index 000000000..e6b54be49
--- /dev/null
+++ b/gtk/ScintillaGTKAccessible.cxx
@@ -0,0 +1,1197 @@
+/* Scintilla source code edit control */
+/* ScintillaGTKAccessible.c - GTK+ accessibility for ScintillaGTK */
+/* Copyright 2016 by Colomban Wendling <colomban@geany.org>
+ * The License.txt file describes the conditions under which this software may be distributed. */
+
+// On GTK < 3.2, we need to use the AtkObjectFactory. We need to query
+// the factory to see what type we should derive from, thus making use of
+// dynamic inheritance. It's tricky, but it works so long as it's done
+// carefully enough.
+//
+// On GTK 3.2 through 3.6, we need to hack around because GTK stopped
+// registering its accessible types in the factory, so we can't query
+// them that way. Unfortunately, the accessible types aren't exposed
+// yet (not until 3.8), so there's no proper way to know which type to
+// inherit from. To work around this, we instantiate the parent's
+// AtkObject temporarily, and use it's type. It means creating an extra
+// throwaway object and being able to pass the type information up to the
+// type registration code, but it's the only solution I could find.
+//
+// On GTK 3.8 onward, we use the proper exposed GtkContainerAccessible as
+// parent, and so a straightforward class.
+//
+// To hide and contain the complexity in type creation arising from the
+// hackish support for GTK 3.2 to 3.8, the actual implementation for the
+// widget's get_accessible() is located in the accessibility layer itself.
+
+// Initially based on GtkTextViewAccessible from GTK 3.20
+// Inspiration for the GTK < 3.2 part comes from Evince 2.24, thanks.
+
+// FIXME: optimize character/byte offset conversion (with a cache?)
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <stdexcept>
+#include <new>
+#include <string>
+#include <vector>
+#include <map>
+#include <algorithm>
+
+#include <glib.h>
+#include <gtk/gtk.h>
+
+// whether we have widget_set() and widget_unset()
+#define HAVE_WIDGET_SET_UNSET (GTK_CHECK_VERSION(3, 3, 6))
+// whether GTK accessibility is available through the ATK factory
+#define HAVE_GTK_FACTORY (! GTK_CHECK_VERSION(3, 1, 9))
+// whether we have gtk-a11y.h and the public GTK accessible types
+#define HAVE_GTK_A11Y_H (GTK_CHECK_VERSION(3, 7, 6))
+
+#if HAVE_GTK_A11Y_H
+# include <gtk/gtk-a11y.h>
+#endif
+
+// ScintillaGTK.h and stuff it needs
+#include "Platform.h"
+
+#include "ILexer.h"
+#include "Scintilla.h"
+#include "ScintillaWidget.h"
+#ifdef SCI_LEXER
+#include "SciLexer.h"
+#endif
+#include "StringCopy.h"
+#ifdef SCI_LEXER
+#include "LexerModule.h"
+#endif
+#include "Position.h"
+#include "SplitVector.h"
+#include "Partitioning.h"
+#include "RunStyles.h"
+#include "ContractionState.h"
+#include "CellBuffer.h"
+#include "CallTip.h"
+#include "KeyMap.h"
+#include "Indicator.h"
+#include "XPM.h"
+#include "LineMarker.h"
+#include "Style.h"
+#include "ViewStyle.h"
+#include "CharClassify.h"
+#include "Decoration.h"
+#include "CaseFolder.h"
+#include "Document.h"
+#include "CaseConvert.h"
+#include "UniConversion.h"
+#include "UnicodeFromUTF8.h"
+#include "Selection.h"
+#include "PositionCache.h"
+#include "EditModel.h"
+#include "MarginView.h"
+#include "EditView.h"
+#include "Editor.h"
+#include "AutoComplete.h"
+#include "ScintillaBase.h"
+
+#include "ScintillaGTK.h"
+#include "ScintillaGTKAccessible.h"
+
+#ifdef SCI_NAMESPACE
+using namespace Scintilla;
+#endif
+
+struct ScintillaObjectAccessiblePrivate {
+ ScintillaGTKAccessible *pscin;
+};
+
+typedef GtkAccessible ScintillaObjectAccessible;
+typedef GtkAccessibleClass ScintillaObjectAccessibleClass;
+
+#define SCINTILLA_OBJECT_ACCESSIBLE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), SCINTILLA_TYPE_OBJECT_ACCESSIBLE, ScintillaObjectAccessible))
+#define SCINTILLA_TYPE_OBJECT_ACCESSIBLE (scintilla_object_accessible_get_type(0))
+
+// We can't use priv member because of dynamic inheritance, so we don't actually know the offset. Meh.
+#define SCINTILLA_OBJECT_ACCESSIBLE_GET_PRIVATE(inst) (G_TYPE_INSTANCE_GET_PRIVATE((inst), SCINTILLA_TYPE_OBJECT_ACCESSIBLE, ScintillaObjectAccessiblePrivate))
+
+static GType scintilla_object_accessible_get_type(GType parent_type);
+
+ScintillaGTKAccessible *ScintillaGTKAccessible::FromAccessible(GtkAccessible *accessible) {
+ // FIXME: do we need the check below? GTK checks that in all methods, so maybe
+ GtkWidget *widget = gtk_accessible_get_widget(accessible);
+ if (! widget) {
+ return 0;
+ }
+
+ return SCINTILLA_OBJECT_ACCESSIBLE_GET_PRIVATE(accessible)->pscin;
+}
+
+ScintillaGTKAccessible::ScintillaGTKAccessible(GtkAccessible *accessible_, GtkWidget *widget_) :
+ accessible(accessible_),
+ sci(ScintillaGTK::FromWidget(widget_)),
+ old_pos(-1) {
+ g_signal_connect(widget_, "sci-notify", G_CALLBACK(SciNotify), this);
+}
+
+ScintillaGTKAccessible::~ScintillaGTKAccessible() {
+}
+
+gchar *ScintillaGTKAccessible::GetTextRangeUTF8(Position startByte, Position endByte) {
+ g_return_val_if_fail(startByte >= 0, NULL);
+ // FIXME: should we swap start/end if necessary?
+ g_return_val_if_fail(endByte >= startByte, NULL);
+
+ gchar *utf8Text = NULL;
+ const char *charSetBuffer;
+
+ // like TargetAsUTF8, but avoids a double conversion
+ if (sci->IsUnicodeMode() || ! *(charSetBuffer = sci->CharacterSetID())) {
+ int len = endByte - startByte;
+ utf8Text = (char *) g_malloc(len + 1);
+ sci->pdoc->GetCharRange(utf8Text, startByte, len);
+ utf8Text[len] = '\0';
+ } else {
+ // Need to convert
+ std::string s = sci->RangeText(startByte, endByte);
+ std::string tmputf = ConvertText(&s[0], s.length(), "UTF-8", charSetBuffer, false);
+ size_t len = tmputf.length();
+ utf8Text = (char *) g_malloc(len + 1);
+ memcpy(utf8Text, tmputf.c_str(), len);
+ utf8Text[len] = '\0';
+ }
+
+ return utf8Text;
+}
+
+gchar *ScintillaGTKAccessible::GetText(int startChar, int endChar) {
+ Position startByte, endByte;
+ if (endChar == -1) {
+ startByte = ByteOffsetFromCharacterOffset(startChar);
+ endByte = sci->pdoc->Length();
+ } else {
+ ByteRangeFromCharacterRange(startChar, endChar, startByte, endByte);
+ }
+ return GetTextRangeUTF8(startByte, endByte);
+}
+
+gchar *ScintillaGTKAccessible::GetTextAfterOffset(int charOffset,
+ AtkTextBoundary boundaryType, int *startChar, int *endChar) {
+ g_return_val_if_fail(charOffset >= 0, NULL);
+
+ Position startByte, endByte;
+ Position byteOffset = ByteOffsetFromCharacterOffset(charOffset);
+
+ switch (boundaryType) {
+ case ATK_TEXT_BOUNDARY_CHAR:
+ startByte = PositionAfter(byteOffset);
+ endByte = PositionAfter(startByte);
+ // FIXME: optimize conversion back, as we can reasonably assume +1 char?
+ break;
+
+ case ATK_TEXT_BOUNDARY_WORD_START:
+ startByte = sci->WndProc(SCI_WORDENDPOSITION, byteOffset, 1);
+ startByte = sci->WndProc(SCI_WORDENDPOSITION, startByte, 0);
+ endByte = sci->WndProc(SCI_WORDENDPOSITION, startByte, 1);
+ endByte = sci->WndProc(SCI_WORDENDPOSITION, endByte, 0);
+ break;
+
+ case ATK_TEXT_BOUNDARY_WORD_END:
+ startByte = sci->WndProc(SCI_WORDENDPOSITION, byteOffset, 0);
+ startByte = sci->WndProc(SCI_WORDENDPOSITION, startByte, 1);
+ endByte = sci->WndProc(SCI_WORDENDPOSITION, startByte, 0);
+ endByte = sci->WndProc(SCI_WORDENDPOSITION, endByte, 1);
+ break;
+
+ case ATK_TEXT_BOUNDARY_LINE_START: {
+ int line = sci->WndProc(SCI_LINEFROMPOSITION, byteOffset, 0);
+ startByte = sci->WndProc(SCI_POSITIONFROMLINE, line + 1, 0);
+ endByte = sci->WndProc(SCI_POSITIONFROMLINE, line + 2, 0);
+ break;
+ }
+
+ case ATK_TEXT_BOUNDARY_LINE_END: {
+ int line = sci->WndProc(SCI_LINEFROMPOSITION, byteOffset, 0);
+ startByte = sci->WndProc(SCI_GETLINEENDPOSITION, line, 0);
+ endByte = sci->WndProc(SCI_GETLINEENDPOSITION, line + 1, 0);
+ break;
+ }
+
+ default:
+ *startChar = *endChar = -1;
+ return NULL;
+ }
+
+ CharacterRangeFromByteRange(startByte, endByte, startChar, endChar);
+ return GetTextRangeUTF8(startByte, endByte);
+}
+
+gchar *ScintillaGTKAccessible::GetTextBeforeOffset(int charOffset,
+ AtkTextBoundary boundaryType, int *startChar, int *endChar) {
+ g_return_val_if_fail(charOffset >= 0, NULL);
+
+ Position startByte, endByte;
+ Position byteOffset = ByteOffsetFromCharacterOffset(charOffset);
+
+ switch (boundaryType) {
+ case ATK_TEXT_BOUNDARY_CHAR:
+ endByte = PositionBefore(byteOffset);
+ startByte = PositionBefore(endByte);
+ break;
+
+ case ATK_TEXT_BOUNDARY_WORD_START:
+ endByte = sci->WndProc(SCI_WORDSTARTPOSITION, byteOffset, 0);
+ endByte = sci->WndProc(SCI_WORDSTARTPOSITION, endByte, 1);
+ startByte = sci->WndProc(SCI_WORDSTARTPOSITION, endByte, 0);
+ startByte = sci->WndProc(SCI_WORDSTARTPOSITION, startByte, 1);
+ break;
+
+ case ATK_TEXT_BOUNDARY_WORD_END:
+ endByte = sci->WndProc(SCI_WORDSTARTPOSITION, byteOffset, 1);
+ endByte = sci->WndProc(SCI_WORDSTARTPOSITION, endByte, 0);
+ startByte = sci->WndProc(SCI_WORDSTARTPOSITION, endByte, 1);
+ startByte = sci->WndProc(SCI_WORDSTARTPOSITION, startByte, 0);
+ break;
+
+ case ATK_TEXT_BOUNDARY_LINE_START: {
+ int line = sci->WndProc(SCI_LINEFROMPOSITION, byteOffset, 0);
+ endByte = sci->WndProc(SCI_POSITIONFROMLINE, line, 0);
+ if (line > 0) {
+ startByte = sci->WndProc(SCI_POSITIONFROMLINE, line - 1, 0);
+ } else {
+ startByte = endByte;
+ }
+ break;
+ }
+
+ case ATK_TEXT_BOUNDARY_LINE_END: {
+ int line = sci->WndProc(SCI_LINEFROMPOSITION, byteOffset, 0);
+ if (line > 0) {
+ endByte = sci->WndProc(SCI_GETLINEENDPOSITION, line - 1, 0);
+ } else {
+ endByte = 0;
+ }
+ if (line > 1) {
+ startByte = sci->WndProc(SCI_GETLINEENDPOSITION, line - 2, 0);
+ } else {
+ startByte = endByte;
+ }
+ break;
+ }
+
+ default:
+ *startChar = *endChar = -1;
+ return NULL;
+ }
+
+ CharacterRangeFromByteRange(startByte, endByte, startChar, endChar);
+ return GetTextRangeUTF8(startByte, endByte);
+}
+
+gchar *ScintillaGTKAccessible::GetTextAtOffset(int charOffset,
+ AtkTextBoundary boundaryType, int *startChar, int *endChar) {
+ g_return_val_if_fail(charOffset >= 0, NULL);
+
+ Position startByte, endByte;
+ Position byteOffset = ByteOffsetFromCharacterOffset(charOffset);
+
+ switch (boundaryType) {
+ case ATK_TEXT_BOUNDARY_CHAR:
+ startByte = byteOffset;
+ endByte = sci->WndProc(SCI_POSITIONAFTER, byteOffset, 0);
+ break;
+
+ case ATK_TEXT_BOUNDARY_WORD_START:
+ startByte = sci->WndProc(SCI_WORDSTARTPOSITION, byteOffset, 1);
+ endByte = sci->WndProc(SCI_WORDENDPOSITION, byteOffset, 1);
+ if (! sci->WndProc(SCI_ISRANGEWORD, startByte, endByte)) {
+ // if the cursor was not on a word, forward back
+ startByte = sci->WndProc(SCI_WORDSTARTPOSITION, startByte, 0);
+ startByte = sci->WndProc(SCI_WORDSTARTPOSITION, startByte, 1);
+ }
+ endByte = sci->WndProc(SCI_WORDENDPOSITION, endByte, 0);
+ break;
+
+ case ATK_TEXT_BOUNDARY_WORD_END:
+ startByte = sci->WndProc(SCI_WORDSTARTPOSITION, byteOffset, 1);
+ endByte = sci->WndProc(SCI_WORDENDPOSITION, byteOffset, 1);
+ if (! sci->WndProc(SCI_ISRANGEWORD, startByte, endByte)) {
+ // if the cursor was not on a word, forward back
+ endByte = sci->WndProc(SCI_WORDENDPOSITION, endByte, 0);
+ endByte = sci->WndProc(SCI_WORDENDPOSITION, endByte, 1);
+ }
+ startByte = sci->WndProc(SCI_WORDSTARTPOSITION, startByte, 0);
+ break;
+
+ case ATK_TEXT_BOUNDARY_LINE_START: {
+ int line = sci->WndProc(SCI_LINEFROMPOSITION, byteOffset, 0);
+ startByte = sci->WndProc(SCI_POSITIONFROMLINE, line, 0);
+ endByte = sci->WndProc(SCI_POSITIONFROMLINE, line + 1, 0);
+ break;
+ }
+
+ case ATK_TEXT_BOUNDARY_LINE_END: {
+ int line = sci->WndProc(SCI_LINEFROMPOSITION, byteOffset, 0);
+ if (line > 0) {
+ startByte = sci->WndProc(SCI_GETLINEENDPOSITION, line - 1, 0);
+ } else {
+ startByte = 0;
+ }
+ endByte = sci->WndProc(SCI_GETLINEENDPOSITION, line, 0);
+ break;
+ }
+
+ default:
+ *startChar = *endChar = -1;
+ return NULL;
+ }
+
+ CharacterRangeFromByteRange(startByte, endByte, startChar, endChar);
+ return GetTextRangeUTF8(startByte, endByte);
+}
+
+gchar *ScintillaGTKAccessible::GetStringAtOffset(int charOffset,
+ AtkTextGranularity granularity, int *startChar, int *endChar) {
+ g_return_val_if_fail(charOffset >= 0, NULL);
+
+ Position startByte, endByte;
+ Position byteOffset = ByteOffsetFromCharacterOffset(charOffset);
+
+ switch (granularity) {
+ case ATK_TEXT_GRANULARITY_CHAR:
+ startByte = byteOffset;
+ endByte = sci->WndProc(SCI_POSITIONAFTER, byteOffset, 0);
+ break;
+ case ATK_TEXT_GRANULARITY_WORD:
+ startByte = sci->WndProc(SCI_WORDSTARTPOSITION, byteOffset, 1);
+ endByte = sci->WndProc(SCI_WORDENDPOSITION, byteOffset, 1);
+ break;
+ case ATK_TEXT_GRANULARITY_LINE: {
+ gint line = sci->WndProc(SCI_LINEFROMPOSITION, byteOffset, 0);
+ startByte = sci->WndProc(SCI_POSITIONFROMLINE, line, 0);
+ endByte = sci->WndProc(SCI_GETLINEENDPOSITION, line, 0);
+ break;
+ }
+ default:
+ *startChar = *endChar = -1;
+ return NULL;
+ }
+
+ CharacterRangeFromByteRange(startByte, endByte, startChar, endChar);
+ return GetTextRangeUTF8(startByte, endByte);
+}
+
+gunichar ScintillaGTKAccessible::GetCharacterAtOffset(int charOffset) {
+ g_return_val_if_fail(charOffset >= 0, 0);
+
+ Position startByte = ByteOffsetFromCharacterOffset(charOffset);
+ Position endByte = PositionAfter(startByte);
+ gchar *ch = GetTextRangeUTF8(startByte, endByte);
+ gunichar unichar = g_utf8_get_char_validated(ch, -1);
+ g_free(ch);
+
+ return unichar;
+}
+
+gint ScintillaGTKAccessible::GetCharacterCount() {
+ return sci->pdoc->CountCharacters(0, sci->pdoc->Length());
+}
+
+gint ScintillaGTKAccessible::GetCaretOffset() {
+ return CharacterOffsetFromByteOffset(sci->WndProc(SCI_GETCURRENTPOS, 0, 0));
+}
+
+gboolean ScintillaGTKAccessible::SetCaretOffset(int charOffset) {
+ sci->WndProc(SCI_GOTOPOS, ByteOffsetFromCharacterOffset(charOffset), 0);
+ return TRUE;
+}
+
+gint ScintillaGTKAccessible::GetOffsetAtPoint(gint x, gint y, AtkCoordType coords) {
+ gint x_widget, y_widget, x_window, y_window;
+ GtkWidget *widget = gtk_accessible_get_widget(accessible);
+
+ GdkWindow *window = gtk_widget_get_window(widget);
+ gdk_window_get_origin(window, &x_widget, &y_widget);
+ if (coords == ATK_XY_SCREEN) {
+ x = x - x_widget;
+ y = y - y_widget;
+ } else if (coords == ATK_XY_WINDOW) {
+ window = gdk_window_get_toplevel(window);
+ gdk_window_get_origin(window, &x_window, &y_window);
+
+ x = x - x_widget + x_window;
+ y = y - y_widget + y_window;
+ } else {
+ return -1;
+ }
+
+ // FIXME: should we handle scrolling?
+ return CharacterOffsetFromByteOffset(sci->WndProc(SCI_CHARPOSITIONFROMPOINTCLOSE, x, y));
+}
+
+void ScintillaGTKAccessible::GetCharacterExtents(int charOffset,
+ gint *x, gint *y, gint *width, gint *height, AtkCoordType coords) {
+ *x = *y = *height = *width = 0;
+
+ Position byteOffset = ByteOffsetFromCharacterOffset(charOffset);
+
+ // FIXME: should we handle scrolling?
+ *x = sci->WndProc(SCI_POINTXFROMPOSITION, 0, byteOffset);
+ *y = sci->WndProc(SCI_POINTYFROMPOSITION, 0, byteOffset);
+
+ int line = sci->WndProc(SCI_LINEFROMPOSITION, byteOffset, 0);
+ *height = sci->WndProc(SCI_TEXTHEIGHT, line, 0);
+
+ int nextByteOffset = PositionAfter(byteOffset);
+ int next_x = sci->WndProc(SCI_POINTXFROMPOSITION, 0, nextByteOffset);
+ if (next_x > *x) {
+ *width = next_x - *x;
+ } else if (nextByteOffset > byteOffset) {
+ /* maybe next position was on the next line or something.
+ * just compute the expected character width */
+ int style = sci->pdoc->StyleAt(byteOffset);
+ int len = nextByteOffset - byteOffset;
+ char *ch = new char[len + 1];
+ sci->pdoc->GetCharRange(ch, byteOffset, len);
+ ch[len] = '\0';
+ *width = sci->TextWidth(style, ch);
+ delete[] ch;
+ }
+
+ GtkWidget *widget = gtk_accessible_get_widget(accessible);
+ GdkWindow *window = gtk_widget_get_window(widget);
+ int x_widget, y_widget;
+ gdk_window_get_origin(window, &x_widget, &y_widget);
+ if (coords == ATK_XY_SCREEN) {
+ *x += x_widget;
+ *y += y_widget;
+ } else if (coords == ATK_XY_WINDOW) {
+ window = gdk_window_get_toplevel(window);
+ int x_window, y_window;
+ gdk_window_get_origin(window, &x_window, &y_window);
+
+ *x += x_widget - x_window;
+ *y += y_widget - y_window;
+ } else {
+ *x = *y = *height = *width = 0;
+ }
+}
+
+static AtkAttributeSet *AddTextAttribute(AtkAttributeSet *attributes, AtkTextAttribute attr, gchar *value) {
+ AtkAttribute *at = g_new(AtkAttribute, 1);
+ at->name = g_strdup(atk_text_attribute_get_name(attr));
+ at->value = value;
+
+ return g_slist_prepend(attributes, at);
+}
+
+static AtkAttributeSet *AddTextIntAttribute(AtkAttributeSet *attributes, AtkTextAttribute attr, gint i) {
+ return AddTextAttribute(attributes, attr, g_strdup(atk_text_attribute_get_value(attr, i)));
+}
+
+static AtkAttributeSet *AddTextColorAttribute(AtkAttributeSet *attributes, AtkTextAttribute attr, const ColourDesired &colour) {
+ return AddTextAttribute(attributes, attr,
+ g_strdup_printf("%u,%u,%u", colour.GetRed() * 257, colour.GetGreen() * 257, colour.GetBlue() * 257));
+}
+
+AtkAttributeSet *ScintillaGTKAccessible::GetAttributesForStyle(unsigned int styleNum) {
+ AtkAttributeSet *attr_set = NULL;
+
+ if (styleNum >= sci->vs.styles.size())
+ return NULL;
+ Style &style = sci->vs.styles[styleNum];
+
+ attr_set = AddTextAttribute(attr_set, ATK_TEXT_ATTR_FAMILY_NAME, g_strdup(style.fontName));
+ attr_set = AddTextAttribute(attr_set, ATK_TEXT_ATTR_SIZE, g_strdup_printf("%d", style.size / SC_FONT_SIZE_MULTIPLIER));
+ attr_set = AddTextIntAttribute(attr_set, ATK_TEXT_ATTR_WEIGHT, CLAMP(style.weight, 100, 1000));
+ attr_set = AddTextIntAttribute(attr_set, ATK_TEXT_ATTR_STYLE, style.italic ? PANGO_STYLE_ITALIC : PANGO_STYLE_NORMAL);
+ attr_set = AddTextIntAttribute(attr_set, ATK_TEXT_ATTR_UNDERLINE, style.underline ? PANGO_UNDERLINE_SINGLE : PANGO_UNDERLINE_NONE);
+ attr_set = AddTextColorAttribute(attr_set, ATK_TEXT_ATTR_FG_COLOR, style.fore);
+ attr_set = AddTextColorAttribute(attr_set, ATK_TEXT_ATTR_BG_COLOR, style.back);
+ attr_set = AddTextIntAttribute(attr_set, ATK_TEXT_ATTR_INVISIBLE, style.visible ? 0 : 1);
+ attr_set = AddTextIntAttribute(attr_set, ATK_TEXT_ATTR_EDITABLE, style.changeable ? 1 : 0);
+
+ return attr_set;
+}
+
+AtkAttributeSet *ScintillaGTKAccessible::GetRunAttributes(int charOffset, int *startChar, int *endChar) {
+ g_return_val_if_fail(charOffset >= -1, NULL);
+
+ Position byteOffset;
+ if (charOffset == -1) {
+ byteOffset = sci->WndProc(SCI_GETCURRENTPOS, 0, 0);
+ } else {
+ byteOffset = ByteOffsetFromCharacterOffset(charOffset);
+ }
+ int length = sci->pdoc->Length();
+
+ g_return_val_if_fail(byteOffset < length, NULL);
+
+ const char style = sci->pdoc->StyleAt(byteOffset);
+ // compute the range for this style
+ Position startByte = byteOffset;
+ while (startByte > 0 && sci->pdoc->StyleAt((startByte) - 1) == style)
+ (startByte)--;
+ Position endByte = byteOffset;
+ while ((endByte) + 1 < length && sci->pdoc->StyleAt((endByte) + 1) == style)
+ (endByte)++;
+
+ CharacterRangeFromByteRange(startByte, endByte, startChar, endChar);
+ return GetAttributesForStyle((unsigned int) style);
+}
+
+AtkAttributeSet *ScintillaGTKAccessible::GetDefaultAttributes() {
+ return GetAttributesForStyle(0);
+}
+
+gint ScintillaGTKAccessible::GetNSelections() {
+ return sci->sel.Empty() ? 0 : sci->sel.Count();
+}
+
+gchar *ScintillaGTKAccessible::GetSelection(gint selection_num, int *startChar, int *endChar) {
+ if (selection_num < 0 || (unsigned int) selection_num >= sci->sel.Count())
+ return NULL;
+
+ Position startByte = sci->sel.Range(selection_num).Start().Position();
+ Position endByte = sci->sel.Range(selection_num).End().Position();
+
+ CharacterRangeFromByteRange(startByte, endByte, startChar, endChar);
+ return GetTextRangeUTF8(startByte, endByte);
+}
+
+gboolean ScintillaGTKAccessible::AddSelection(int startChar, int endChar) {
+ size_t n_selections = sci->sel.Count();
+ Position startByte, endByte;
+ ByteRangeFromCharacterRange(startChar, endChar, startByte, endByte);
+ // use WndProc() to set the selections so it notifies as needed
+ if (n_selections > 1 || ! sci->sel.Empty()) {
+ sci->WndProc(SCI_ADDSELECTION, startByte, endByte);
+ } else {
+ sci->WndProc(SCI_SETSELECTION, startByte, endByte);
+ }
+
+ return TRUE;
+}
+
+gboolean ScintillaGTKAccessible::RemoveSelection(gint selection_num) {
+ size_t n_selections = sci->sel.Count();
+ if (selection_num < 0 || (unsigned int) selection_num >= n_selections)
+ return FALSE;
+
+ if (n_selections > 1) {
+ sci->WndProc(SCI_DROPSELECTIONN, selection_num, 0);
+ } else if (sci->sel.Empty()) {
+ return FALSE;
+ } else {
+ sci->WndProc(SCI_CLEARSELECTIONS, 0, 0);
+ }
+
+ return TRUE;
+}
+
+gboolean ScintillaGTKAccessible::SetSelection(gint selection_num, int startChar, int endChar) {
+ if (selection_num < 0 || (unsigned int) selection_num >= sci->sel.Count())
+ return FALSE;
+
+ Position startByte, endByte;
+ ByteRangeFromCharacterRange(startChar, endChar, startByte, endByte);
+
+ sci->WndProc(SCI_SETSELECTIONNSTART, selection_num, startByte);
+ sci->WndProc(SCI_SETSELECTIONNEND, selection_num, endByte);
+
+ return TRUE;
+}
+
+void ScintillaGTKAccessible::AtkTextIface::init(::AtkTextIface *iface) {
+ iface->get_text = GetText;
+ iface->get_text_after_offset = GetTextAfterOffset;
+ iface->get_text_at_offset = GetTextAtOffset;
+ iface->get_text_before_offset = GetTextBeforeOffset;
+#if ATK_CHECK_VERSION(2, 10, 0)
+ iface->get_string_at_offset = GetStringAtOffset;
+#endif
+ iface->get_character_at_offset = GetCharacterAtOffset;
+ iface->get_character_count = GetCharacterCount;
+ iface->get_caret_offset = GetCaretOffset;
+ iface->set_caret_offset = SetCaretOffset;
+ iface->get_offset_at_point = GetOffsetAtPoint;
+ iface->get_character_extents = GetCharacterExtents;
+ iface->get_n_selections = GetNSelections;
+ iface->get_selection = GetSelection;
+ iface->add_selection = AddSelection;
+ iface->remove_selection = RemoveSelection;
+ iface->set_selection = SetSelection;
+ iface->get_run_attributes = GetRunAttributes;
+ iface->get_default_attributes = GetDefaultAttributes;
+}
+
+/* atkeditabletext.h */
+
+void ScintillaGTKAccessible::SetTextContents(const gchar *contents) {
+ // FIXME: it's probably useless to check for READONLY here, SETTEXT probably does it just fine?
+ if (! sci->pdoc->IsReadOnly()) {
+ sci->WndProc(SCI_SETTEXT, 0, (sptr_t) contents);
+ }
+}
+
+bool ScintillaGTKAccessible::InsertStringUTF8(Position bytePos, const gchar *utf8, int lengthBytes) {
+ if (sci->pdoc->IsReadOnly()) {
+ return false;
+ }
+
+ // like EncodedFromUTF8(), but avoids an extra copy
+ // FIXME: update target?
+ const char *charSetBuffer;
+ if (sci->IsUnicodeMode() || ! *(charSetBuffer = sci->CharacterSetID())) {
+ sci->pdoc->InsertString(bytePos, utf8, lengthBytes);
+ } else {
+ // conversion needed
+ std::string encoded = ConvertText(utf8, lengthBytes, charSetBuffer, "UTF-8", true);
+ sci->pdoc->InsertString(bytePos, encoded.c_str(), encoded.length());
+ }
+
+ return true;
+}
+
+void ScintillaGTKAccessible::InsertText(const gchar *text, int lengthBytes, int *charPosition) {
+ Position bytePosition = ByteOffsetFromCharacterOffset(*charPosition);
+
+ // FIXME: should we update the target?
+ if (InsertStringUTF8(bytePosition, text, lengthBytes)) {
+ (*charPosition) += sci->pdoc->CountCharacters(bytePosition, lengthBytes);
+ }
+}
+
+void ScintillaGTKAccessible::CopyText(int startChar, int endChar) {
+ Position startByte, endByte;
+ ByteRangeFromCharacterRange(startChar, endChar, startByte, endByte);
+ sci->CopyRangeToClipboard(startByte, endByte);
+}
+
+void ScintillaGTKAccessible::CutText(int startChar, int endChar) {
+ g_return_if_fail(endChar >= startChar);
+
+ if (! sci->pdoc->IsReadOnly()) {
+ // FIXME: have a byte variant of those and convert only once?
+ CopyText(startChar, endChar);
+ DeleteText(startChar, endChar);
+ }
+}
+
+void ScintillaGTKAccessible::DeleteText(int startChar, int endChar) {
+ g_return_if_fail(endChar >= startChar);
+
+ if (! sci->pdoc->IsReadOnly()) {
+ Position startByte, endByte;
+ ByteRangeFromCharacterRange(startChar, endChar, startByte, endByte);
+
+ if (! sci->RangeContainsProtected(startByte, endByte)) {
+ // FIXME: restore the target?
+ sci->pdoc->DeleteChars(startByte, endByte - startByte);
+ }
+ }
+}
+
+void ScintillaGTKAccessible::PasteText(int charPosition) {
+ if (sci->pdoc->IsReadOnly())
+ return;
+
+ // Helper class holding the position for the asynchronous paste operation.
+ // We can only hope that when the callback gets called scia is still valid, but ScintillaGTK
+ // has always done that without problems, so let's guess it's a fairly safe bet.
+ struct Helper : GObjectWatcher {
+ ScintillaGTKAccessible *scia;
+ Position bytePosition;
+
+ virtual void Destroyed() {
+ scia = 0;
+ }
+
+ Helper(ScintillaGTKAccessible *scia_, Position bytePos_) :
+ GObjectWatcher(G_OBJECT(scia_->sci->sci)),
+ scia(scia_),
+ bytePosition(bytePos_) {
+ }
+
+ void TextReceived(GtkClipboard *, const gchar *text) {
+ if (text) {
+ size_t len = strlen(text);
+ std::string convertedText;
+ if (len > 0 && scia->sci->convertPastes) {
+ // Convert line endings of the paste into our local line-endings mode
+ convertedText = Document::TransformLineEnds(text, len, scia->sci->pdoc->eolMode);
+ len = convertedText.length();
+ text = convertedText.c_str();
+ }
+ scia->InsertStringUTF8(bytePosition, text, static_cast<int>(len));
+ }
+ }
+
+ static void TextReceivedCallback(GtkClipboard *clipboard, const gchar *text, gpointer data) {
+ Helper *helper = reinterpret_cast<Helper*>(data);
+ try {
+ if (helper->scia != 0) {
+ helper->TextReceived(clipboard, text);
+ }
+ } catch (...) {}
+ delete helper;
+ }
+ };
+
+ Helper *helper = new Helper(this, ByteOffsetFromCharacterOffset(charPosition));
+ GtkWidget *widget = gtk_accessible_get_widget(accessible);
+ GtkClipboard *clipboard = gtk_widget_get_clipboard(widget, GDK_SELECTION_CLIPBOARD);
+ gtk_clipboard_request_text(clipboard, helper->TextReceivedCallback, helper);
+}
+
+void ScintillaGTKAccessible::AtkEditableTextIface::init(::AtkEditableTextIface *iface) {
+ iface->set_text_contents = SetTextContents;
+ iface->insert_text = InsertText;
+ iface->copy_text = CopyText;
+ iface->cut_text = CutText;
+ iface->delete_text = DeleteText;
+ iface->paste_text = PasteText;
+ //~ iface->set_run_attributes = SetRunAttributes;
+}
+
+// Callbacks
+
+void ScintillaGTKAccessible::UpdateCursor() {
+ Position pos = sci->WndProc(SCI_GETCURRENTPOS, 0, 0);
+ if (old_pos != pos) {
+ int charPosition = CharacterOffsetFromByteOffset(pos);
+ g_signal_emit_by_name(accessible, "text-caret-moved", charPosition);
+ old_pos = pos;
+ }
+
+ size_t n_selections = sci->sel.Count();
+ size_t prev_n_selections = old_sels.size();
+ bool selection_changed = n_selections != prev_n_selections;
+
+ old_sels.resize(n_selections);
+ for (size_t i = 0; i < n_selections; i++) {
+ SelectionRange &sel = sci->sel.Range(i);
+
+ if (i < prev_n_selections && ! selection_changed) {
+ SelectionRange &old_sel = old_sels[i];
+ // do not consider a caret move to be a selection change
+ selection_changed = ((! old_sel.Empty() || ! sel.Empty()) && ! (old_sel == sel));
+ }
+
+ old_sels[i] = sel;
+ }
+
+ if (selection_changed)
+ g_signal_emit_by_name(accessible, "text-selection-changed");
+}
+
+void ScintillaGTKAccessible::ChangeDocument(Document *oldDoc, Document *newDoc) {
+ if (oldDoc == newDoc) {
+ return;
+ }
+
+ if (oldDoc) {
+ int charLength = oldDoc->CountCharacters(0, oldDoc->Length());
+ g_signal_emit_by_name(accessible, "text-changed::delete", 0, charLength);
+ }
+
+ if (newDoc) {
+ PLATFORM_ASSERT(newDoc == sci->pdoc);
+
+ int charLength = newDoc->CountCharacters(0, newDoc->Length());
+ g_signal_emit_by_name(accessible, "text-changed::insert", 0, charLength);
+
+ if ((oldDoc ? oldDoc->IsReadOnly() : false) != newDoc->IsReadOnly()) {
+ NotifyReadOnly();
+ }
+
+ // update cursor and selection
+ old_pos = -1;
+ old_sels.clear();
+ UpdateCursor();
+ }
+}
+
+void ScintillaGTKAccessible::NotifyReadOnly() {
+ bool readonly = sci->pdoc->IsReadOnly();
+ atk_object_notify_state_change(ATK_OBJECT(accessible), ATK_STATE_EDITABLE, ! readonly);
+#if ATK_CHECK_VERSION(2, 16, 0)
+ atk_object_notify_state_change(ATK_OBJECT(accessible), ATK_STATE_READ_ONLY, readonly);
+#endif
+}
+
+void ScintillaGTKAccessible::Notify(GtkWidget *, gint, SCNotification *nt) {
+ switch (nt->nmhdr.code) {
+ case SCN_MODIFIED: {
+ if (nt->modificationType & SC_MOD_INSERTTEXT) {
+ int startChar = CharacterOffsetFromByteOffset(nt->position);
+ int lengthChar = sci->pdoc->CountCharacters(nt->position, nt->position + nt->length);
+ g_signal_emit_by_name(accessible, "text-changed::insert", startChar, lengthChar);
+ UpdateCursor();
+ }
+ if (nt->modificationType & SC_MOD_DELETETEXT) {
+ int startChar = CharacterOffsetFromByteOffset(nt->position);
+ int lengthChar = sci->pdoc->CountCharacters(nt->position, nt->position + nt->length);
+ g_signal_emit_by_name(accessible, "text-changed::delete", startChar, lengthChar);
+ UpdateCursor();
+ }
+ if (nt->modificationType & SC_MOD_CHANGESTYLE) {
+ g_signal_emit_by_name(accessible, "text-attributes-changed");
+ }
+ } break;
+ case SCN_UPDATEUI: {
+ if (nt->updated & SC_UPDATE_SELECTION) {
+ UpdateCursor();
+ }
+ } break;
+ }
+}
+
+// ATK method wrappers
+
+// wraps a call from the accessible object to the ScintillaGTKAccessible, and avoid leaking any exception
+#define WRAPPER_METHOD_BODY(accessible, call, defret) \
+ try { \
+ ScintillaGTKAccessible *thisAccessible = FromAccessible(reinterpret_cast<GtkAccessible*>(accessible)); \
+ if (thisAccessible) { \
+ return thisAccessible->call; \
+ } else { \
+ return defret; \
+ } \
+ } catch (...) { \
+ return defret; \
+ }
+
+// AtkText
+gchar *ScintillaGTKAccessible::AtkTextIface::GetText(AtkText *text, int start_offset, int end_offset) {
+ WRAPPER_METHOD_BODY(text, GetText(start_offset, end_offset), NULL);
+}
+gchar *ScintillaGTKAccessible::AtkTextIface::GetTextAfterOffset(AtkText *text, int offset, AtkTextBoundary boundary_type, int *start_offset, int *end_offset) {
+ WRAPPER_METHOD_BODY(text, GetTextAfterOffset(offset, boundary_type, start_offset, end_offset), NULL)
+}
+gchar *ScintillaGTKAccessible::AtkTextIface::GetTextBeforeOffset(AtkText *text, int offset, AtkTextBoundary boundary_type, int *start_offset, int *end_offset) {
+ WRAPPER_METHOD_BODY(text, GetTextBeforeOffset(offset, boundary_type, start_offset, end_offset), NULL)
+}
+gchar *ScintillaGTKAccessible::AtkTextIface::GetTextAtOffset(AtkText *text, gint offset, AtkTextBoundary boundary_type, gint *start_offset, gint *end_offset) {
+ WRAPPER_METHOD_BODY(text, GetTextAtOffset(offset, boundary_type, start_offset, end_offset), NULL)
+}
+gchar *ScintillaGTKAccessible::AtkTextIface::GetStringAtOffset(AtkText *text, gint offset, AtkTextGranularity granularity, gint *start_offset, gint *end_offset) {
+ WRAPPER_METHOD_BODY(text, GetStringAtOffset(offset, granularity, start_offset, end_offset), NULL)
+}
+gunichar ScintillaGTKAccessible::AtkTextIface::GetCharacterAtOffset(AtkText *text, gint offset) {
+ WRAPPER_METHOD_BODY(text, GetCharacterAtOffset(offset), 0)
+}
+gint ScintillaGTKAccessible::AtkTextIface::GetCharacterCount(AtkText *text) {
+ WRAPPER_METHOD_BODY(text, GetCharacterCount(), 0)
+}
+gint ScintillaGTKAccessible::AtkTextIface::GetCaretOffset(AtkText *text) {
+ WRAPPER_METHOD_BODY(text, GetCaretOffset(), 0)
+}
+gboolean ScintillaGTKAccessible::AtkTextIface::SetCaretOffset(AtkText *text, gint offset) {
+ WRAPPER_METHOD_BODY(text, SetCaretOffset(offset), FALSE)
+}
+gint ScintillaGTKAccessible::AtkTextIface::GetOffsetAtPoint(AtkText *text, gint x, gint y, AtkCoordType coords) {
+ WRAPPER_METHOD_BODY(text, GetOffsetAtPoint(x, y, coords), -1)
+}
+void ScintillaGTKAccessible::AtkTextIface::GetCharacterExtents(AtkText *text, gint offset, gint *x, gint *y, gint *width, gint *height, AtkCoordType coords) {
+ WRAPPER_METHOD_BODY(text, GetCharacterExtents(offset, x, y, width, height, coords), )
+}
+AtkAttributeSet *ScintillaGTKAccessible::AtkTextIface::GetRunAttributes(AtkText *text, gint offset, gint *start_offset, gint *end_offset) {
+ WRAPPER_METHOD_BODY(text, GetRunAttributes(offset, start_offset, end_offset), NULL)
+}
+AtkAttributeSet *ScintillaGTKAccessible::AtkTextIface::GetDefaultAttributes(AtkText *text) {
+ WRAPPER_METHOD_BODY(text, GetDefaultAttributes(), NULL)
+}
+gint ScintillaGTKAccessible::AtkTextIface::GetNSelections(AtkText *text) {
+ WRAPPER_METHOD_BODY(text, GetNSelections(), 0)
+}
+gchar *ScintillaGTKAccessible::AtkTextIface::GetSelection(AtkText *text, gint selection_num, gint *start_pos, gint *end_pos) {
+ WRAPPER_METHOD_BODY(text, GetSelection(selection_num, start_pos, end_pos), NULL)
+}
+gboolean ScintillaGTKAccessible::AtkTextIface::AddSelection(AtkText *text, gint start, gint end) {
+ WRAPPER_METHOD_BODY(text, AddSelection(start, end), FALSE)
+}
+gboolean ScintillaGTKAccessible::AtkTextIface::RemoveSelection(AtkText *text, gint selection_num) {
+ WRAPPER_METHOD_BODY(text, RemoveSelection(selection_num), FALSE)
+}
+gboolean ScintillaGTKAccessible::AtkTextIface::SetSelection(AtkText *text, gint selection_num, gint start, gint end) {
+ WRAPPER_METHOD_BODY(text, SetSelection(selection_num, start, end), FALSE)
+}
+// AtkEditableText
+void ScintillaGTKAccessible::AtkEditableTextIface::SetTextContents(AtkEditableText *text, const gchar *contents) {
+ WRAPPER_METHOD_BODY(text, SetTextContents(contents), )
+}
+void ScintillaGTKAccessible::AtkEditableTextIface::InsertText(AtkEditableText *text, const gchar *contents, gint length, gint *position) {
+ WRAPPER_METHOD_BODY(text, InsertText(contents, length, position), )
+}
+void ScintillaGTKAccessible::AtkEditableTextIface::CopyText(AtkEditableText *text, gint start, gint end) {
+ WRAPPER_METHOD_BODY(text, CopyText(start, end), )
+}
+void ScintillaGTKAccessible::AtkEditableTextIface::CutText(AtkEditableText *text, gint start, gint end) {
+ WRAPPER_METHOD_BODY(text, CutText(start, end), )
+}
+void ScintillaGTKAccessible::AtkEditableTextIface::DeleteText(AtkEditableText *text, gint start, gint end) {
+ WRAPPER_METHOD_BODY(text, DeleteText(start, end), )
+}
+void ScintillaGTKAccessible::AtkEditableTextIface::PasteText(AtkEditableText *text, gint position) {
+ WRAPPER_METHOD_BODY(text, PasteText(position), )
+}
+
+// GObject glue
+
+#if HAVE_GTK_FACTORY
+static GType scintilla_object_accessible_factory_get_type(void);
+#endif
+
+static void scintilla_object_accessible_init(ScintillaObjectAccessible *accessible);
+static void scintilla_object_accessible_class_init(ScintillaObjectAccessibleClass *klass);
+static gpointer scintilla_object_accessible_parent_class = NULL;
+
+
+// @p parent_type is only required on GTK 3.2 to 3.6, and only on the first call
+static GType scintilla_object_accessible_get_type(GType parent_type) {
+ static volatile gsize type_id_result = 0;
+
+ if (g_once_init_enter(&type_id_result)) {
+ GTypeInfo tinfo = {
+ 0, /* class size */
+ (GBaseInitFunc) NULL, /* base init */
+ (GBaseFinalizeFunc) NULL, /* base finalize */
+ (GClassInitFunc) scintilla_object_accessible_class_init, /* class init */
+ (GClassFinalizeFunc) NULL, /* class finalize */
+ NULL, /* class data */
+ 0, /* instance size */
+ 0, /* nb preallocs */
+ (GInstanceInitFunc) scintilla_object_accessible_init, /* instance init */
+ NULL /* value table */
+ };
+
+ const GInterfaceInfo atk_text_info = {
+ (GInterfaceInitFunc) ScintillaGTKAccessible::AtkTextIface::init,
+ (GInterfaceFinalizeFunc) NULL,
+ NULL
+ };
+
+ const GInterfaceInfo atk_editable_text_info = {
+ (GInterfaceInitFunc) ScintillaGTKAccessible::AtkEditableTextIface::init,
+ (GInterfaceFinalizeFunc) NULL,
+ NULL
+ };
+
+#if HAVE_GTK_A11Y_H
+ // good, we have gtk-a11y.h, we can use that
+ GType derived_atk_type = GTK_TYPE_CONTAINER_ACCESSIBLE;
+ tinfo.class_size = sizeof (GtkContainerAccessibleClass);
+ tinfo.instance_size = sizeof (GtkContainerAccessible);
+#else // ! HAVE_GTK_A11Y_H
+# if HAVE_GTK_FACTORY
+ // Figure out the size of the class and instance we are deriving from through the registry
+ GType derived_type = g_type_parent(SCINTILLA_TYPE_OBJECT);
+ AtkObjectFactory *factory = atk_registry_get_factory(atk_get_default_registry(), derived_type);
+ GType derived_atk_type = atk_object_factory_get_accessible_type(factory);
+# else // ! HAVE_GTK_FACTORY
+ // We're kind of screwed and can't determine the parent (no registry, and no public type)
+ // Hack your way around by requiring the caller to give us our parent type. The caller
+ // might be able to trick its way into doing that, by e.g. instantiating the parent's
+ // accessible type and get its GType. It's ugly but we can't do better on GTK 3.2 to 3.6.
+ g_assert(parent_type != 0);
+
+ GType derived_atk_type = parent_type;
+# endif // ! HAVE_GTK_FACTORY
+
+ GTypeQuery query;
+ g_type_query(derived_atk_type, &query);
+ tinfo.class_size = query.class_size;
+ tinfo.instance_size = query.instance_size;
+#endif // ! HAVE_GTK_A11Y_H
+
+ GType type_id = g_type_register_static(derived_atk_type, "ScintillaObjectAccessible", &tinfo, (GTypeFlags) 0);
+ g_type_add_interface_static(type_id, ATK_TYPE_TEXT, &atk_text_info);
+ g_type_add_interface_static(type_id, ATK_TYPE_EDITABLE_TEXT, &atk_editable_text_info);
+
+ g_once_init_leave(&type_id_result, type_id);
+ }
+
+ return type_id_result;
+}
+
+static AtkObject *scintilla_object_accessible_new(GType parent_type, GObject *obj) {
+ g_return_val_if_fail(SCINTILLA_IS_OBJECT(obj), NULL);
+
+ AtkObject *accessible = (AtkObject *) g_object_new(scintilla_object_accessible_get_type(parent_type),
+#if HAVE_WIDGET_SET_UNSET
+ "widget", obj,
+#endif
+ NULL);
+ atk_object_initialize(accessible, obj);
+
+ return accessible;
+}
+
+// implementation for gtk_widget_get_accessible().
+// See the comment at the top of the file for details on the implementation
+// @p widget the widget.
+// @p cache pointer to store the AtkObject between repeated calls. Might or might not be filled.
+// @p widget_parent_class pointer to the widget's parent class (to chain up method calls).
+AtkObject *ScintillaGTKAccessible::WidgetGetAccessibleImpl(GtkWidget *widget, AtkObject **cache, gpointer widget_parent_class) {
+#if HAVE_GTK_A11Y_H // just instantiate the accessible
+ if (*cache == NULL) {
+ *cache = scintilla_object_accessible_new(0, G_OBJECT(widget));
+ }
+#elif HAVE_GTK_FACTORY // register in the factory and let GTK instantiate
+ static volatile gsize registered = 0;
+
+ if (g_once_init_enter(&registered)) {
+ // Figure out whether accessibility is enabled by looking at the type of the accessible
+ // object which would be created for the parent type of ScintillaObject.
+ GType derived_type = g_type_parent(SCINTILLA_TYPE_OBJECT);
+
+ AtkRegistry *registry = atk_get_default_registry();
+ AtkObjectFactory *factory = atk_registry_get_factory(registry, derived_type);
+ GType derived_atk_type = atk_object_factory_get_accessible_type(factory);
+ if (g_type_is_a(derived_atk_type, GTK_TYPE_ACCESSIBLE)) {
+ atk_registry_set_factory_type(registry, SCINTILLA_TYPE_OBJECT,
+ scintilla_object_accessible_factory_get_type());
+ }
+ g_once_init_leave(&registered, 1);
+ }
+ *cache = GTK_WIDGET_CLASS(widget_parent_class)->get_accessible(widget);
+#else // no public API, no factory, so guess from the parent and instantiate
+ if (*cache == NULL) {
+ static GType parent_atk_type = 0;
+
+ if (parent_atk_type == 0) {
+ AtkObject *parent_obj = GTK_WIDGET_CLASS(widget_parent_class)->get_accessible(widget);
+ if (parent_obj) {
+ GType parent_atk_type = G_OBJECT_TYPE(parent_obj);
+
+ // Figure out whether accessibility is enabled by looking at the type of the accessible
+ // object which would be created for the parent type of ScintillaObject.
+ if (g_type_is_a(parent_atk_type, GTK_TYPE_ACCESSIBLE)) {
+ *cache = scintilla_object_accessible_new(parent_atk_type, G_OBJECT(widget));
+ g_object_unref(parent_obj);
+ } else {
+ *cache = parent_obj;
+ }
+ }
+ }
+ }
+#endif
+ return *cache;
+}
+
+static AtkStateSet *scintilla_object_accessible_ref_state_set(AtkObject *accessible) {
+ AtkStateSet *state_set = ATK_OBJECT_CLASS(scintilla_object_accessible_parent_class)->ref_state_set(accessible);
+
+ GtkWidget *widget = gtk_accessible_get_widget(GTK_ACCESSIBLE(accessible));
+ if (widget == NULL) {
+ atk_state_set_add_state(state_set, ATK_STATE_DEFUNCT);
+ } else {
+ if (! scintilla_send_message(SCINTILLA_OBJECT(widget), SCI_GETREADONLY, 0, 0))
+ atk_state_set_add_state(state_set, ATK_STATE_EDITABLE);
+#if ATK_CHECK_VERSION(2, 16, 0)
+ else
+ atk_state_set_add_state(state_set, ATK_STATE_READ_ONLY);
+#endif
+ atk_state_set_add_state(state_set, ATK_STATE_MULTI_LINE);
+ atk_state_set_add_state(state_set, ATK_STATE_MULTISELECTABLE);
+ atk_state_set_add_state(state_set, ATK_STATE_SELECTABLE_TEXT);
+ /*atk_state_set_add_state(state_set, ATK_STATE_SUPPORTS_AUTOCOMPLETION);*/
+ }
+
+ return state_set;
+}
+
+static void scintilla_object_accessible_widget_set(GtkAccessible *accessible) {
+ GtkWidget *widget = gtk_accessible_get_widget(accessible);
+ if (widget == NULL)
+ return;
+
+ ScintillaObjectAccessiblePrivate *priv = SCINTILLA_OBJECT_ACCESSIBLE_GET_PRIVATE(accessible);
+ if (priv->pscin != 0)
+ delete priv->pscin;
+ priv->pscin = new ScintillaGTKAccessible(accessible, widget);
+}
+
+#if HAVE_WIDGET_SET_UNSET
+static void scintilla_object_accessible_widget_unset(GtkAccessible *accessible) {
+ GtkWidget *widget = gtk_accessible_get_widget(accessible);
+ if (widget == NULL)
+ return;
+
+ ScintillaObjectAccessiblePrivate *priv = SCINTILLA_OBJECT_ACCESSIBLE_GET_PRIVATE(accessible);
+ delete priv->pscin;
+ priv->pscin = 0;
+}
+#endif
+
+static void scintilla_object_accessible_initialize(AtkObject *obj, gpointer data) {
+ ATK_OBJECT_CLASS(scintilla_object_accessible_parent_class)->initialize(obj, data);
+
+#if ! HAVE_WIDGET_SET_UNSET
+ scintilla_object_accessible_widget_set(GTK_ACCESSIBLE(obj));
+#endif
+
+ obj->role = ATK_ROLE_TEXT;
+}
+
+static void scintilla_object_accessible_finalize(GObject *object) {
+ ScintillaObjectAccessiblePrivate *priv = SCINTILLA_OBJECT_ACCESSIBLE_GET_PRIVATE(object);
+
+ if (priv->pscin) {
+ delete priv->pscin;
+ priv->pscin = 0;
+ }
+
+ G_OBJECT_CLASS(scintilla_object_accessible_parent_class)->finalize(object);
+}
+
+static void scintilla_object_accessible_class_init(ScintillaObjectAccessibleClass *klass) {
+ GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
+ AtkObjectClass *object_class = ATK_OBJECT_CLASS(klass);
+
+#if HAVE_WIDGET_SET_UNSET
+ GtkAccessibleClass *accessible_class = GTK_ACCESSIBLE_CLASS(klass);
+ accessible_class->widget_set = scintilla_object_accessible_widget_set;
+ accessible_class->widget_unset = scintilla_object_accessible_widget_unset;
+#endif
+
+ object_class->ref_state_set = scintilla_object_accessible_ref_state_set;
+ object_class->initialize = scintilla_object_accessible_initialize;
+
+ gobject_class->finalize = scintilla_object_accessible_finalize;
+
+ scintilla_object_accessible_parent_class = g_type_class_peek_parent(klass);
+
+ g_type_class_add_private(klass, sizeof (ScintillaObjectAccessiblePrivate));
+}
+
+static void scintilla_object_accessible_init(ScintillaObjectAccessible *accessible) {
+ ScintillaObjectAccessiblePrivate *priv = SCINTILLA_OBJECT_ACCESSIBLE_GET_PRIVATE(accessible);
+
+ priv->pscin = 0;
+}
+
+#if HAVE_GTK_FACTORY
+// Object factory
+typedef AtkObjectFactory ScintillaObjectAccessibleFactory;
+typedef AtkObjectFactoryClass ScintillaObjectAccessibleFactoryClass;
+
+G_DEFINE_TYPE(ScintillaObjectAccessibleFactory, scintilla_object_accessible_factory, ATK_TYPE_OBJECT_FACTORY)
+
+static void scintilla_object_accessible_factory_init(ScintillaObjectAccessibleFactory *) {
+}
+
+static GType scintilla_object_accessible_factory_get_accessible_type(void) {
+ return SCINTILLA_TYPE_OBJECT_ACCESSIBLE;
+}
+
+static AtkObject *scintilla_object_accessible_factory_create_accessible(GObject *obj) {
+ return scintilla_object_accessible_new(0, obj);
+}
+
+static void scintilla_object_accessible_factory_class_init(AtkObjectFactoryClass * klass) {
+ klass->create_accessible = scintilla_object_accessible_factory_create_accessible;
+ klass->get_accessible_type = scintilla_object_accessible_factory_get_accessible_type;
+}
+#endif