/* Scintilla source code edit control */ /* ScintillaGTKAccessible.cxx - GTK+ accessibility for ScintillaGTK */ /* Copyright 2016 by Colomban Wendling * The License.txt file describes the conditions under which this software may be distributed. */ // REFERENCES BETWEEN THE DIFFERENT OBJECTS // // ScintillaGTKAccessible is the actual implementation, as a C++ class. // ScintillaObjectAccessible is the GObject derived from AtkObject that // implements the various ATK interfaces, through ScintillaGTKAccessible. // This follows the same pattern as ScintillaGTK and ScintillaObject. // // ScintillaGTK owns a strong reference to the ScintillaObjectAccessible, and // is both responsible for creating and destroying that object. // // ScintillaObjectAccessible owns a strong reference to ScintillaGTKAccessible, // and is responsible for creating and destroying that object. // // ScintillaGTKAccessible has weak references to both the ScintillaGTK and // the ScintillaObjectAccessible objects associated, but does not own any // strong references to those objects. // // The chain of ownership is as follows: // ScintillaGTK -> ScintillaObjectAccessible -> ScintillaGTKAccessible // DETAILS ON THE GOBJECT TYPE IMPLEMENTATION // // 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // 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 #endif #if defined(_WIN32) // On Win32 use windows.h to access CLIPFORMAT #undef NOMINMAX #define NOMINMAX #include #endif // ScintillaGTK.h and stuff it needs #include "ScintillaTypes.h" #include "ScintillaMessages.h" #include "ScintillaStructures.h" #include "ILoader.h" #include "ILexer.h" #include "Debugging.h" #include "Geometry.h" #include "Platform.h" #include "Scintilla.h" #include "ScintillaWidget.h" #include "CharacterCategoryMap.h" #include "Position.h" #include "UniqueString.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 "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 "Selection.h" #include "PositionCache.h" #include "EditModel.h" #include "MarginView.h" #include "EditView.h" #include "Editor.h" #include "AutoComplete.h" #include "ScintillaBase.h" #include "Wrappers.h" #include "ScintillaGTK.h" #include "ScintillaGTKAccessible.h" using namespace Scintilla; using namespace Scintilla::Internal; 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 nullptr; } return SCINTILLA_OBJECT_ACCESSIBLE_GET_PRIVATE(accessible)->pscin; } ScintillaGTKAccessible::ScintillaGTKAccessible(GtkAccessible *accessible_, GtkWidget *widget_) : accessible(accessible_), sci(ScintillaGTK::FromWidget(widget_)), old_pos(-1) { SetAccessibility(true); g_signal_connect(widget_, "sci-notify", G_CALLBACK(SciNotify), this); } ScintillaGTKAccessible::~ScintillaGTKAccessible() { if (gtk_accessible_get_widget(accessible)) { g_signal_handlers_disconnect_matched(sci->sci, G_SIGNAL_MATCH_DATA, 0, 0, nullptr, nullptr, this); } } gchar *ScintillaGTKAccessible::GetTextRangeUTF8(Sci::Position startByte, Sci::Position endByte) { g_return_val_if_fail(startByte >= 0, nullptr); // FIXME: should we swap start/end if necessary? g_return_val_if_fail(endByte >= startByte, nullptr); gchar *utf8Text = nullptr; 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) { Sci::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, nullptr); Sci::Position startByte, endByte; Sci::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(Message::WordEndPosition, byteOffset, 1); startByte = sci->WndProc(Message::WordEndPosition, startByte, 0); endByte = sci->WndProc(Message::WordEndPosition, startByte, 1); endByte = sci->WndProc(Message::WordEndPosition, endByte, 0); break; case ATK_TEXT_BOUNDARY_WORD_END: startByte = sci->WndProc(Message::WordEndPosition, byteOffset, 0); startByte = sci->WndProc(Message::WordEndPosition, startByte, 1); endByte = sci->WndProc(Message::WordEndPosition, startByte, 0); endByte = sci->WndProc(Message::WordEndPosition, endByte, 1); break; case ATK_TEXT_BOUNDARY_LINE_START: { int line = sci->WndProc(Message::LineFromPosition, byteOffset, 0); startByte = sci->WndProc(Message::PositionFromLine, line + 1, 0); endByte = sci->WndProc(Message::PositionFromLine, line + 2, 0); break; } case ATK_TEXT_BOUNDARY_LINE_END: { int line = sci->WndProc(Message::LineFromPosition, byteOffset, 0); startByte = sci->WndProc(Message::GetLineEndPosition, line, 0); endByte = sci->WndProc(Message::GetLineEndPosition, line + 1, 0); break; } default: *startChar = *endChar = -1; return nullptr; } 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, nullptr); Sci::Position startByte, endByte; Sci::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(Message::WordStartPosition, byteOffset, 0); endByte = sci->WndProc(Message::WordStartPosition, endByte, 1); startByte = sci->WndProc(Message::WordStartPosition, endByte, 0); startByte = sci->WndProc(Message::WordStartPosition, startByte, 1); break; case ATK_TEXT_BOUNDARY_WORD_END: endByte = sci->WndProc(Message::WordStartPosition, byteOffset, 1); endByte = sci->WndProc(Message::WordStartPosition, endByte, 0); startByte = sci->WndProc(Message::WordStartPosition, endByte, 1); startByte = sci->WndProc(Message::WordStartPosition, startByte, 0); break; case ATK_TEXT_BOUNDARY_LINE_START: { int line = sci->WndProc(Message::LineFromPosition, byteOffset, 0); endByte = sci->WndProc(Message::PositionFromLine, line, 0); if (line > 0) { startByte = sci->WndProc(Message::PositionFromLine, line - 1, 0); } else { startByte = endByte; } break; } case ATK_TEXT_BOUNDARY_LINE_END: { int line = sci->WndProc(Message::LineFromPosition, byteOffset, 0); if (line > 0) { endByte = sci->WndProc(Message::GetLineEndPosition, line - 1, 0); } else { endByte = 0; } if (line > 1) { startByte = sci->WndProc(Message::GetLineEndPosition, line - 2, 0); } else { startByte = endByte; } break; } default: *startChar = *endChar = -1; return nullptr; } 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, nullptr); Sci::Position startByte, endByte; Sci::Position byteOffset = ByteOffsetFromCharacterOffset(charOffset); switch (boundaryType) { case ATK_TEXT_BOUNDARY_CHAR: startByte = byteOffset; endByte = sci->WndProc(Message::PositionAfter, byteOffset, 0); break; case ATK_TEXT_BOUNDARY_WORD_START: startByte = sci->WndProc(Message::WordStartPosition, byteOffset, 1); endByte = sci->WndProc(Message::WordEndPosition, byteOffset, 1); if (! sci->WndProc(Message::IsRangeWord, startByte, endByte)) { // if the cursor was not on a word, forward back startByte = sci->WndProc(Message::WordStartPosition, startByte, 0); startByte = sci->WndProc(Message::WordStartPosition, startByte, 1); } endByte = sci->WndProc(Message::WordEndPosition, endByte, 0); break; case ATK_TEXT_BOUNDARY_WORD_END: startByte = sci->WndProc(Message::WordStartPosition, byteOffset, 1); endByte = sci->WndProc(Message::WordEndPosition, byteOffset, 1); if (! sci->WndProc(Message::IsRangeWord, startByte, endByte)) { // if the cursor was not on a word, forward back endByte = sci->WndProc(Message::WordEndPosition, endByte, 0); endByte = sci->WndProc(Message::WordEndPosition, endByte, 1); } startByte = sci->WndProc(Message::WordStartPosition, startByte, 0); break; case ATK_TEXT_BOUNDARY_LINE_START: { int line = sci->WndProc(Message::LineFromPosition, byteOffset, 0); startByte = sci->WndProc(Message::PositionFromLine, line, 0); endByte = sci->WndProc(Message::PositionFromLine, line + 1, 0); break; } case ATK_TEXT_BOUNDARY_LINE_END: { int line = sci->WndProc(Message::LineFromPosition, byteOffset, 0); if (line > 0) { startByte = sci->WndProc(Message::GetLineEndPosition, line - 1, 0); } else { startByte = 0; } endByte = sci->WndProc(Message::GetLineEndPosition, line, 0); break; } default: *startChar = *endChar = -1; return nullptr; } CharacterRangeFromByteRange(startByte, endByte, startChar, endChar); return GetTextRangeUTF8(startByte, endByte); } #if ATK_CHECK_VERSION(2, 10, 0) gchar *ScintillaGTKAccessible::GetStringAtOffset(int charOffset, AtkTextGranularity granularity, int *startChar, int *endChar) { g_return_val_if_fail(charOffset >= 0, nullptr); Sci::Position startByte, endByte; Sci::Position byteOffset = ByteOffsetFromCharacterOffset(charOffset); switch (granularity) { case ATK_TEXT_GRANULARITY_CHAR: startByte = byteOffset; endByte = sci->WndProc(Message::PositionAfter, byteOffset, 0); break; case ATK_TEXT_GRANULARITY_WORD: startByte = sci->WndProc(Message::WordStartPosition, byteOffset, 1); endByte = sci->WndProc(Message::WordEndPosition, byteOffset, 1); break; case ATK_TEXT_GRANULARITY_LINE: { gint line = sci->WndProc(Message::LineFromPosition, byteOffset, 0); startByte = sci->WndProc(Message::PositionFromLine, line, 0); endByte = sci->WndProc(Message::GetLineEndPosition, line, 0); break; } default: *startChar = *endChar = -1; return nullptr; } CharacterRangeFromByteRange(startByte, endByte, startChar, endChar); return GetTextRangeUTF8(startByte, endByte); } #endif gunichar ScintillaGTKAccessible::GetCharacterAtOffset(int charOffset) { g_return_val_if_fail(charOffset >= 0, 0); Sci::Position startByte = ByteOffsetFromCharacterOffset(charOffset); Sci::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(Message::GetCurrentPos, 0, 0)); } gboolean ScintillaGTKAccessible::SetCaretOffset(int charOffset) { sci->WndProc(Message::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(Message::CharPositionFromPointClose, x, y)); } void ScintillaGTKAccessible::GetCharacterExtents(int charOffset, gint *x, gint *y, gint *width, gint *height, AtkCoordType coords) { *x = *y = *height = *width = 0; Sci::Position byteOffset = ByteOffsetFromCharacterOffset(charOffset); // FIXME: should we handle scrolling? *x = sci->WndProc(Message::PointXFromPosition, 0, byteOffset); *y = sci->WndProc(Message::PointYFromPosition, 0, byteOffset); int line = sci->WndProc(Message::LineFromPosition, byteOffset, 0); *height = sci->WndProc(Message::TextHeight, line, 0); int nextByteOffset = PositionAfter(byteOffset); int next_x = sci->WndProc(Message::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 = StyleAt(byteOffset, true); 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, ColourRGBA 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 = nullptr; if (styleNum >= sci->vs.styles.size()) return nullptr; 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(static_cast(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, nullptr); Sci::Position byteOffset; if (charOffset == -1) { byteOffset = sci->WndProc(Message::GetCurrentPos, 0, 0); } else { byteOffset = ByteOffsetFromCharacterOffset(charOffset); } int length = sci->pdoc->Length(); g_return_val_if_fail(byteOffset <= length, nullptr); const char style = StyleAt(byteOffset, true); // compute the range for this style Sci::Position startByte = byteOffset; // when going backwards, we know the style is already computed while (startByte > 0 && sci->pdoc->StyleAt((startByte) - 1) == style) (startByte)--; Sci::Position endByte = byteOffset + 1; while (endByte < length && StyleAt(endByte, true) == 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 nullptr; Sci::Position startByte = sci->sel.Range(selection_num).Start().Position(); Sci::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(); Sci::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(Message::AddSelection, startByte, endByte); } else { sci->WndProc(Message::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(Message::DropSelectionN, selection_num, 0); } else if (sci->sel.Empty()) { return FALSE; } else { sci->WndProc(Message::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; Sci::Position startByte, endByte; ByteRangeFromCharacterRange(startChar, endChar, startByte, endByte); sci->WndProc(Message::SetSelectionNStart, selection_num, startByte); sci->WndProc(Message::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(Message::SetText, 0, (sptr_t) contents); } } bool ScintillaGTKAccessible::InsertStringUTF8(Sci::Position bytePos, const gchar *utf8, Sci::Position 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) { Sci::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) { Sci::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()) { Sci::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; Sci::Position bytePosition; void Destroyed() override { scia = nullptr; } Helper(ScintillaGTKAccessible *scia_, Sci::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(len)); } } static void TextReceivedCallback(GtkClipboard *clipboard, const gchar *text, gpointer data) { Helper *helper = static_cast(data); try { if (helper->scia != nullptr) { 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; } bool ScintillaGTKAccessible::Enabled() const { return sci->accessibilityEnabled == SC_ACCESSIBILITY_ENABLED; } // Callbacks void ScintillaGTKAccessible::UpdateCursor() { Sci::Position pos = sci->WndProc(Message::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 (!Enabled()) { return; } 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::SetAccessibility(bool enabled) { // Called by ScintillaGTK when application has enabled or disabled accessibility if (enabled) sci->pdoc->AllocateLineCharacterIndex(LineCharacterIndexType::Utf32); else sci->pdoc->ReleaseLineCharacterIndex(LineCharacterIndexType::Utf32); } void ScintillaGTKAccessible::Notify(GtkWidget *, gint, NotificationData *nt) { if (!Enabled()) return; switch (nt->nmhdr.code) { case Notification::Modified: { if (FlagSet(nt->modificationType, ModificationFlags::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 (FlagSet(nt->modificationType, ModificationFlags::BeforeDelete)) { 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); } if (FlagSet(nt->modificationType, ModificationFlags::DeleteText)) { UpdateCursor(); } if (FlagSet(nt->modificationType, ModificationFlags::ChangeStyle)) { g_signal_emit_by_name(accessible, "text-attributes-changed"); } } break; case Notification::UpdateUI: { if (FlagSet(nt->updated, Update::Selection)) { UpdateCursor(); } } break; default: 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(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), nullptr); } 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), nullptr) } 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), nullptr) } 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), nullptr) } #if ATK_CHECK_VERSION(2, 10, 0) 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), nullptr) } #endif 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), nullptr) } AtkAttributeSet *ScintillaGTKAccessible::AtkTextIface::GetDefaultAttributes(AtkText *text) { WRAPPER_METHOD_BODY(text, GetDefaultAttributes(), nullptr) } 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), nullptr) } 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 = nullptr; // @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 G_GNUC_UNUSED) { static gsize type_id_result = 0; if (g_once_init_enter(&type_id_result)) { GTypeInfo tinfo = { 0, /* class size */ (GBaseInitFunc) nullptr, /* base init */ (GBaseFinalizeFunc) nullptr, /* base finalize */ (GClassInitFunc) scintilla_object_accessible_class_init, /* class init */ (GClassFinalizeFunc) nullptr, /* class finalize */ nullptr, /* class data */ 0, /* instance size */ 0, /* nb preallocs */ (GInstanceInitFunc) scintilla_object_accessible_init, /* instance init */ nullptr /* value table */ }; const GInterfaceInfo atk_text_info = { (GInterfaceInitFunc) ScintillaGTKAccessible::AtkTextIface::init, (GInterfaceFinalizeFunc) nullptr, nullptr }; const GInterfaceInfo atk_editable_text_info = { (GInterfaceInitFunc) ScintillaGTKAccessible::AtkEditableTextIface::init, (GInterfaceFinalizeFunc) nullptr, nullptr }; #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), nullptr); AtkObject *accessible = (AtkObject *) g_object_new(scintilla_object_accessible_get_type(parent_type), #if HAVE_WIDGET_SET_UNSET "widget", obj, #endif nullptr); 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 G_GNUC_UNUSED) { if (*cache != nullptr) { return *cache; } #if HAVE_GTK_A11Y_H // just instantiate the accessible *cache = scintilla_object_accessible_new(0, G_OBJECT(widget)); #elif HAVE_GTK_FACTORY // register in the factory and let GTK instantiate static gsize registered = 0; if (g_once_init_enter(®istered)) { // 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(®istered, 1); } AtkObject *obj = GTK_WIDGET_CLASS(widget_parent_class)->get_accessible(widget); *cache = static_cast(g_object_ref(obj)); #else // no public API, no factory, so guess from the parent and instantiate 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) { 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)); } else { *cache = static_cast(g_object_ref(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 == nullptr) { atk_state_set_add_state(state_set, ATK_STATE_DEFUNCT); } else { if (! scintilla_send_message(SCINTILLA_OBJECT(widget), static_cast(Message::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 == nullptr) return; ScintillaObjectAccessiblePrivate *priv = SCINTILLA_OBJECT_ACCESSIBLE_GET_PRIVATE(accessible); if (priv->pscin) 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 == nullptr) 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 = nullptr; } 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 = nullptr; } #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