diff options
Diffstat (limited to 'gtk/a11y')
45 files changed, 9705 insertions, 0 deletions
diff --git a/gtk/a11y/atspi/Accessibility.xml b/gtk/a11y/atspi/Accessibility.xml new file mode 100644 index 0000000000..a454aeb55b --- /dev/null +++ b/gtk/a11y/atspi/Accessibility.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<spec xmlns:xi="http://www.w3.org/2001/XInclude"> + <xi:include href="Accessible.xml" parse="xml"/> + <xi:include href="Action.xml" parse="xml"/> + <xi:include href="Application.xml" parse="xml"/> + <xi:include href="Collection.xml" parse="xml"/> + <xi:include href="Component.xml" parse="xml"/> + <xi:include href="Document.xml" parse="xml"/> + <xi:include href="Hypertext.xml" parse="xml"/> + <xi:include href="Hyperlink.xml" parse="xml"/> + <xi:include href="Image.xml" parse="xml"/> + <xi:include href="Selection.xml" parse="xml"/> + <xi:include href="Table.xml" parse="xml"/> + <xi:include href="TableCell.xml" parse="xml"/> + <xi:include href="Text.xml" parse="xml"/> + <xi:include href="EditableText.xml" parse="xml"/> + <xi:include href="Cache.xml" parse="xml"/> + <xi:include href="Value.xml" parse="xml"/> + <xi:include href="Registry.xml" parse="xml"/> + <xi:include href="DeviceEventController.xml" parse="xml"/> + <xi:include href="DeviceEventListener.xml" parse="xml"/> +</spec> diff --git a/gtk/a11y/atspi/Accessible.xml b/gtk/a11y/atspi/Accessible.xml new file mode 100644 index 0000000000..a274cdb902 --- /dev/null +++ b/gtk/a11y/atspi/Accessible.xml @@ -0,0 +1,71 @@ +<?xml version="1.0" encoding="UTF-8"?> +<node name="/node"> +<interface name="org.a11y.atspi.Accessible"> + + <property name="Name" type="s" access="read"/> + + <property name="Description" type="s" access="read"/> + + <property name="Parent" type="(so)" access="read"> + <annotation name="com.trolltech.QtDBus.QtTypeName" value="QSpiObjectReference"/> + </property> + + <property name="ChildCount" type="i" access="read"/> + + <property name="Locale" type="s" access="read"/> + + <property name="AccessibleId" type="s" access="read"/> + + <method name="GetChildAtIndex"> + <arg direction="in" name="index" type="i"/> + <arg direction="out" type="(so)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QSpiObjectReference"/> + </method> + + <method name="GetChildren"> + <arg direction="out" type="a(so)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QSpiObjectReferenceArray"/> + </method> + + <method name="GetIndexInParent"> + <arg direction="out" type="i"/> + </method> + + <method name="GetRelationSet"> + <arg direction="out" type="a(ua(so))"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QSpiRelationArray"/> + </method> + + <method name="GetRole"> + <arg direction="out" type="u"/> + </method> + + <method name="GetRoleName"> + <arg direction="out" type="s"/> + </method> + + <method name="GetLocalizedRoleName"> + <arg direction="out" type="s"/> + </method> + + <method name="GetState"> + <arg direction="out" type="au"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QSpiIntList"/> + </method> + + <method name="GetAttributes"> + <arg direction="out" type="a{ss}"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QSpiAttributeSet"/> + </method> + + <method name="GetApplication"> + <arg direction="out" type="(so)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QSpiObjectReference"/> + </method> + + <method name="GetInterfaces"> + <arg direction="out" type="as"/> + </method> + +</interface> +</node> diff --git a/gtk/a11y/atspi/Action.xml b/gtk/a11y/atspi/Action.xml new file mode 100644 index 0000000000..0f5aa850bd --- /dev/null +++ b/gtk/a11y/atspi/Action.xml @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="UTF-8"?> +<node name="/node"> +<interface name="org.a11y.atspi.Action"> + + <property name="NActions" type="i" access="read"/> + + <method name="GetDescription"> + <arg type="i" name="index" direction="in"/> + <arg type="s" direction="out"/> + </method> + + <method name="GetName"> + <arg type="i" name="index" direction="in"/> + <arg type="s" direction="out"/> + </method> + + <method name="GetLocalizedName"> + <arg type="i" name="index" direction="in"/> + <arg type="s" direction="out"/> + </method> + + <method name="GetKeyBinding"> + <arg type="i" name="index" direction="in"/> + <arg type="s" direction="out"/> + </method> + + <method name="GetActions"> + <arg direction="out" type="a(sss)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QSpiActionArray"/> + </method> + + <method name="DoAction"> + <arg direction="in" name="index" type="i"/> + <arg direction="out" type="b"/> + </method> + +</interface> +</node> diff --git a/gtk/a11y/atspi/Application.xml b/gtk/a11y/atspi/Application.xml new file mode 100644 index 0000000000..2191f08858 --- /dev/null +++ b/gtk/a11y/atspi/Application.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> +<node name="/node"> +<interface name="org.a11y.atspi.Application"> + + <property name="ToolkitName" type="s" access="read"/> + + <property name="Version" type="s" access="read"/> + + <property name="AtspiVersion" type="s" access="read"/> + <property name="Id" type="i" access="readwrite"/> + + <method name="GetLocale"> + <arg direction="in" name="lctype" type="u"/> + <arg direction="out" type="s"/> + </method> + + <method name="RegisterEventListener"> + <arg direction="in" name="event" type="s"/> + </method> + + <method name="DeregisterEventListener"> + <arg direction="in" name="event" type="s"/> + </method> + +</interface> +</node> diff --git a/gtk/a11y/atspi/Cache.xml b/gtk/a11y/atspi/Cache.xml new file mode 100644 index 0000000000..e693b54cdb --- /dev/null +++ b/gtk/a11y/atspi/Cache.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<node name="/node"> +<interface name="org.a11y.atspi.Cache"> + + <method name="GetItems"> + <arg name="nodes" type="a((so)(so)iiassusau)" direction="out"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QSpiAccessibleCacheArray"/> + </method> + + <signal name="AddAccessible"> + <arg name="nodeAdded" type="((so)(so)iiassusau)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiAccessibleCacheItem"/> + </signal> + + <signal name="RemoveAccessible"> + <arg name="nodeRemoved" type="(so)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiObjectReference"/> + </signal> + +</interface> +</node> diff --git a/gtk/a11y/atspi/Collection.xml b/gtk/a11y/atspi/Collection.xml new file mode 100644 index 0000000000..7b39776a0a --- /dev/null +++ b/gtk/a11y/atspi/Collection.xml @@ -0,0 +1,48 @@ +<?xml version="1.0" encoding="UTF-8"?> +<node name="/node"> +<interface name="org.a11y.atspi.Collection"> + + <method name="GetMatches"> + <arg direction="in" name="rule" type="(auuasuauusub)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.In1" value="QSpiMatchRule"/> + <arg direction="in" name="sortby" type="u"/> + <arg direction="in" name="count" type="i"/> + <arg direction="in" name="traverse" type="b"/> + <arg direction="out" type="a(so)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QSpiReferenceSet"/> + </method> + + <method name="GetMatchesTo"> + <arg direction="in" name="current_object" type="o"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiObjectReference"/> + <arg direction="in" name="rule" type="(auuasuauusub)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.In1" value="QSpiMatchRule"/> + <arg direction="in" name="sortby" type="u"/> + <arg direction="in" name="tree" type="u"/> + <arg direction="in" name="limit_scope" type="b"/> + <arg direction="in" name="count" type="i"/> + <arg direction="in" name="traverse" type="b"/> + <arg direction="out" type="a(so)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QSpiReferenceSet"/> + </method> + + <method name="GetMatchesFrom"> + <arg direction="in" name="current_object" type="o"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiObjectReference"/> + <arg direction="in" name="rule" type="(auuasuauusub)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.In1" value="QSpiMatchRule"/> + <arg direction="in" name="sortby" type="u"/> + <arg direction="in" name="tree" type="u"/> + <arg direction="in" name="count" type="i"/> + <arg direction="in" name="traverse" type="b"/> + <arg direction="out" type="a(so)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QSpiReferenceSet"/> + </method> + + <method name="GetActiveDescendant"> + <arg direction="out" type="(so)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QSpiReferenceSet"/> + </method> + +</interface> +</node> diff --git a/gtk/a11y/atspi/Component.xml b/gtk/a11y/atspi/Component.xml new file mode 100644 index 0000000000..c1258d27c6 --- /dev/null +++ b/gtk/a11y/atspi/Component.xml @@ -0,0 +1,86 @@ +<?xml version="1.0" encoding="UTF-8"?> +<node name="/node"> +<interface name="org.a11y.atspi.Component"> + + <method name="Contains"> + <arg direction="in" name="x" type="i"/> + <arg direction="in" name="y" type="i"/> + <arg direction="in" name="coord_type" type="u"/> + <arg direction="out" type="b"/> + </method> + + <method name="GetAccessibleAtPoint"> + <arg direction="in" name="x" type="i"/> + <arg direction="in" name="y" type="i"/> + <arg direction="in" name="coord_type" type="u"/> + <arg direction="out" type="(so)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QSpiObjectReference"/> + </method> + + <method name="GetExtents"> + <arg direction="in" name="coord_type" type="u"/> + <arg direction="out" type="(iiii)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QSpiRect"/> + </method> + + <method name="GetPosition"> + <arg direction="in" name="coord_type" type="u"/> + <arg direction="out" name="x" type="i"/> + <arg direction="out" name="y" type="i"/> + </method> + + <method name="GetSize"> + <arg direction="out" name="width" type="i"/> + <arg direction="out" name="height" type="i"/> + </method> + + <method name="GetLayer"> + <arg direction="out" type="u"/> + </method> + + <method name="GetMDIZOrder"> + <arg direction="out" type="n"/> + </method> + + <method name="GrabFocus"> + <arg direction="out" type="b"/> + </method> + + <method name="GetAlpha"> + <arg direction="out" type="d"/> + </method> + + <method name="SetExtents"> + <arg direction="in" name="x" type="i"/> + <arg direction="in" name="y" type="i"/> + <arg direction="in" name="width" type="i"/> + <arg direction="in" name="height" type="i"/> + <arg direction="in" name="coord_type" type="u"/> + <arg direction="out" type="b"/> + </method> + + <method name="SetPosition"> + <arg direction="in" name="x" type="i"/> + <arg direction="in" name="y" type="i"/> + <arg direction="in" name="coord_type" type="u"/> + <arg direction="out" type="b"/> + </method> + + <method name="SetSize"> + <arg direction="in" name="width" type="i"/> + <arg direction="in" name="height" type="i"/> + <arg direction="out" type="b"/> + </method> + + <method name="ScrollTo"> + <arg direction="in" name="type" type="u"/> + </method> + + <method name="ScrollToPoint"> + <arg direction="in" name="type" type="u"/> + <arg direction="in" name="x" type="i"/> + <arg direction="in" name="y" type="i"/> + </method> + +</interface> +</node> diff --git a/gtk/a11y/atspi/DeviceEventController.xml b/gtk/a11y/atspi/DeviceEventController.xml new file mode 100644 index 0000000000..0af9aac27f --- /dev/null +++ b/gtk/a11y/atspi/DeviceEventController.xml @@ -0,0 +1,64 @@ +<?xml version="1.0" encoding="UTF-8"?> +<node name="/node"> +<interface name="org.a11y.atspi.DeviceEventController"> + + <method name="RegisterKeystrokeListener"> + <arg direction="in" name="listener" type="o"/> + <arg direction="in" name="keys" type="a(iisi)"> + <annotation name="com.trolltech.QtDBus.QtTypeName.In1" value="QSpiKeyTypeArray"/> + </arg> + <arg direction="in" name="mask" type="u"/> + <arg direction="in" name="type" type="au"> + <annotation name="com.trolltech.QtDBus.QtTypeName.In3" value="QSpiEventTypeArray"/> + </arg> + <arg direction="in" name="mode" type="(bbb)"> + <annotation name="com.trolltech.QtDBus.QtTypeName.In4" value="QSpiEventMode"/> + </arg> + <arg direction="out" type="b"/> + </method> + + <method name="DeregisterKeystrokeListener"> + <arg direction="in" name="listener" type="o"/> + <arg direction="in" name="keys" type="a(iisi)"> + <annotation name="com.trolltech.QtDBus.QtTypeName.In1" value="QSpiKeyTypeArray"/> + </arg> + <arg direction="in" name="mask" type="u"/> + <arg direction="in" name="type" type="u"/> + </method> + + <method name="RegisterDeviceEventListener"> + <arg direction="in" name="listener" type="o"/> + <arg direction="in" name="types" type="u"/> + <arg direction="out" type="b"/> + </method> + + <method name="DeregisterDeviceEventListener"> + <arg direction="in" name="listener" type="o"/> + <arg direction="in" name="types" type="u"/> + </method> + + <method name="GenerateKeyboardEvent"> + <arg direction="in" name="keycode" type="i"/> + <arg direction="in" name="keystring" type="s"/> + <arg direction="in" name="type" type="u"/> + </method> + + <method name="GenerateMouseEvent"> + <arg direction="in" name="x" type="i"/> + <arg direction="in" name="y" type="i"/> + <arg direction="in" name="eventName" type="s"/> + </method> + + <method name="NotifyListenersSync"> + <arg direction="in" name="event" type="(uiuuisb)"/> + <arg direction="out" type="b"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiDeviceEvent"/> + </method> + + <method name="NotifyListenersAsync"> + <arg direction="in" name="event" type="(uiuuisb)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiDeviceEvent"/> + </method> + +</interface> +</node> diff --git a/gtk/a11y/atspi/DeviceEventListener.xml b/gtk/a11y/atspi/DeviceEventListener.xml new file mode 100644 index 0000000000..a6dd3b60c8 --- /dev/null +++ b/gtk/a11y/atspi/DeviceEventListener.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?> +<node name="/node"> +<interface name="org.a11y.atspi.DeviceEventListener"> + + <method name="NotifyEvent"> + <arg direction="in" name="event" type="(uiuuisb)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiDeviceEvent"/> + <arg direction="out" type="b"/> + </method> + +</interface> +</node> diff --git a/gtk/a11y/atspi/Document.xml b/gtk/a11y/atspi/Document.xml new file mode 100644 index 0000000000..d12a3060ca --- /dev/null +++ b/gtk/a11y/atspi/Document.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8"?> +<node name="/node"> +<interface name="org.a11y.atspi.Document"> + + <property name="CurrentPageNumber" type="i" access="read"/> + + <property name="PageCount" type="i" access="read"/> + + <method name="GetLocale"> + <arg direction="out" type="s"/> + </method> + + <method name="GetAttributeValue"> + <arg direction="in" name="attributename" type="s"/> + <arg direction="out" type="s"/> + </method> + + <method name="GetAttributes"> + <arg direction="out" type="{ss}"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QSpiAttrubutes"/> + </method> + +</interface> +</node> diff --git a/gtk/a11y/atspi/EditableText.xml b/gtk/a11y/atspi/EditableText.xml new file mode 100644 index 0000000000..09f62f5aa7 --- /dev/null +++ b/gtk/a11y/atspi/EditableText.xml @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="UTF-8"?> +<node name="/node"> +<interface name="org.a11y.atspi.EditableText"> + + <method name="SetTextContents"> + <arg direction="in" name="newContents" type="s"/> + <arg direction="out" type="b"/> + </method> + + <method name="InsertText"> + <arg direction="in" name="position" type="i"/> + <arg direction="in" name="text" type="s"/> + <arg direction="in" name="length" type="i"/> + <arg direction="out" type="b"/> + </method> + + <method name="CopyText"> + <arg direction="in" name="startPos" type="i"/> + <arg direction="in" name="endPos" type="i"/> + </method> + + <method name="CutText"> + <arg direction="in" name="startPos" type="i"/> + <arg direction="in" name="endPos" type="i"/> + <arg direction="out" type="b"/> + </method> + + <method name="DeleteText"> + <arg direction="in" name="startPos" type="i"/> + <arg direction="in" name="endPos" type="i"/> + <arg direction="out" type="b"/> + </method> + + <method name="PasteText"> + <arg direction="in" name="position" type="i"/> + <arg direction="out" type="b"/> + </method> + +</interface> +</node> diff --git a/gtk/a11y/atspi/Event.xml b/gtk/a11y/atspi/Event.xml new file mode 100644 index 0000000000..fac67deb05 --- /dev/null +++ b/gtk/a11y/atspi/Event.xml @@ -0,0 +1,193 @@ +<?xml version="1.0" encoding="UTF-8"?> +<node name="/node"> + +<interface name="org.a11y.atspi.Event.Object"> + <signal name="PropertyChange"><arg direction="in" type="(suuv)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/> + </signal> + <signal name="BoundsChanged"><arg direction="in" type="(suuv)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/> + </signal> + <signal name="LinkSelected"><arg direction="in" type="(suuv)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/> + </signal> + <signal name="StateChanged"><arg direction="in" type="(suuv)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/> + </signal> + <signal name="ChildrenChanged"><arg direction="in" type="(suuv)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/> + </signal> + <signal name="VisibleDataChanged"><arg direction="in" type="(suuv)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/> + </signal> + <signal name="SelectionChanged"><arg direction="in" type="(suuv)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/> + </signal> + <signal name="ModelChanged"><arg direction="in" type="(suuv)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/> + </signal> + <signal name="ActiveDescendantChanged"><arg direction="in" type="(suuv)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/> + </signal> + <signal name="RowInserted"><arg direction="in" type="(suuv)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/> + </signal> + <signal name="RowReordered"><arg direction="in" type="(suuv)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/> + </signal> + <signal name="RowDeleted"><arg direction="in" type="(suuv)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/> + </signal> + <signal name="ColumnInserted"><arg direction="in" type="(suuv)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/> + </signal> + <signal name="ColumnReordered"><arg direction="in" type="(suuv)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/> + </signal> + <signal name="ColumnDeleted"><arg direction="in" type="(suuv)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/> + </signal> + <signal name="TextBoundsChanged"><arg direction="in" type="(suuv)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/> + </signal> + <signal name="TextSelectionChanged"><arg direction="in" type="(suuv)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/> + </signal> + <signal name="TextChanged"><arg direction="in" type="(suuv)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/> + </signal> + <signal name="TextAttributesChanged"><arg direction="in" type="(suuv)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/> + </signal> + <signal name="TextCaretMoved"><arg direction="in" type="(suuv)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/> + </signal> + <signal name="AttributesChanged"><arg direction="in" type="(suuv)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/> + </signal> +</interface> + +<interface name="org.a11y.atspi.Event.Window"> + <signal name="PropertyChange"><arg direction="in" type="(suuv)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/> + </signal> + <signal name="Minimize"><arg direction="in" type="(suuv)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/> + </signal> + <signal name="Maximize"><arg direction="in" type="(suuv)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/> + </signal> + <signal name="Restore"><arg direction="in" type="(suuv)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/> + </signal> + <signal name="Close"><arg direction="in" type="(suuv)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/> + </signal> + <signal name="Create"><arg direction="in" type="(suuv)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/> + </signal> + <signal name="Reparent"><arg direction="in" type="(suuv)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/> + </signal> + <signal name="DesktopCreate"><arg direction="in" type="(suuv)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/> + </signal> + <signal name="DesktopDestroy"><arg direction="in" type="(suuv)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/> + </signal> + <signal name="Destroy"><arg direction="in" type="(suuv)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/> + </signal> + <signal name="Activate"><arg direction="in" type="(suuv)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/> + </signal> + <signal name="Deactivate"><arg direction="in" type="(suuv)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/> + </signal> + <signal name="Raise"><arg direction="in" type="(suuv)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/> + </signal> + <signal name="Lower"><arg direction="in" type="(suuv)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/> + </signal> + <signal name="Move"><arg direction="in" type="(suuv)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/> + </signal> + <signal name="Resize"><arg direction="in" type="(suuv)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/> + </signal> + <signal name="Shade"><arg direction="in" type="(suuv)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/> + </signal> + <signal name="uUshade"><arg direction="in" type="(suuv)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/> + </signal> + <signal name="Restyle"><arg direction="in" type="(suuv)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/> + </signal> +</interface> + +<interface name="org.a11y.atspi.Event.Mouse"> + <signal name="Abs"><arg direction="in" type="(suuv)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/> + </signal> + <signal name="Rel"><arg direction="in" type="(suuv)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/> + </signal> + <signal name="Button"><arg direction="in" type="(suuv)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/> + </signal> +</interface> + +<interface name="org.a11y.atspi.Event.Keyboard"> + <signal name="Modifiers"><arg direction="in" type="(suuv)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/> + </signal> +</interface> + +<interface name="org.a11y.atspi.Event.Terminal"> + <signal name="LineChanged"><arg direction="in" type="(suuv)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/> + </signal> + <signal name="ColumncountChanged"><arg direction="in" type="(suuv)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/> + </signal> + <signal name="LinecountChanged"><arg direction="in" type="(suuv)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/> + </signal> + <signal name="ApplicationChanged"><arg direction="in" type="(suuv)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/> + </signal> + <signal name="CharwidthChanged"><arg direction="in" type="(suuv)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/> + </signal> +</interface> + +<interface name="org.a11y.atspi.Event.Document"> + <signal name="LoadComplete"><arg direction="in" type="(suuv)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/> + </signal> + <signal name="Reload"><arg direction="in" type="(suuv)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/> + </signal> + <signal name="LoadStopped"><arg direction="in" type="(suuv)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/> + </signal> + <signal name="ContentChanged"><arg direction="in" type="(suuv)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/> + </signal> + <signal name="AttributesChanged"><arg direction="in" type="(suuv)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/> + </signal> + <signal name="PageChanged"><arg direction="in" type="(suuv)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/> + </signal> +</interface> + +<interface name="org.a11y.atspi.Event.Focus"> + <signal name="Focus"><arg direction="in" type="(suuv)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/> + </signal> +</interface> + +</node> diff --git a/gtk/a11y/atspi/Hyperlink.xml b/gtk/a11y/atspi/Hyperlink.xml new file mode 100644 index 0000000000..75f809931f --- /dev/null +++ b/gtk/a11y/atspi/Hyperlink.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<node name="/node"> +<interface name="org.a11y.atspi.Hyperlink"> + + <property name="NAnchors" type="n" access="read"/> + + <property name="StartIndex" type="i" access="read"/> + + <property name="EndIndex" type="i" access="read"/> + + <method name="GetObject"> + <arg direction="in" name="i" type="i"/> + <arg direction="out" type="(so)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QSpiObjectReference"/> + </method> + + <method name="GetURI"> + <arg direction="in" name="i" type="i"/> + <arg direction="out" type="s"/> + </method> + + <method name="IsValid"> + <arg direction="out" type="b"/> + </method> + +</interface> +</node> diff --git a/gtk/a11y/atspi/Hypertext.xml b/gtk/a11y/atspi/Hypertext.xml new file mode 100644 index 0000000000..18a3b342a8 --- /dev/null +++ b/gtk/a11y/atspi/Hypertext.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<node name="/node"> +<interface name="org.a11y.atspi.Hypertext"> + + <method name="GetNLinks"> + <arg direction="out" type="i"/> + </method> + + <method name="GetLink"> + <arg direction="in" name="linkIndex" type="i"/> + <arg direction="out" type="(so)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QSpiObjectReference"/> + </method> + + <method name="GetLinkIndex"> + <arg direction="in" name="characterIndex" type="i"/> + <arg direction="out" type="i"/> + </method> + +</interface> +</node> diff --git a/gtk/a11y/atspi/Image.xml b/gtk/a11y/atspi/Image.xml new file mode 100644 index 0000000000..43536ee956 --- /dev/null +++ b/gtk/a11y/atspi/Image.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<node name="/node"> +<interface name="org.a11y.atspi.Image"> + + <property name="ImageDescription" type="s" access="read"/> + + <property name="ImageLocale" type="s" access="read"/> + + <method name="GetImageExtents"> + <arg direction="in" name="coordType" type="u"/> + <arg direction="out" type="(iiii)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QSpiRect"/> + </method> + + <method name="GetImagePosition"> + <arg direction="out" name="x" type="i"/> + <arg direction="out" name="y" type="i"/> + <arg direction="in" name="coordType" type="u"/> + </method> + + <method name="GetImageSize"> + <arg direction="out" name="width" type="i"/> + <arg direction="out" name="height" type="i"/> + </method> + +</interface> +</node> diff --git a/gtk/a11y/atspi/Registry.xml b/gtk/a11y/atspi/Registry.xml new file mode 100644 index 0000000000..a3ab93c3c2 --- /dev/null +++ b/gtk/a11y/atspi/Registry.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="UTF-8"?> +<node name="/node"> +<interface name="org.a11y.atspi.Registry"> + + <method name="RegisterEvent"> + <arg direction="in" name="event" type="s"> + </arg> + </method> + + <method name="DeregisterEvent"> + <arg direction="in" name="event" type="s"> + </arg> + </method> + + <method name="GetRegisteredEvents"> + <arg direction="out" name="events" type="a(ss)"> + </arg> + </method> + + <signal name="EventListenerRegistered"> + <arg direction="out" name="bus" type="s"/> + <arg direction="out" name="path" type="s"/> + </signal> + + <signal name="EventListenerDeregistered"> + <arg direction="out" name="bus" type="s"/> + <arg direction="out" name="path" type="s"/> + </signal> +</interface> +</node> diff --git a/gtk/a11y/atspi/Selection.xml b/gtk/a11y/atspi/Selection.xml new file mode 100644 index 0000000000..2e1918757a --- /dev/null +++ b/gtk/a11y/atspi/Selection.xml @@ -0,0 +1,42 @@ +<?xml version="1.0" encoding="UTF-8"?> +<node name="/node"> +<interface name="org.a11y.atspi.Selection"> + + <property name="NSelectedChildren" type="i" access="read"/> + + <method name="GetSelectedChild"> + <arg direction="in" name="selectedChildIndex" type="i"/> + <arg direction="out" type="(so)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QSpiObjectReference"/> + </method> + + <method name="SelectChild"> + <arg direction="in" name="childIndex" type="i"/> + <arg direction="out" type="b"/> + </method> + + <method name="DeselectSelectedChild"> + <arg direction="in" name="selectedChildIndex" type="i"/> + <arg direction="out" type="b"/> + </method> + + <method name="IsChildSelected"> + <arg direction="in" name="childIndex" type="i"/> + <arg direction="out" type="b"/> + </method> + + <method name="SelectAll"> + <arg direction="out" type="b"/> + </method> + + <method name="ClearSelection"> + <arg direction="out" type="b"/> + </method> + + <method name="DeselectChild"> + <arg direction="in" name="childIndex" type="i"/> + <arg direction="out" type="b"/> + </method> + +</interface> +</node> diff --git a/gtk/a11y/atspi/Socket.xml b/gtk/a11y/atspi/Socket.xml new file mode 100644 index 0000000000..8da9948833 --- /dev/null +++ b/gtk/a11y/atspi/Socket.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<node name="/node"> +<interface name="org.a11y.atspi.Socket"> + + <method name="Embed"> + <arg direction="in" name="plug" type="(so)"> + <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiObjectReference"/> + </arg> + <arg direction="out" name="socket" type="(so)"> + <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QSpiObjectReference"/> + </arg> + </method> + + <method name="Unembed"> + <arg direction="in" name="plug" type="(so)"> + <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiObjectReference"/> + </arg> + </method> + + <signal name="Available"> + <arg direction="in" name="socket" type="(so)"> + <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiObjectReference"/> + </arg> + </signal> + +</interface> +</node> diff --git a/gtk/a11y/atspi/Table.xml b/gtk/a11y/atspi/Table.xml new file mode 100644 index 0000000000..181acaa6af --- /dev/null +++ b/gtk/a11y/atspi/Table.xml @@ -0,0 +1,135 @@ +<?xml version="1.0" encoding="UTF-8"?> +<node name="/node"> +<interface name="org.a11y.atspi.Table"> + + <property name="NRows" type="i" access="read"/> + + <property name="NColumns" type="i" access="read"/> + + <property name="Caption" type="(so)" access="read"> + <annotation name="com.trolltech.QtDBus.QtTypeName" value="QSpiObjectReference"/> + </property> + + <property name="Summary" type="(so)" access="read"> + <annotation name="com.trolltech.QtDBus.QtTypeName" value="QSpiObjectReference"/> + </property> + + <property name="NSelectedRows" type="i" access="read"/> + + <property name="NSelectedColumns" type="i" access="read"/> + + <method name="GetAccessibleAt"> + <arg direction="in" name="row" type="i"/> + <arg direction="in" name="column" type="i"/> + <arg direction="out" type="(so)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QSpiObjectReference"/> + </method> + + <method name="GetIndexAt"> + <arg direction="in" name="row" type="i"/> + <arg direction="in" name="column" type="i"/> + <arg direction="out" type="i"/> + </method> + + <method name="GetRowAtIndex"> + <arg direction="in" name="index" type="i"/> + <arg direction="out" type="i"/> + </method> + + <method name="GetColumnAtIndex"> + <arg direction="in" name="index" type="i"/> + <arg direction="out" type="i"/> + </method> + + <method name="GetRowDescription"> + <arg direction="in" name="row" type="i"/> + <arg direction="out" type="s"/> + </method> + + <method name="GetColumnDescription"> + <arg direction="in" name="column" type="i"/> + <arg direction="out" type="s"/> + </method> + + <method name="GetRowExtentAt"> + <arg direction="in" name="row" type="i"/> + <arg direction="in" name="column" type="i"/> + <arg direction="out" type="i"/> + </method> + + <method name="GetColumnExtentAt"> + <arg direction="in" name="row" type="i"/> + <arg direction="in" name="column" type="i"/> + <arg direction="out" type="i"/> + </method> + + <method name="GetRowHeader"> + <arg direction="in" name="row" type="i"/> + <arg direction="out" type="(so)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QSpiObjectReference"/> + </method> + + <method name="GetColumnHeader"> + <arg direction="in" name="column" type="i"/> + <arg direction="out" type="(so)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QSpiObjectReference"/> + </method> + + <method name="GetSelectedRows"> + <arg direction="out" type="ai"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QSpiIntList"/> + </method> + + <method name="GetSelectedColumns"> + <arg direction="out" type="ai"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QSpiIntList"/> + </method> + + <method name="IsRowSelected"> + <arg direction="in" name="row" type="i"/> + <arg direction="out" type="b"/> + </method> + + <method name="IsColumnSelected"> + <arg direction="in" name="column" type="i"/> + <arg direction="out" type="b"/> + </method> + + <method name="IsSelected"> + <arg direction="in" name="row" type="i"/> + <arg direction="in" name="column" type="i"/> + <arg direction="out" type="b"/> + </method> + + <method name="AddRowSelection"> + <arg direction="in" name="row" type="i"/> + <arg direction="out" type="b"/> + </method> + + <method name="AddColumnSelection"> + <arg direction="in" name="column" type="i"/> + <arg direction="out" type="b"/> + </method> + + <method name="RemoveRowSelection"> + <arg direction="in" name="row" type="i"/> + <arg direction="out" type="b"/> + </method> + + <method name="RemoveColumnSelection"> + <arg direction="in" name="column" type="i"/> + <arg direction="out" type="b"/> + </method> + + <method name="GetRowColumnExtentsAtIndex"> + <arg direction="in" name="index" type="i"/> + <arg direction="out" type="b"/> + <arg direction="out" name="row" type="i"/> + <arg direction="out" name="col" type="i"/> + <arg direction="out" name="row_extents" type="i"/> + <arg direction="out" name="col_extents" type="i"/> + <arg direction="out" name="is_selected" type="b"/> + </method> + +</interface> +</node> diff --git a/gtk/a11y/atspi/TableCell.xml b/gtk/a11y/atspi/TableCell.xml new file mode 100644 index 0000000000..c60a074fd4 --- /dev/null +++ b/gtk/a11y/atspi/TableCell.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<node name="/node"> +<interface name="org.a11y.atspi.TableCell"> + + <property access="read" name="ColumnSpan" type="i" /> + + <property access="read" name="Position" type="(ii)" /> + + <property access="read" name="RowSpan" type="i" /> + + <property access="read" name="Table" type="(so)" /> + + <method name="GetRowColumnSpan"> + <arg direction="out" type="b" /> + <arg direction="out" name="row" type="i" /> + <arg direction="out" name="col" type="i" /> + <arg direction="out" name="row_extents" type="i" /> + <arg direction="out" name="col_extents" type="i" /> + </method> + +</interface> +</node> diff --git a/gtk/a11y/atspi/Text.xml b/gtk/a11y/atspi/Text.xml new file mode 100644 index 0000000000..313131e6f8 --- /dev/null +++ b/gtk/a11y/atspi/Text.xml @@ -0,0 +1,170 @@ +<?xml version="1.0" encoding="UTF-8"?> +<node name="/node"> +<interface name="org.a11y.atspi.Text"> + + <property name="CharacterCount" type="i" access="read"/> + + <property name="CaretOffset" type="i" access="read"/> + + <method name="GetStringAtOffset"> + <arg direction="in" name="offset" type="i"/> + <arg direction="in" name="granularity" type="u"/> + <arg direction="out" type="s"/> + <arg direction="out" name="startOffset" type="i"/> + <arg direction="out" name="endOffset" type="i"/> + </method> + + <method name="GetText"> + <arg direction="in" name="startOffset" type="i"/> + <arg direction="in" name="endOffset" type="i"/> + <arg direction="out" type="s"/> + </method> + + <method name="SetCaretOffset"> + <arg direction="in" name="offset" type="i"/> + <arg direction="out" type="b"/> + </method> + + <method name="GetTextBeforeOffset"> + <arg direction="in" name="offset" type="i"/> + <arg direction="in" name="type" type="u"/> + <arg direction="out" type="s"/> + <arg direction="out" name="startOffset" type="i"/> + <arg direction="out" name="endOffset" type="i"/> + </method> + + <method name="GetTextAtOffset"> + <arg direction="in" name="offset" type="i"/> + <arg direction="in" name="type" type="u"/> + <arg direction="out" type="s"/> + <arg direction="out" name="startOffset" type="i"/> + <arg direction="out" name="endOffset" type="i"/> + </method> + + <method name="GetTextAfterOffset"> + <arg direction="in" name="offset" type="i"/> + <arg direction="in" name="type" type="u"/> + <arg direction="out" type="s"/> + <arg direction="out" name="startOffset" type="i"/> + <arg direction="out" name="endOffset" type="i"/> + </method> + + <method name="GetCharacterAtOffset"> + <arg name="offset" type="i" direction="in"/> + <arg type="i" direction="out"/> + </method> + + <method name="GetAttributeValue"> + <arg direction="in" name="offset" type="i"/> + <arg direction="in" name="attributeName" type="s"/> + <arg direction="out" type="s"/> + </method> + + <method name="GetAttributes"> + <arg direction="in" name="offset" type="i"/> + <arg direction="out" type="a{ss}"/> + <arg direction="out" name="startOffset" type="i"/> + <arg direction="out" name="endOffset" type="i"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QSpiAttributeSet"/> + </method> + + <method name="GetDefaultAttributes"> + <arg direction="out" type="a{ss}"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QSpiAttributeSet"/> + </method> + + <method name="GetCharacterExtents"> + <arg direction="in" name="offset" type="i"/> + <arg direction="out" name="x" type="i"/> + <arg direction="out" name="y" type="i"/> + <arg direction="out" name="width" type="i"/> + <arg direction="out" name="height" type="i"/> + <arg direction="in" name="coordType" type="u"/> + </method> + + <method name="GetOffsetAtPoint"> + <arg direction="in" name="x" type="i"/> + <arg direction="in" name="y" type="i"/> + <arg direction="in" name="coordType" type="u"/> + <arg direction="out" type="i"/> + </method> + + <method name="GetNSelections"> + <arg direction="out" type="i"/> + </method> + + <method name="GetSelection"> + <arg direction="in" name="selectionNum" type="i"/> + <arg direction="out" name="startOffset" type="i"/> + <arg direction="out" name="endOffset" type="i"/> + </method> + + <method name="AddSelection"> + <arg direction="in" name="startOffset" type="i"/> + <arg direction="in" name="endOffset" type="i"/> + <arg direction="out" type="b"/> + </method> + + <method name="RemoveSelection"> + <arg direction="in" name="selectionNum" type="i"/> + <arg direction="out" type="b"/> + </method> + + <method name="SetSelection"> + <arg direction="in" name="selectionNum" type="i"/> + <arg direction="in" name="startOffset" type="i"/> + <arg direction="in" name="endOffset" type="i"/> + <arg direction="out" type="b"/> + </method> + + <method name="GetRangeExtents"> + <arg direction="in" name="startOffset" type="i"/> + <arg direction="in" name="endOffset" type="i"/> + <arg direction="out" name="x" type="i"/> + <arg direction="out" name="y" type="i"/> + <arg direction="out" name="width" type="i"/> + <arg direction="out" name="height" type="i"/> + <arg direction="in" name="coordType" type="u"/> + </method> + + <method name="GetBoundedRanges"> + <arg direction="in" name="x" type="i"/> + <arg direction="in" name="y" type="i"/> + <arg direction="in" name="width" type="i"/> + <arg direction="in" name="height" type="i"/> + <arg direction="in" name="coordType" type="u"/> + <arg direction="in" name="xClipType" type="u"/> + <arg direction="in" name="yClipType" type="u"/> + <arg direction="out" type="a(iisv)"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QSpiRangeList"/> + </method> + + <method name="GetAttributeRun"> + <arg direction="in" name="offset" type="i"/> + <arg direction="in" name="includeDefaults" type="b"/> + <arg direction="out" type="a{ss}"/> + <arg direction="out" name="startOffset" type="i"/> + <arg direction="out" name="endOffset" type="i"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QSpiAttributeSet"/> + </method> + + <method name="GetDefaultAttributeSet"> + <arg direction="out" type="a{ss}"/> + </method> + + <method name="ScrollSubstringTo"> + <arg direction="in" name="startOffset" type="i"/> + <arg direction="in" name="endOffset" type="i"/> + <arg direction="in" name="type" type="u"/> + </method> + + <method name="ScrollSubstringToPoint"> + <arg direction="in" name="startOffset" type="i"/> + <arg direction="in" name="endOffset" type="i"/> + <arg direction="in" name="type" type="u"/> + <arg direction="in" name="x" type="i"/> + <arg direction="in" name="y" type="i"/> + </method> + +</interface> +</node> diff --git a/gtk/a11y/atspi/Value.xml b/gtk/a11y/atspi/Value.xml new file mode 100644 index 0000000000..ccd6c7aa59 --- /dev/null +++ b/gtk/a11y/atspi/Value.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<node name="/node"> +<interface name="org.a11y.atspi.Value"> + + <property name="MinimumValue" type="d" access="read"/> + + <property name="MaximumValue" type="d" access="read"/> + + <property name="MinimumIncrement" type="d" access="read"/> + + <property name="CurrentValue" type="d" access="readwrite"/> + +</interface> +</node> diff --git a/gtk/a11y/atspi/meson.build b/gtk/a11y/atspi/meson.build new file mode 100644 index 0000000000..05be90c2e7 --- /dev/null +++ b/gtk/a11y/atspi/meson.build @@ -0,0 +1,65 @@ +atspi_xml = [ + 'Accessible.xml', + 'Action.xml', + 'Application.xml', + 'Cache.xml', + 'Collection.xml', + 'Component.xml', + 'DeviceEventController.xml', + 'DeviceEventListener.xml', + 'Document.xml', + 'EditableText.xml', + 'Event.xml', + 'Hyperlink.xml', + 'Hypertext.xml', + 'Image.xml', + 'Registry.xml', + 'Selection.xml', + 'Socket.xml', + 'Table.xml', + 'TableCell.xml', + 'Text.xml', + 'Value.xml', +] + +gdbus_codegen = find_program('gdbus-codegen') +atspi_src = [] +foreach xml: atspi_xml + obj_name = xml.split('.').get(0) + + # We cannot use gnome.gdbus_codegen() directly because we only care about + # the interface definitions, not the whole GTypeInterface/GObject proxy + # classes + gen_hdr = custom_target('atspi-' + obj_name.to_lower() + '-hdr', + input: xml, + output: 'atspi-' + obj_name.to_lower() + '.h', + command: [ + gdbus_codegen, + '--interface-prefix=org.a11y.atspi', + '--c-namespace=Atspi', + '--pragma-once', + '--interface-info-header', + '--output=@OUTPUT@', + '@INPUT@', + ], + build_by_default: true, + ) + + gen_src = custom_target('atspi-' + obj_name.to_lower() + '-src', + input: xml, + output: 'atspi-' + obj_name.to_lower() + '.c', + command: [ + gdbus_codegen, + '--interface-prefix=org.a11y.atspi', + '--c-namespace=Atspi', + '--interface-info-body', + '--output=@OUTPUT@', + '@INPUT@', + ], + build_by_default: true, + ) + + atspi_src += [gen_src, gen_hdr] +endforeach + +gtk_a11y_src += atspi_src diff --git a/gtk/a11y/gtkatspicache.c b/gtk/a11y/gtkatspicache.c new file mode 100644 index 0000000000..9681fd613e --- /dev/null +++ b/gtk/a11y/gtkatspicache.c @@ -0,0 +1,218 @@ +/* gtkatspicache.c: AT-SPI object cache + * + * Copyright 2020 holder + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include "gtkatspicacheprivate.h" + +#include "gtkdebug.h" + +#include "a11y/atspi/atspi-cache.h" + +struct _GtkAtSpiCache +{ + GObject parent_instance; + + char *cache_path; + GDBusConnection *connection; + + GHashTable *contexts; +}; + +enum +{ + PROP_CACHE_PATH = 1, + PROP_CONNECTION, + + N_PROPS +}; + +static GParamSpec *obj_props[N_PROPS]; + +G_DEFINE_TYPE (GtkAtSpiCache, gtk_at_spi_cache, G_TYPE_OBJECT) + +static void +gtk_at_spi_cache_finalize (GObject *gobject) +{ + GtkAtSpiCache *self = GTK_AT_SPI_CACHE (gobject); + + g_clear_pointer (&self->contexts, g_hash_table_unref); + g_clear_object (&self->connection); + g_free (self->cache_path); + + G_OBJECT_CLASS (gtk_at_spi_cache_parent_class)->finalize (gobject); +} + +static void +gtk_at_spi_cache_set_property (GObject *gobject, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkAtSpiCache *self = GTK_AT_SPI_CACHE (gobject); + + switch (prop_id) + { + case PROP_CACHE_PATH: + g_free (self->cache_path); + self->cache_path = g_value_dup_string (value); + break; + + case PROP_CONNECTION: + g_clear_object (&self->connection); + self->connection = g_value_dup_object (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + } +} + +static void +handle_cache_method (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *method_name, + GVariant *parameters, + GDBusMethodInvocation *invocation, + gpointer user_data) +{ + g_printerr ("[Cache] Method '%s' on interface '%s' for object '%s' from '%s'\n", + method_name, interface_name, object_path, sender); + +} + +static GVariant * +handle_cache_get_property (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *property_name, + GError **error, + gpointer user_data) +{ + GVariant *res = NULL; + + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + "Unknown property '%s'", property_name); + + return res; +} + + +static const GDBusInterfaceVTable cache_vtable = { + handle_cache_method, + handle_cache_get_property, + NULL, +}; + +static void +gtk_at_spi_cache_constructed (GObject *gobject) +{ + GtkAtSpiCache *self = GTK_AT_SPI_CACHE (gobject); + + g_assert (self->connection); + g_assert (self->cache_path); + + g_dbus_connection_register_object (self->connection, + self->cache_path, + (GDBusInterfaceInfo *) &atspi_cache_interface, + &cache_vtable, + self, + NULL, + NULL); + + GTK_NOTE (A11Y, g_message ("Cache registered at %s", self->cache_path)); + + G_OBJECT_CLASS (gtk_at_spi_cache_parent_class)->constructed (gobject); +} + +static void +gtk_at_spi_cache_class_init (GtkAtSpiCacheClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->constructed = gtk_at_spi_cache_constructed; + gobject_class->set_property = gtk_at_spi_cache_set_property; + gobject_class->finalize = gtk_at_spi_cache_finalize; + + obj_props[PROP_CACHE_PATH] = + g_param_spec_string ("cache-path", NULL, NULL, + NULL, + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + + obj_props[PROP_CONNECTION] = + g_param_spec_object ("connection", NULL, NULL, + G_TYPE_DBUS_CONNECTION, + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (gobject_class, N_PROPS, obj_props); +} + +static void +gtk_at_spi_cache_init (GtkAtSpiCache *self) +{ + self->contexts = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, + NULL); +} + +GtkAtSpiCache * +gtk_at_spi_cache_new (GDBusConnection *connection, + const char *cache_path) +{ + g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL); + g_return_val_if_fail (cache_path != NULL, NULL); + + return g_object_new (GTK_TYPE_AT_SPI_CACHE, + "connection", connection, + "cache-path", cache_path, + NULL); +} + +void +gtk_at_spi_cache_add_context (GtkAtSpiCache *self, + const char *path, + GtkATContext *context) +{ + g_return_if_fail (GTK_IS_AT_SPI_CACHE (self)); + g_return_if_fail (path != NULL); + g_return_if_fail (GTK_IS_AT_CONTEXT (context)); + + if (g_hash_table_contains (self->contexts, path)) + return; + + g_hash_table_insert (self->contexts, g_strdup (path), context); +} + +GtkATContext * +gtk_at_spi_cache_get_context (GtkAtSpiCache *self, + const char *path) +{ + g_return_val_if_fail (GTK_IS_AT_SPI_CACHE (self), NULL); + g_return_val_if_fail (path != NULL, NULL); + + return g_hash_table_lookup (self->contexts, path); +} diff --git a/gtk/a11y/gtkatspicacheprivate.h b/gtk/a11y/gtkatspicacheprivate.h new file mode 100644 index 0000000000..12952a3c87 --- /dev/null +++ b/gtk/a11y/gtkatspicacheprivate.h @@ -0,0 +1,44 @@ +/* gtkatspicacheprivate.h: AT-SPI object cache + * + * Copyright 2020 GNOME Foundation + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "gtkatcontextprivate.h" + +G_BEGIN_DECLS + +#define GTK_TYPE_AT_SPI_CACHE (gtk_at_spi_cache_get_type()) + +G_DECLARE_FINAL_TYPE (GtkAtSpiCache, gtk_at_spi_cache, GTK, AT_SPI_CACHE, GObject) + +GtkAtSpiCache * +gtk_at_spi_cache_new (GDBusConnection *connection, + const char *cache_path); + +void +gtk_at_spi_cache_add_context (GtkAtSpiCache *self, + const char *path, + GtkATContext *context); + +GtkATContext * +gtk_at_spi_cache_get_context (GtkAtSpiCache *self, + const char *path); + +G_END_DECLS diff --git a/gtk/a11y/gtkatspicontext.c b/gtk/a11y/gtkatspicontext.c new file mode 100644 index 0000000000..36f4636862 --- /dev/null +++ b/gtk/a11y/gtkatspicontext.c @@ -0,0 +1,1416 @@ +/* gtkatspicontext.c: AT-SPI GtkATContext implementation + * + * Copyright 2020 GNOME Foundation + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include "gtkatspicontextprivate.h" + +#include "gtkaccessibleprivate.h" + +#include "gtkatspicacheprivate.h" +#include "gtkatspirootprivate.h" +#include "gtkatspiprivate.h" +#include "gtkatspiutilsprivate.h" +#include "gtkatspitextprivate.h" +#include "gtkatspieditabletextprivate.h" +#include "gtkatspivalueprivate.h" +#include "gtkatspiselectionprivate.h" + +#include "a11y/atspi/atspi-accessible.h" +#include "a11y/atspi/atspi-text.h" +#include "a11y/atspi/atspi-editabletext.h" +#include "a11y/atspi/atspi-value.h" +#include "a11y/atspi/atspi-selection.h" + +#include "gtkdebug.h" +#include "gtkeditable.h" +#include "gtkentryprivate.h" +#include "gtkroot.h" +#include "gtktextview.h" +#include "gtkwindow.h" +#include "gtkstack.h" + +#include <gio/gio.h> + +#include <locale.h> + +#if defined(GDK_WINDOWING_WAYLAND) +# include <gdk/wayland/gdkwaylanddisplay.h> +#endif +#if defined(GDK_WINDOWING_X11) +# include <gdk/x11/gdkx11display.h> +# include <gdk/x11/gdkx11property.h> +#endif + +/* We create a GtkAtSpiContext object for (almost) every widget. + * + * Each context implements a number of Atspi interfaces on a D-Bus + * object. The context objects are connected into a tree by the + * Parent property and GetChildAtIndex method of the Accessible + * interface. + * + * The tree is an almost perfect mirror image of the widget tree, + * with a few notable exceptions: + * + * - We don't create contexts for the GtkText widgets inside + * entry wrappers, since the Text functionality is represented + * on the entry contexts. + * + * - We insert non-widget backed context objects for each page + * of a stack. The main purpose of these extra context is to + * hold the TAB_PANEL role and be the target of the CONTROLS + * relation with their corresponding tabs (in the stack + * switcher or notebook). + */ + +struct _GtkAtSpiContext +{ + GtkATContext parent_instance; + + /* The root object, used as a entry point */ + GtkAtSpiRoot *root; + + /* The cache object, used to retrieve ATContexts */ + GtkAtSpiCache *cache; + + /* The address for the ATSPI accessibility bus */ + char *bus_address; + + /* The object path of the ATContext on the bus */ + char *context_path; + + /* Just a pointer; the connection is owned by the GtkAtSpiRoot + * associated to the GtkATContext + */ + GDBusConnection *connection; + + /* Accerciser refuses to work unless we implement a GetInterface + * call that returns a list of all implemented interfaces. We + * collect the answer here. + */ + GVariant *interfaces; + + guint registration_ids[20]; + guint n_registered_objects; +}; + +enum +{ + PROP_BUS_ADDRESS = 1, + + N_PROPS +}; + +static GParamSpec *obj_props[N_PROPS]; + +G_DEFINE_TYPE (GtkAtSpiContext, gtk_at_spi_context, GTK_TYPE_AT_CONTEXT) + +static void +set_atspi_state (guint64 *states, + AtspiStateType state) +{ + *states |= (G_GUINT64_CONSTANT (1) << state); +} + +static void +unset_atspi_state (guint64 *states, + AtspiStateType state) +{ + *states &= ~(G_GUINT64_CONSTANT (1) << state); +} + +static void +collect_states (GtkAtSpiContext *self, + GVariantBuilder *builder) +{ + GtkATContext *ctx = GTK_AT_CONTEXT (self); + GtkAccessibleValue *value; + GtkAccessible *accessible; + guint64 states = 0; + + accessible = gtk_at_context_get_accessible (ctx); + + set_atspi_state (&states, ATSPI_STATE_VISIBLE); + + if (ctx->accessible_role == GTK_ACCESSIBLE_ROLE_TEXT_BOX || + ctx->accessible_role == GTK_ACCESSIBLE_ROLE_SEARCH_BOX || + ctx->accessible_role == GTK_ACCESSIBLE_ROLE_SPIN_BUTTON) + set_atspi_state (&states, ATSPI_STATE_EDITABLE); + + if (gtk_at_context_has_accessible_property (ctx, GTK_ACCESSIBLE_PROPERTY_READ_ONLY)) + { + value = gtk_at_context_get_accessible_property (ctx, GTK_ACCESSIBLE_PROPERTY_READ_ONLY); + if (gtk_boolean_accessible_value_get (value)) + { + set_atspi_state (&states, ATSPI_STATE_READ_ONLY); + unset_atspi_state (&states, ATSPI_STATE_EDITABLE); + } + } + + if (gtk_accessible_get_platform_state (accessible, GTK_ACCESSIBLE_PLATFORM_STATE_FOCUSABLE)) + set_atspi_state (&states, ATSPI_STATE_FOCUSABLE); + + if (gtk_accessible_get_platform_state (accessible, GTK_ACCESSIBLE_PLATFORM_STATE_FOCUSED)) + set_atspi_state (&states, ATSPI_STATE_FOCUSED); + + if (gtk_at_context_has_accessible_property (ctx, GTK_ACCESSIBLE_PROPERTY_ORIENTATION)) + { + value = gtk_at_context_get_accessible_property (ctx, GTK_ACCESSIBLE_PROPERTY_ORIENTATION); + if (gtk_orientation_accessible_value_get (value) == GTK_ORIENTATION_HORIZONTAL) + set_atspi_state (&states, ATSPI_STATE_HORIZONTAL); + else + set_atspi_state (&states, ATSPI_STATE_VERTICAL); + } + + if (gtk_at_context_has_accessible_property (ctx, GTK_ACCESSIBLE_PROPERTY_MODAL)) + { + value = gtk_at_context_get_accessible_property (ctx, GTK_ACCESSIBLE_PROPERTY_MODAL); + if (gtk_boolean_accessible_value_get (value)) + set_atspi_state (&states, ATSPI_STATE_MODAL); + } + + if (gtk_at_context_has_accessible_property (ctx, GTK_ACCESSIBLE_PROPERTY_MULTI_LINE)) + { + value = gtk_at_context_get_accessible_property (ctx, GTK_ACCESSIBLE_PROPERTY_MULTI_LINE); + if (gtk_boolean_accessible_value_get (value)) + set_atspi_state (&states, ATSPI_STATE_MULTI_LINE); + } + + if (gtk_at_context_has_accessible_state (ctx, GTK_ACCESSIBLE_STATE_BUSY)) + { + value = gtk_at_context_get_accessible_state (ctx, GTK_ACCESSIBLE_STATE_BUSY); + if (gtk_boolean_accessible_value_get (value)) + set_atspi_state (&states, ATSPI_STATE_BUSY); + } + + if (gtk_at_context_has_accessible_state (ctx, GTK_ACCESSIBLE_STATE_CHECKED)) + { + value = gtk_at_context_get_accessible_state (ctx, GTK_ACCESSIBLE_STATE_CHECKED); + switch (gtk_tristate_accessible_value_get (value)) + { + case GTK_ACCESSIBLE_TRISTATE_TRUE: + set_atspi_state (&states, ATSPI_STATE_CHECKED); + break; + case GTK_ACCESSIBLE_TRISTATE_MIXED: + set_atspi_state (&states, ATSPI_STATE_INDETERMINATE); + break; + case GTK_ACCESSIBLE_TRISTATE_FALSE: + default: + break; + } + } + + if (gtk_at_context_has_accessible_state (ctx, GTK_ACCESSIBLE_STATE_DISABLED)) + { + value = gtk_at_context_get_accessible_state (ctx, GTK_ACCESSIBLE_STATE_DISABLED); + if (!gtk_boolean_accessible_value_get (value)) + set_atspi_state (&states, ATSPI_STATE_SENSITIVE); + } + else + set_atspi_state (&states, ATSPI_STATE_SENSITIVE); + + if (gtk_at_context_has_accessible_state (ctx, GTK_ACCESSIBLE_STATE_EXPANDED)) + { + value = gtk_at_context_get_accessible_state (ctx, GTK_ACCESSIBLE_STATE_EXPANDED); + if (value->value_class->type == GTK_ACCESSIBLE_VALUE_TYPE_BOOLEAN) + { + set_atspi_state (&states, ATSPI_STATE_EXPANDABLE); + if (gtk_boolean_accessible_value_get (value)) + set_atspi_state (&states, ATSPI_STATE_EXPANDED); + } + } + + if (gtk_at_context_has_accessible_state (ctx, GTK_ACCESSIBLE_STATE_INVALID)) + { + value = gtk_at_context_get_accessible_state (ctx, GTK_ACCESSIBLE_STATE_INVALID); + switch (gtk_invalid_accessible_value_get (value)) + { + case GTK_ACCESSIBLE_INVALID_TRUE: + case GTK_ACCESSIBLE_INVALID_GRAMMAR: + case GTK_ACCESSIBLE_INVALID_SPELLING: + set_atspi_state (&states, ATSPI_STATE_INVALID); + break; + case GTK_ACCESSIBLE_INVALID_FALSE: + default: + break; + } + } + + if (gtk_at_context_has_accessible_state (ctx, GTK_ACCESSIBLE_STATE_PRESSED)) + { + value = gtk_at_context_get_accessible_state (ctx, GTK_ACCESSIBLE_STATE_PRESSED); + switch (gtk_tristate_accessible_value_get (value)) + { + case GTK_ACCESSIBLE_TRISTATE_TRUE: + set_atspi_state (&states, ATSPI_STATE_PRESSED); + break; + case GTK_ACCESSIBLE_TRISTATE_MIXED: + set_atspi_state (&states, ATSPI_STATE_INDETERMINATE); + break; + case GTK_ACCESSIBLE_TRISTATE_FALSE: + default: + break; + } + } + + if (gtk_at_context_has_accessible_state (ctx, GTK_ACCESSIBLE_STATE_SELECTED)) + { + value = gtk_at_context_get_accessible_state (ctx, GTK_ACCESSIBLE_STATE_SELECTED); + if (value->value_class->type == GTK_ACCESSIBLE_VALUE_TYPE_BOOLEAN) + { + set_atspi_state (&states, ATSPI_STATE_SELECTABLE); + if (gtk_boolean_accessible_value_get (value)) + set_atspi_state (&states, ATSPI_STATE_SELECTED); + } + } + + g_variant_builder_add (builder, "u", (guint32) (states & 0xffffffff)); + g_variant_builder_add (builder, "u", (guint32) (states >> 32)); +} + +static void +collect_relations (GtkAtSpiContext *self, + GVariantBuilder *builder) +{ + GtkATContext *ctx = GTK_AT_CONTEXT (self); + struct { + GtkAccessibleRelation r; + AtspiRelationType s; + } map[] = { + { GTK_ACCESSIBLE_RELATION_LABELLED_BY, ATSPI_RELATION_LABELLED_BY }, + { GTK_ACCESSIBLE_RELATION_CONTROLS, ATSPI_RELATION_CONTROLLER_FOR }, + { GTK_ACCESSIBLE_RELATION_DESCRIBED_BY, ATSPI_RELATION_DESCRIBED_BY }, + { GTK_ACCESSIBLE_RELATION_FLOW_TO, ATSPI_RELATION_FLOWS_TO}, + }; + GtkAccessibleValue *value; + GList *list, *l; + GtkATContext *target_ctx; + const char *unique_name; + int i; + + unique_name = g_dbus_connection_get_unique_name (self->connection); + + for (i = 0; i < G_N_ELEMENTS (map); i++) + { + if (!gtk_at_context_has_accessible_relation (ctx, map[i].r)) + continue; + + GVariantBuilder b = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("a(so)")); + + value = gtk_at_context_get_accessible_relation (ctx, map[i].r); + list = gtk_reference_list_accessible_value_get (value); + + for (l = list; l; l = l->next) + { + target_ctx = gtk_accessible_get_at_context (GTK_ACCESSIBLE (l->data)); + g_variant_builder_add (&b, "(so)", + unique_name, + GTK_AT_SPI_CONTEXT (target_ctx)->context_path); + } + + g_variant_builder_add (builder, "(ua(so))", map[i].s, &b); + } +} + +static int +get_index_in_parent (GtkWidget *widget) +{ + GtkWidget *parent = gtk_widget_get_parent (widget); + GtkWidget *child; + int idx; + + idx = 0; + for (child = gtk_widget_get_first_child (parent); + child; + child = gtk_widget_get_next_sibling (child)) + { + if (!gtk_accessible_should_present (GTK_ACCESSIBLE (child))) + continue; + + if (child == widget) + return idx; + + idx++; + } + + return -1; +} + +static int +get_index_in_toplevels (GtkWidget *widget) +{ + GListModel *toplevels = gtk_window_get_toplevels (); + guint n_toplevels = g_list_model_get_n_items (toplevels); + GtkWidget *window; + int idx; + + idx = 0; + for (guint i = 0; i < n_toplevels; i++) + { + window = g_list_model_get_item (toplevels, i); + + g_object_unref (window); + + if (!gtk_widget_get_visible (window)) + continue; + + if (window == widget) + return idx; + + idx += 1; + } + + return -1; +} + +static void +handle_accessible_method (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *method_name, + GVariant *parameters, + GDBusMethodInvocation *invocation, + gpointer user_data) +{ + GtkAtSpiContext *self = user_data; + + if (g_strcmp0 (method_name, "GetRole") == 0) + { + guint atspi_role = gtk_atspi_role_for_context (GTK_AT_CONTEXT (self)); + + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(u)", atspi_role)); + } + else if (g_strcmp0 (method_name, "GetRoleName") == 0) + { + GtkAccessibleRole role = gtk_at_context_get_accessible_role (GTK_AT_CONTEXT (self)); + const char *name = gtk_accessible_role_to_name (role, NULL); + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", name)); + } + else if (g_strcmp0 (method_name, "GetLocalizedRoleName") == 0) + { + GtkAccessibleRole role = gtk_at_context_get_accessible_role (GTK_AT_CONTEXT (self)); + const char *name = gtk_accessible_role_to_name (role, GETTEXT_PACKAGE); + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", name)); + } + else if (g_strcmp0 (method_name, "GetState") == 0) + { + GVariantBuilder builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("(au)")); + + g_variant_builder_open (&builder, G_VARIANT_TYPE ("au")); + collect_states (self, &builder); + g_variant_builder_close (&builder); + + g_dbus_method_invocation_return_value (invocation, g_variant_builder_end (&builder)); + } + else if (g_strcmp0 (method_name, "GetAttributes") == 0) + { + GVariantBuilder builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("(a{ss})")); + + g_variant_builder_open (&builder, G_VARIANT_TYPE ("a{ss}")); + g_variant_builder_add (&builder, "{ss}", "toolkit", "GTK"); + + if (gtk_at_context_has_accessible_property (GTK_AT_CONTEXT (self), GTK_ACCESSIBLE_PROPERTY_PLACEHOLDER)) + { + GtkAccessibleValue *value; + + value = gtk_at_context_get_accessible_property (GTK_AT_CONTEXT (self), GTK_ACCESSIBLE_PROPERTY_PLACEHOLDER); + + g_variant_builder_add (&builder, "{ss}", + "placeholder-text", gtk_string_accessible_value_get (value)); + } + + g_variant_builder_close (&builder); + + g_dbus_method_invocation_return_value (invocation, g_variant_builder_end (&builder)); + } + else if (g_strcmp0 (method_name, "GetApplication") == 0) + { + g_dbus_method_invocation_return_value (invocation, + g_variant_new ("(@(so))", gtk_at_spi_root_to_ref (self->root))); + } + else if (g_strcmp0 (method_name, "GetChildAtIndex") == 0) + { + GtkWidget *child = NULL; + int idx, real_idx = 0; + + g_variant_get (parameters, "(i)", &idx); + + GtkAccessible *accessible = gtk_at_context_get_accessible (GTK_AT_CONTEXT (self)); + if (GTK_IS_WIDGET (accessible)) + { + GtkWidget *widget = GTK_WIDGET (accessible); + + real_idx = 0; + for (child = gtk_widget_get_first_child (widget); + child; + child = gtk_widget_get_next_sibling (child)) + { + if (!gtk_accessible_should_present (GTK_ACCESSIBLE (child))) + continue; + + if (real_idx == idx) + break; + + real_idx += 1; + } + } + else if (GTK_IS_STACK_PAGE (accessible)) + { + if (idx == 0) + { + child = gtk_stack_page_get_child (GTK_STACK_PAGE (accessible)); + if (!gtk_accessible_should_present (GTK_ACCESSIBLE (child))) + child = NULL; + } + } + + if (child == NULL) + { + g_dbus_method_invocation_return_error (invocation, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + "No child with index %d", idx); + return; + } + + GtkATContext *context = gtk_accessible_get_at_context (GTK_ACCESSIBLE (child)); + + const char *name = g_dbus_connection_get_unique_name (self->connection); + const char *path = gtk_at_spi_context_get_context_path (GTK_AT_SPI_CONTEXT (context)); + + g_dbus_method_invocation_return_value (invocation, g_variant_new ("((so))", name, path)); + } + else if (g_strcmp0 (method_name, "GetChildren") == 0) + { + GVariantBuilder builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("a(so)")); + + GtkAccessible *accessible = gtk_at_context_get_accessible (GTK_AT_CONTEXT (self)); + if (GTK_IS_WIDGET (accessible)) + { + GtkWidget *widget = GTK_WIDGET (accessible); + GtkWidget *child; + + for (child = gtk_widget_get_first_child (widget); + child; + child = gtk_widget_get_next_sibling (child)) + { + if (!gtk_accessible_should_present (GTK_ACCESSIBLE (child))) + continue; + + GtkATContext *context = gtk_accessible_get_at_context (GTK_ACCESSIBLE (child)); + + const char *name = g_dbus_connection_get_unique_name (self->connection); + const char *path = gtk_at_spi_context_get_context_path (GTK_AT_SPI_CONTEXT (context)); + + g_variant_builder_add (&builder, "(so)", name, path); + } + } + else if (GTK_IS_STACK_PAGE (accessible)) + { + GtkWidget *child = gtk_stack_page_get_child (GTK_STACK_PAGE (accessible)); + + if (gtk_accessible_should_present (GTK_ACCESSIBLE (child))) + { + GtkATContext *context = gtk_accessible_get_at_context (GTK_ACCESSIBLE (child)); + const char *name = g_dbus_connection_get_unique_name (self->connection); + const char *path = gtk_at_spi_context_get_context_path (GTK_AT_SPI_CONTEXT (context)); + g_variant_builder_add (&builder, "(so)", name, path); + } + } + + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(a(so))", &builder)); + } + else if (g_strcmp0 (method_name, "GetIndexInParent") == 0) + { + GtkAccessible *accessible = gtk_at_context_get_accessible (GTK_AT_CONTEXT (self)); + int idx; + + if (GTK_IS_ROOT (accessible)) + idx = get_index_in_toplevels (GTK_WIDGET (accessible)); + else if (GTK_IS_STACK_PAGE (accessible)) + idx = get_index_in_parent (gtk_stack_page_get_child (GTK_STACK_PAGE (accessible))); + else if (GTK_IS_STACK (gtk_widget_get_parent (GTK_WIDGET (accessible)))) + idx = 1; + else + idx = get_index_in_parent (GTK_WIDGET (accessible)); + + if (idx == -1) + g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "Not found"); + else + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(i)", idx)); + } + else if (g_strcmp0 (method_name, "GetRelationSet") == 0) + { + GVariantBuilder builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("a(ua(so))")); + collect_relations (self, &builder); + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(a(ua(so)))", &builder)); + } + else if (g_strcmp0 (method_name, "GetInterfaces") == 0) + { + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(@as)", self->interfaces)); + } + +} + +static GVariant * +handle_accessible_get_property (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *property_name, + GError **error, + gpointer user_data) +{ + GtkAtSpiContext *self = user_data; + GVariant *res = NULL; + + GtkAccessible *accessible = gtk_at_context_get_accessible (GTK_AT_CONTEXT (self)); + + if (g_strcmp0 (property_name, "Name") == 0) + { + if (GTK_IS_WIDGET (accessible)) + res = g_variant_new_string (gtk_widget_get_name (GTK_WIDGET (accessible))); + else if (GTK_IS_STACK_PAGE (accessible)) + { + const char *name = gtk_stack_page_get_name (GTK_STACK_PAGE (accessible)); + if (name == NULL) + name = G_OBJECT_TYPE_NAME (accessible); + res = g_variant_new_string (name); + } + else + res = g_variant_new_string (G_OBJECT_TYPE_NAME (accessible)); + } + else if (g_strcmp0 (property_name, "Description") == 0) + { + char *label = gtk_at_context_get_label (GTK_AT_CONTEXT (self)); + res = g_variant_new_string (label); + g_free (label); + } + else if (g_strcmp0 (property_name, "Locale") == 0) + res = g_variant_new_string (setlocale (LC_MESSAGES, NULL)); + else if (g_strcmp0 (property_name, "AccessibleId") == 0) + res = g_variant_new_string (""); + else if (g_strcmp0 (property_name, "Parent") == 0) + { + if (GTK_IS_WIDGET (accessible)) + { + GtkWidget *parent = gtk_widget_get_parent (GTK_WIDGET (accessible)); + + if (parent == NULL) + { + res = gtk_at_spi_root_to_ref (self->root); + } + else if (GTK_IS_STACK (parent)) + { + GtkStackPage *page = + gtk_stack_get_page (GTK_STACK (parent), GTK_WIDGET (accessible)); + GtkATContext *parent_context = + gtk_accessible_get_at_context (GTK_ACCESSIBLE (page)); + + if (parent_context != NULL) + res = g_variant_new ("(so)", + g_dbus_connection_get_unique_name (self->connection), + GTK_AT_SPI_CONTEXT (parent_context)->context_path); + } + else + { + GtkATContext *parent_context = + gtk_accessible_get_at_context (GTK_ACCESSIBLE (parent)); + + if (parent_context != NULL) + res = g_variant_new ("(so)", + g_dbus_connection_get_unique_name (self->connection), + GTK_AT_SPI_CONTEXT (parent_context)->context_path); + } + } + else if (GTK_IS_STACK_PAGE (accessible)) + { + GtkWidget *parent = gtk_widget_get_parent (gtk_stack_page_get_child (GTK_STACK_PAGE (accessible))); + GtkATContext *parent_context = + gtk_accessible_get_at_context (GTK_ACCESSIBLE (parent)); + + if (parent_context != NULL) + res = g_variant_new ("(so)", + g_dbus_connection_get_unique_name (self->connection), + GTK_AT_SPI_CONTEXT (parent_context)->context_path); + } + + if (res == NULL) + res = gtk_at_spi_null_ref (); + } + else if (g_strcmp0 (property_name, "ChildCount") == 0) + { + int n_children = 0; + + if (GTK_IS_WIDGET (accessible)) + { + GtkWidget *child; + + for (child = gtk_widget_get_first_child (GTK_WIDGET (accessible)); + child; + child = gtk_widget_get_next_sibling (child)) + { + if (!gtk_accessible_should_present (GTK_ACCESSIBLE (child))) + continue; + + n_children++; + } + } + else if (GTK_IS_STACK_PAGE (accessible)) + { + n_children = 1; + } + + res = g_variant_new_int32 (n_children); + } + else + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + "Unknown property '%s'", property_name); + + return res; +} + +static const GDBusInterfaceVTable accessible_vtable = { + handle_accessible_method, + handle_accessible_get_property, + NULL, +}; + +static void +gtk_at_spi_context_register_object (GtkAtSpiContext *self) +{ + GtkAccessible *accessible = gtk_at_context_get_accessible (GTK_AT_CONTEXT (self)); + GVariantBuilder interfaces = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_STRING_ARRAY); + const GDBusInterfaceVTable *vtable; + + g_variant_builder_add (&interfaces, "s", atspi_accessible_interface.name); + self->registration_ids[self->n_registered_objects] = + g_dbus_connection_register_object (self->connection, + self->context_path, + (GDBusInterfaceInfo *) &atspi_accessible_interface, + &accessible_vtable, + self, + NULL, + NULL); + self->n_registered_objects++; + + vtable = gtk_atspi_get_text_vtable (accessible); + if (vtable) + { + g_variant_builder_add (&interfaces, "s", atspi_text_interface.name); + self->registration_ids[self->n_registered_objects] = + g_dbus_connection_register_object (self->connection, + self->context_path, + (GDBusInterfaceInfo *) &atspi_text_interface, + vtable, + self, + NULL, + NULL); + self->n_registered_objects++; + } + + vtable = gtk_atspi_get_editable_text_vtable (accessible); + if (vtable) + { + g_variant_builder_add (&interfaces, "s", atspi_editable_text_interface.name); + self->registration_ids[self->n_registered_objects] = + g_dbus_connection_register_object (self->connection, + self->context_path, + (GDBusInterfaceInfo *) &atspi_editable_text_interface, + vtable, + self, + NULL, + NULL); + self->n_registered_objects++; + } + vtable = gtk_atspi_get_value_vtable (accessible); + if (vtable) + { + g_variant_builder_add (&interfaces, "s", atspi_value_interface.name); + self->registration_ids[self->n_registered_objects] = + g_dbus_connection_register_object (self->connection, + self->context_path, + (GDBusInterfaceInfo *) &atspi_value_interface, + vtable, + self, + NULL, + NULL); + self->n_registered_objects++; + } + + /* Calling gtk_accessible_get_accessible_role() in here will recurse, + * so pass the role in explicitly. + */ + vtable = gtk_atspi_get_selection_vtable (accessible, + GTK_AT_CONTEXT (self)->accessible_role); + if (vtable) + { + g_variant_builder_add (&interfaces, "s", atspi_selection_interface.name); + self->registration_ids[self->n_registered_objects] = + g_dbus_connection_register_object (self->connection, + self->context_path, + (GDBusInterfaceInfo *) &atspi_selection_interface, + vtable, + self, + NULL, + NULL); + self->n_registered_objects++; + } + + self->interfaces = g_variant_ref_sink (g_variant_builder_end (&interfaces)); +} + +static void +gtk_at_spi_context_unregister_object (GtkAtSpiContext *self) +{ + while (self->n_registered_objects > 0) + { + self->n_registered_objects--; + g_dbus_connection_unregister_object (self->connection, + self->registration_ids[self->n_registered_objects]); + self->registration_ids[self->n_registered_objects] = 0; + } +} + +static void +emit_text_changed (GtkAtSpiContext *self, + const char *kind, + int start, + int end, + const char *text) +{ + g_dbus_connection_emit_signal (self->connection, + NULL, + self->context_path, + "org.a11y.atspi.Event.Object", + "TextChanged", + g_variant_new ("(siiva{sv})", + kind, start, end, g_variant_new_string (text), NULL), + NULL); +} + +static void +emit_text_selection_changed (GtkAtSpiContext *self, + const char *kind, + int cursor_position) +{ + g_dbus_connection_emit_signal (self->connection, + NULL, + self->context_path, + "org.a11y.atspi.Event.Object", + "TextChanged", + g_variant_new ("(siiva{sv})", + kind, cursor_position, 0, g_variant_new_string (""), NULL), + NULL); +} + +static void +emit_selection_changed (GtkAtSpiContext *self, + const char *kind) +{ + g_dbus_connection_emit_signal (self->connection, + NULL, + self->context_path, + "org.a11y.atspi.Event.Object", + "SelectionChanged", + g_variant_new ("(siiva{sv})", + "", 0, 0, g_variant_new_string (""), NULL), + NULL); +} + +static void +emit_state_changed (GtkAtSpiContext *self, + const char *name, + gboolean enabled) +{ + g_dbus_connection_emit_signal (self->connection, + NULL, + self->context_path, + "org.a11y.atspi.Event.Object", + "StateChanged", + g_variant_new ("(siiva{sv})", + name, enabled, 0, g_variant_new_string ("0"), NULL), + NULL); +} + +static void +emit_property_changed (GtkAtSpiContext *self, + const char *name, + GVariant *value) +{ + g_dbus_connection_emit_signal (self->connection, + NULL, + self->context_path, + "org.a11y.atspi.Event.Object", + "PropertyChange", + g_variant_new ("(siiva{sv})", + name, 0, 0, value, NULL), + NULL); +} + +static void +gtk_at_spi_context_state_change (GtkATContext *ctx, + GtkAccessibleStateChange changed_states, + GtkAccessiblePropertyChange changed_properties, + GtkAccessibleRelationChange changed_relations, + GtkAccessiblePlatformChange changed_platform, + GtkAccessibleAttributeSet *states, + GtkAccessibleAttributeSet *properties, + GtkAccessibleAttributeSet *relations) +{ + GtkAtSpiContext *self = GTK_AT_SPI_CONTEXT (ctx); + GtkWidget *widget = GTK_WIDGET (gtk_at_context_get_accessible (ctx)); + GtkAccessibleValue *value; + + if (!gtk_widget_get_realized (widget)) + return; + + if (changed_states & GTK_ACCESSIBLE_STATE_CHANGE_BUSY) + { + value = gtk_accessible_attribute_set_get_value (states, GTK_ACCESSIBLE_STATE_BUSY); + emit_state_changed (self, "busy", gtk_boolean_accessible_value_get (value)); + } + + if (changed_states & GTK_ACCESSIBLE_STATE_CHANGE_CHECKED) + { + value = gtk_accessible_attribute_set_get_value (states, GTK_ACCESSIBLE_STATE_CHECKED); + + switch (gtk_tristate_accessible_value_get (value)) + { + case GTK_ACCESSIBLE_TRISTATE_TRUE: + emit_state_changed (self, "checked", TRUE); + emit_state_changed (self, "indeterminate", FALSE); + break; + case GTK_ACCESSIBLE_TRISTATE_MIXED: + emit_state_changed (self, "checked", FALSE); + emit_state_changed (self, "indeterminate", TRUE); + break; + case GTK_ACCESSIBLE_TRISTATE_FALSE: + emit_state_changed (self, "checked", FALSE); + emit_state_changed (self, "indeterminate", FALSE); + default: + break; + } + } + + if (changed_states & GTK_ACCESSIBLE_STATE_CHANGE_DISABLED) + { + value = gtk_accessible_attribute_set_get_value (states, GTK_ACCESSIBLE_STATE_DISABLED); + emit_state_changed (self, "sensitive", !gtk_boolean_accessible_value_get (value)); + } + + if (changed_states & GTK_ACCESSIBLE_STATE_CHANGE_EXPANDED) + { + value = gtk_accessible_attribute_set_get_value (states, GTK_ACCESSIBLE_STATE_EXPANDED); + if (value->value_class->type == GTK_ACCESSIBLE_VALUE_TYPE_BOOLEAN) + { + emit_state_changed (self, "expandable", TRUE); + emit_state_changed (self, "expanded",gtk_boolean_accessible_value_get (value)); + } + else + emit_state_changed (self, "expandable", FALSE); + } + + if (changed_states & GTK_ACCESSIBLE_STATE_CHANGE_INVALID) + { + value = gtk_accessible_attribute_set_get_value (states, GTK_ACCESSIBLE_STATE_INVALID); + switch (gtk_invalid_accessible_value_get (value)) + { + case GTK_ACCESSIBLE_INVALID_TRUE: + case GTK_ACCESSIBLE_INVALID_GRAMMAR: + case GTK_ACCESSIBLE_INVALID_SPELLING: + emit_state_changed (self, "invalid", TRUE); + break; + case GTK_ACCESSIBLE_INVALID_FALSE: + emit_state_changed (self, "invalid", FALSE); + default: + break; + } + } + + if (changed_states & GTK_ACCESSIBLE_STATE_CHANGE_PRESSED) + { + value = gtk_accessible_attribute_set_get_value (states, GTK_ACCESSIBLE_STATE_PRESSED); + switch (gtk_tristate_accessible_value_get (value)) + { + case GTK_ACCESSIBLE_TRISTATE_TRUE: + emit_state_changed (self, "pressed", TRUE); + emit_state_changed (self, "indeterminate", FALSE); + break; + case GTK_ACCESSIBLE_TRISTATE_MIXED: + emit_state_changed (self, "pressed", FALSE); + emit_state_changed (self, "indeterminate", TRUE); + break; + case GTK_ACCESSIBLE_TRISTATE_FALSE: + emit_state_changed (self, "pressed", FALSE); + emit_state_changed (self, "indeterminate", FALSE); + default: + break; + } + } + + if (changed_states & GTK_ACCESSIBLE_STATE_CHANGE_SELECTED) + { + value = gtk_accessible_attribute_set_get_value (states, GTK_ACCESSIBLE_STATE_SELECTED); + if (value->value_class->type == GTK_ACCESSIBLE_VALUE_TYPE_BOOLEAN) + { + emit_state_changed (self, "selectable", TRUE); + emit_state_changed (self, "selected",gtk_boolean_accessible_value_get (value)); + } + else + emit_state_changed (self, "selectable", FALSE); + } + + if (changed_properties & GTK_ACCESSIBLE_PROPERTY_CHANGE_READ_ONLY) + { + gboolean readonly; + + value = gtk_accessible_attribute_set_get_value (properties, GTK_ACCESSIBLE_PROPERTY_READ_ONLY); + readonly = gtk_boolean_accessible_value_get (value); + + emit_state_changed (self, "read-only", readonly); + if (ctx->accessible_role == GTK_ACCESSIBLE_ROLE_TEXT_BOX) + emit_state_changed (self, "editable", !readonly); + } + + if (changed_properties & GTK_ACCESSIBLE_PROPERTY_CHANGE_ORIENTATION) + { + value = gtk_accessible_attribute_set_get_value (properties, GTK_ACCESSIBLE_PROPERTY_ORIENTATION); + if (gtk_orientation_accessible_value_get (value) == GTK_ORIENTATION_HORIZONTAL) + { + emit_state_changed (self, "horizontal", TRUE); + emit_state_changed (self, "vertical", FALSE); + } + else + { + emit_state_changed (self, "horizontal", FALSE); + emit_state_changed (self, "vertical", TRUE); + } + } + + if (changed_properties & GTK_ACCESSIBLE_PROPERTY_CHANGE_MODAL) + { + value = gtk_accessible_attribute_set_get_value (properties, GTK_ACCESSIBLE_PROPERTY_MODAL); + emit_state_changed (self, "modal", gtk_boolean_accessible_value_get (value)); + } + + if (changed_properties & GTK_ACCESSIBLE_PROPERTY_CHANGE_MULTI_LINE) + { + value = gtk_accessible_attribute_set_get_value (properties, GTK_ACCESSIBLE_PROPERTY_MULTI_LINE); + emit_state_changed (self, "multi-line", gtk_boolean_accessible_value_get (value)); + } + + if (changed_properties & GTK_ACCESSIBLE_PROPERTY_CHANGE_LABEL) + { + char *label = gtk_at_context_get_label (GTK_AT_CONTEXT (self)); + GVariant *v = g_variant_new_take_string (label); + emit_property_changed (self, "accessible-description", v); + } + + if (changed_platform & GTK_ACCESSIBLE_PLATFORM_CHANGE_FOCUSABLE) + { + gboolean state = gtk_accessible_get_platform_state (GTK_ACCESSIBLE (widget), + GTK_ACCESSIBLE_PLATFORM_STATE_FOCUSABLE); + emit_state_changed (self, "focusable", state); + } + + if (changed_platform & GTK_ACCESSIBLE_PLATFORM_CHANGE_FOCUSED) + { + gboolean state = gtk_accessible_get_platform_state (GTK_ACCESSIBLE (widget), + GTK_ACCESSIBLE_PLATFORM_STATE_FOCUSED); + emit_state_changed (self, "focused", state); + } +} + +static void +gtk_at_spi_context_dispose (GObject *gobject) +{ + GtkAtSpiContext *self = GTK_AT_SPI_CONTEXT (gobject); + GtkAccessible *accessible = gtk_at_context_get_accessible (GTK_AT_CONTEXT (self)); + + gtk_at_spi_context_unregister_object (self); + gtk_atspi_disconnect_text_signals (accessible); + gtk_atspi_disconnect_selection_signals (accessible); + + G_OBJECT_CLASS (gtk_at_spi_context_parent_class)->dispose (gobject); +} + +static void +gtk_at_spi_context_finalize (GObject *gobject) +{ + GtkAtSpiContext *self = GTK_AT_SPI_CONTEXT (gobject); + + g_free (self->bus_address); + g_free (self->context_path); + g_clear_pointer (&self->interfaces, g_variant_unref); + + G_OBJECT_CLASS (gtk_at_spi_context_parent_class)->finalize (gobject); +} + +static void +gtk_at_spi_context_set_property (GObject *gobject, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkAtSpiContext *self = GTK_AT_SPI_CONTEXT (gobject); + + switch (prop_id) + { + case PROP_BUS_ADDRESS: + self->bus_address = g_value_dup_string (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + } +} + +static void +gtk_at_spi_context_get_property (GObject *gobject, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GtkAtSpiContext *self = GTK_AT_SPI_CONTEXT (gobject); + + switch (prop_id) + { + case PROP_BUS_ADDRESS: + g_value_set_string (value, self->bus_address); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + } +} + +static void +gtk_at_spi_context_constructed (GObject *gobject) +{ + GtkAtSpiContext *self = GTK_AT_SPI_CONTEXT (gobject); + GdkDisplay *display; + + g_assert (self->bus_address); + + /* Every GTK application has a single root AT-SPI object, which + * handles all the global state, including the cache of accessible + * objects. We use the GdkDisplay to store it, so it's guaranteed + * to be unique per-display connection + */ + display = gtk_at_context_get_display (GTK_AT_CONTEXT (self)); + self->root = + g_object_get_data (G_OBJECT (display), "-gtk-atspi-root"); + + if (self->root == NULL) + { + self->root = gtk_at_spi_root_new (self->bus_address); + g_object_set_data_full (G_OBJECT (display), "-gtk-atspi-root", + self->root, + g_object_unref); + } + + self->connection = gtk_at_spi_root_get_connection (self->root); + + /* We use the application's object path to build the path of each + * accessible object exposed on the accessibility bus; the path is + * also used to access the object cache + */ + GApplication *application = g_application_get_default (); + char *base_path = NULL; + + if (application != NULL) + { + const char *app_path = g_application_get_dbus_object_path (application); + base_path = g_strconcat (app_path, "/a11y", NULL); + } + else + { + char *uuid = g_uuid_string_random (); + base_path = g_strconcat ("/org/gtk/application/", uuid, "/a11y", NULL); + g_free (uuid); + } + + /* We use a unique id to ensure that we don't have conflicting + * objects on the bus + */ + char *uuid = g_uuid_string_random (); + + self->context_path = g_strconcat (base_path, "/", uuid, NULL); + + /* UUIDs use '-' as the separator, but that's not a valid character + * for a DBus object path + */ + size_t path_len = strlen (self->context_path); + for (size_t i = 0; i < path_len; i++) + { + if (self->context_path[i] == '-') + self->context_path[i] = '_'; + } + + g_free (base_path); + g_free (uuid); + + GtkAccessible *accessible = gtk_at_context_get_accessible (GTK_AT_CONTEXT (self)); + gtk_atspi_connect_text_signals (accessible, + (GtkAtspiTextChangedCallback *)emit_text_changed, + (GtkAtspiTextSelectionCallback *)emit_text_selection_changed, + self); + gtk_atspi_connect_selection_signals (accessible, + (GtkAtspiSelectionCallback *)emit_selection_changed, + self); + gtk_at_spi_context_register_object (self); + + G_OBJECT_CLASS (gtk_at_spi_context_parent_class)->constructed (gobject); +} + +static void +gtk_at_spi_context_class_init (GtkAtSpiContextClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GtkATContextClass *context_class = GTK_AT_CONTEXT_CLASS (klass); + + gobject_class->constructed = gtk_at_spi_context_constructed; + gobject_class->set_property = gtk_at_spi_context_set_property; + gobject_class->get_property = gtk_at_spi_context_get_property; + gobject_class->finalize = gtk_at_spi_context_finalize; + gobject_class->dispose = gtk_at_spi_context_dispose; + + context_class->state_change = gtk_at_spi_context_state_change; + + obj_props[PROP_BUS_ADDRESS] = + g_param_spec_string ("bus-address", NULL, NULL, + NULL, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (gobject_class, N_PROPS, obj_props); +} + +static void +gtk_at_spi_context_init (GtkAtSpiContext *self) +{ +} + +#ifdef GDK_WINDOWING_X11 +static char * +get_bus_address_x11 (GdkDisplay *display) +{ + GTK_NOTE (A11Y, g_message ("Acquiring a11y bus via X11...")); + + Display *xdisplay = gdk_x11_display_get_xdisplay (display); + Atom type_return; + int format_return; + gulong nitems_return; + gulong bytes_after_return; + guchar *data = NULL; + char *address = NULL; + + gdk_x11_display_error_trap_push (display); + XGetWindowProperty (xdisplay, DefaultRootWindow (xdisplay), + gdk_x11_get_xatom_by_name_for_display (display, "AT_SPI_BUS"), + 0L, BUFSIZ, False, + (Atom) 31, + &type_return, &format_return, &nitems_return, + &bytes_after_return, &data); + gdk_x11_display_error_trap_pop_ignored (display); + + address = g_strdup ((char *) data); + + XFree (data); + + return address; +} +#endif + +#if defined(GDK_WINDOWING_WAYLAND) || defined(GDK_WINDOWING_X11) +static char * +get_bus_address_dbus (GdkDisplay *display) +{ + GTK_NOTE (A11Y, g_message ("Acquiring a11y bus via DBus...")); + + GError *error = NULL; + GDBusConnection *connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); + + if (error != NULL) + { + g_critical ("Unable to acquire session bus: %s", error->message); + g_error_free (error); + return NULL; + } + + GVariant *res = + g_dbus_connection_call_sync (connection, "org.a11y.Bus", + "/org/a11y/bus", + "org.a11y.Bus", + "GetAddress", + NULL, NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error); + if (error != NULL) + { + g_critical ("Unable to acquire the address of the accessibility bus: %s", + error->message); + g_error_free (error); + } + + char *address = NULL; + if (res != NULL) + { + g_variant_get (res, "(s)", &address); + g_variant_unref (res); + } + + g_object_unref (connection); + + return address; +} +#endif + +static const char * +get_bus_address (GdkDisplay *display) +{ + const char *bus_address; + + bus_address = g_object_get_data (G_OBJECT (display), "-gtk-atspi-bus-address"); + if (bus_address != NULL) + return bus_address; + + /* The bus address environment variable takes precedence; this is the + * mechanism used by Flatpak to handle the accessibility bus portal + * between the sandbox and the outside world + */ + bus_address = g_getenv ("AT_SPI_BUS_ADDRESS"); + if (bus_address != NULL && *bus_address != '\0') + { + GTK_NOTE (A11Y, g_message ("Using ATSPI bus address from environment: %s", bus_address)); + g_object_set_data_full (G_OBJECT (display), "-gtk-atspi-bus-address", + g_strdup (bus_address), + g_free); + goto out; + } + +#if defined(GDK_WINDOWING_WAYLAND) + if (bus_address == NULL) + { + if (GDK_IS_WAYLAND_DISPLAY (display)) + { + char *addr = get_bus_address_dbus (display); + + GTK_NOTE (A11Y, g_message ("Using ATSPI bus address from D-Bus: %s", addr)); + g_object_set_data_full (G_OBJECT (display), "-gtk-atspi-bus-address", + addr, + g_free); + + bus_address = addr; + } + } +#endif +#if defined(GDK_WINDOWING_X11) + if (bus_address == NULL) + { + if (GDK_IS_X11_DISPLAY (display)) + { + char *addr = get_bus_address_dbus (display); + + if (addr == NULL) + { + addr = get_bus_address_x11 (display); + GTK_NOTE (A11Y, g_message ("Using ATSPI bus address from X11: %s", addr)); + } + else + { + GTK_NOTE (A11Y, g_message ("Using ATSPI bus address from D-Bus: %s", addr)); + } + + g_object_set_data_full (G_OBJECT (display), "-gtk-atspi-bus-address", + addr, + g_free); + + bus_address = addr; + } + } +#endif + +out: + return bus_address; +} + +GtkATContext * +gtk_at_spi_create_context (GtkAccessibleRole accessible_role, + GtkAccessible *accessible, + GdkDisplay *display) +{ + g_return_val_if_fail (GTK_IS_ACCESSIBLE (accessible), NULL); + g_return_val_if_fail (GDK_IS_DISPLAY (display), NULL); + + const char *bus_address = get_bus_address (display); + + if (bus_address == NULL) + return NULL; + +#if defined(GDK_WINDOWING_WAYLAND) + if (GDK_IS_WAYLAND_DISPLAY (display)) + return g_object_new (GTK_TYPE_AT_SPI_CONTEXT, + "accessible-role", accessible_role, + "accessible", accessible, + "display", display, + "bus-address", bus_address, + NULL); +#endif +#if defined(GDK_WINDOWING_X11) + if (GDK_IS_X11_DISPLAY (display)) + return g_object_new (GTK_TYPE_AT_SPI_CONTEXT, + "accessible-role", accessible_role, + "accessible", accessible, + "display", display, + "bus-address", bus_address, + NULL); +#endif + + return NULL; +} + +const char * +gtk_at_spi_context_get_context_path (GtkAtSpiContext *self) +{ + g_return_val_if_fail (GTK_IS_AT_SPI_CONTEXT (self), NULL); + + return self->context_path; +} + +/*< private > + * gtk_at_spi_context_to_ref: + * @self: a #GtkAtSpiContext + * + * Returns an ATSPI object reference for the #GtkAtSpiContext. + * + * Returns: (transfer floating): a #GVariant with the reference + */ +GVariant * +gtk_at_spi_context_to_ref (GtkAtSpiContext *self) +{ + const char *name = g_dbus_connection_get_unique_name (self->connection); + return g_variant_new ("(so)", name, self->context_path); +} diff --git a/gtk/a11y/gtkatspicontextprivate.h b/gtk/a11y/gtkatspicontextprivate.h new file mode 100644 index 0000000000..b0cd407579 --- /dev/null +++ b/gtk/a11y/gtkatspicontextprivate.h @@ -0,0 +1,42 @@ +/* gtkatspicontextprivate.h: AT-SPI GtkATContext implementation + * + * Copyright 2020 GNOME Foundation + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "gtkatcontextprivate.h" + +G_BEGIN_DECLS + +#define GTK_TYPE_AT_SPI_CONTEXT (gtk_at_spi_context_get_type()) + +G_DECLARE_FINAL_TYPE (GtkAtSpiContext, gtk_at_spi_context, GTK, AT_SPI_CONTEXT, GtkATContext) + +GtkATContext * +gtk_at_spi_create_context (GtkAccessibleRole accessible_role, + GtkAccessible *accessible, + GdkDisplay *display); + +const char * +gtk_at_spi_context_get_context_path (GtkAtSpiContext *self); + +GVariant * +gtk_at_spi_context_to_ref (GtkAtSpiContext *self); + +G_END_DECLS diff --git a/gtk/a11y/gtkatspieditabletext.c b/gtk/a11y/gtkatspieditabletext.c new file mode 100644 index 0000000000..a904ef916f --- /dev/null +++ b/gtk/a11y/gtkatspieditabletext.c @@ -0,0 +1,356 @@ +/* gtkatspieditabletext.c: EditableText interface for GtkAtspiContext + * + * Copyright 2020 Red Hat, Inc + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include "gtkatspieditabletextprivate.h" +#include "gtkatcontextprivate.h" + +#include "a11y/atspi/atspi-editabletext.h" + +#include "gtkeditable.h" +#include "gtkentry.h" +#include "gtksearchentry.h" +#include "gtkpasswordentry.h" +#include "gtkspinbutton.h" +#include "gtktextview.h" + +#include <gio/gio.h> + +typedef struct +{ + GtkWidget *widget; + int position; +} PasteData; + +static void +text_received (GObject *source, + GAsyncResult *result, + gpointer data) +{ + GdkClipboard *clipboard = GDK_CLIPBOARD (source); + PasteData *pdata = data; + char *text; + + text = gdk_clipboard_read_text_finish (clipboard, result, NULL); + if (text) + gtk_editable_insert_text (GTK_EDITABLE (pdata->widget), text, -1, &pdata->position); + g_free (text); + g_free (pdata); +} + +static void +entry_handle_method (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *method_name, + GVariant *parameters, + GDBusMethodInvocation *invocation, + gpointer user_data) +{ + GtkATContext *self = user_data; + GtkAccessible *accessible = gtk_at_context_get_accessible (self); + GtkWidget *widget = GTK_WIDGET (accessible); + + if (g_strcmp0 (method_name, "SetTextContents") == 0) + { + char *text; + gboolean ret = FALSE; + + g_variant_get (parameters, "(&s)", &text); + + if (gtk_editable_get_editable (GTK_EDITABLE (widget))) + { + gtk_editable_set_text (GTK_EDITABLE (widget), text); + ret = TRUE; + } + + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret)); + } + else if (g_strcmp0 (method_name, "InsertText") == 0) + { + int position; + char *text; + int len; + gboolean ret = FALSE; + + g_variant_get (parameters, "(i&si)", &position, &text, &len); + + if (gtk_editable_get_editable (GTK_EDITABLE (widget))) + { + gtk_editable_insert_text (GTK_EDITABLE (widget), text, -1, &position); + ret = TRUE; + } + + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret)); + } + else if (g_strcmp0 (method_name, "CopyText") == 0) + { + int start, end; + char *str; + + g_variant_get (parameters, "(ii)", &start, &end); + + str = gtk_editable_get_chars (GTK_EDITABLE (widget), start, end); + gdk_clipboard_set_text (gtk_widget_get_clipboard (widget), str); + g_free (str); + g_dbus_method_invocation_return_value (invocation, NULL); + } + else if (g_strcmp0 (method_name, "CutText") == 0) + { + int start, end; + gboolean ret = FALSE; + + g_variant_get (parameters, "(ii)", &start, &end); + + if (gtk_editable_get_editable (GTK_EDITABLE (widget))) + { + char *str; + + str = gtk_editable_get_chars (GTK_EDITABLE (widget), start, end); + gdk_clipboard_set_text (gtk_widget_get_clipboard (widget), str); + g_free (str); + gtk_editable_delete_text (GTK_EDITABLE (widget), start, end); + ret = TRUE; + } + + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret)); + } + else if (g_strcmp0 (method_name, "DeleteText") == 0) + { + int start, end; + gboolean ret = FALSE; + + g_variant_get (parameters, "(ii)", &start, &end); + + if (gtk_editable_get_editable (GTK_EDITABLE (widget))) + { + gtk_editable_delete_text (GTK_EDITABLE (widget), start, end); + ret = TRUE; + } + + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret)); + } + else if (g_strcmp0 (method_name, "PasteText") == 0) + { + int position; + gboolean ret = FALSE; + + g_variant_get (parameters, "(i)", &position); + + if (gtk_editable_get_editable (GTK_EDITABLE (widget))) + { + PasteData *data; + + data = g_new (PasteData, 1); + data->widget = widget; + data->position = position; + + gdk_clipboard_read_text_async (gtk_widget_get_clipboard (widget), NULL, text_received, data); + + ret = TRUE; + } + + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret)); + } +} + +static const GDBusInterfaceVTable entry_vtable = { + entry_handle_method, + NULL, +}; + + +static void +text_view_received (GObject *source, + GAsyncResult *result, + gpointer data) +{ + GdkClipboard *clipboard = GDK_CLIPBOARD (source); + PasteData *pdata = data; + GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (pdata->widget)); + GtkTextIter iter; + char *text; + + text = gdk_clipboard_read_text_finish (clipboard, result, NULL); + if (text) + { + gtk_text_buffer_get_iter_at_offset (buffer, &iter, pdata->position); + gtk_text_buffer_insert (buffer, &iter, text, -1); + } + + g_free (text); + g_free (pdata); +} + +static void +text_view_handle_method (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *method_name, + GVariant *parameters, + GDBusMethodInvocation *invocation, + gpointer user_data) +{ + GtkATContext *self = user_data; + GtkAccessible *accessible = gtk_at_context_get_accessible (self); + GtkWidget *widget = GTK_WIDGET (accessible); + + if (g_strcmp0 (method_name, "SetTextContents") == 0) + { + GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget)); + char *text; + gboolean ret = FALSE; + + g_variant_get (parameters, "(&s)", &text); + + if (gtk_text_view_get_editable (GTK_TEXT_VIEW (widget))) + { + gtk_text_buffer_set_text (buffer, text, -1); + ret = TRUE; + } + + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret)); + } + else if (g_strcmp0 (method_name, "InsertText") == 0) + { + GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget)); + GtkTextIter iter; + int position; + char *text; + int len; + gboolean ret = FALSE; + + g_variant_get (parameters, "(i&si)", &position, &text, &len); + + if (gtk_text_view_get_editable (GTK_TEXT_VIEW (widget))) + { + gtk_text_buffer_get_iter_at_offset (buffer, &iter, position); + gtk_text_buffer_insert (buffer, &iter, text, len); + ret = TRUE; + } + + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret)); + } + else if (g_strcmp0 (method_name, "CopyText") == 0) + { + GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget)); + GtkTextIter start_iter, end_iter; + int start, end; + char *str; + + g_variant_get (parameters, "(ii)", &start, &end); + + gtk_text_buffer_get_iter_at_offset (buffer, &start_iter, start); + gtk_text_buffer_get_iter_at_offset (buffer, &end_iter, end); + str = gtk_text_buffer_get_text (buffer, &start_iter, &end_iter, FALSE); + gdk_clipboard_set_text (gtk_widget_get_clipboard (widget), str); + g_free (str); + g_dbus_method_invocation_return_value (invocation, NULL); + } + else if (g_strcmp0 (method_name, "CutText") == 0) + { + GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget)); + GtkTextIter start_iter, end_iter; + int start, end; + gboolean ret = FALSE; + + g_variant_get (parameters, "(ii)", &start, &end); + + if (gtk_text_view_get_editable (GTK_TEXT_VIEW (widget))) + { + char *str; + + gtk_text_buffer_get_iter_at_offset (buffer, &start_iter, start); + gtk_text_buffer_get_iter_at_offset (buffer, &end_iter, end); + str = gtk_text_buffer_get_text (buffer, &start_iter, &end_iter, FALSE); + gdk_clipboard_set_text (gtk_widget_get_clipboard (widget), str); + g_free (str); + gtk_text_buffer_delete (buffer, &start_iter, &end_iter); + ret = TRUE; + } + + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret)); + } + else if (g_strcmp0 (method_name, "DeleteText") == 0) + { + GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget)); + GtkTextIter start_iter, end_iter; + int start, end; + gboolean ret = FALSE; + + g_variant_get (parameters, "(ii)", &start, &end); + + if (gtk_text_view_get_editable (GTK_TEXT_VIEW (widget))) + { + gtk_text_buffer_get_iter_at_offset (buffer, &start_iter, start); + gtk_text_buffer_get_iter_at_offset (buffer, &end_iter, end); + gtk_text_buffer_delete (buffer, &start_iter, &end_iter); + ret = TRUE; + } + + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret)); + } + else if (g_strcmp0 (method_name, "PasteText") == 0) + { + int position; + gboolean ret = FALSE; + + g_variant_get (parameters, "(i)", &position); + + if (gtk_text_view_get_editable (GTK_TEXT_VIEW (widget))) + { + PasteData *data; + + data = g_new (PasteData, 1); + data->widget = widget; + data->position = position; + + gdk_clipboard_read_text_async (gtk_widget_get_clipboard (widget), NULL, text_view_received, data); + + ret = TRUE; + } + + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret)); + } +} + +static const GDBusInterfaceVTable text_view_vtable = { + text_view_handle_method, + NULL, +}; + + +const GDBusInterfaceVTable * +gtk_atspi_get_editable_text_vtable (GtkAccessible *accessible) +{ + if (GTK_IS_ENTRY (accessible) || + GTK_IS_SEARCH_ENTRY (accessible) || + GTK_IS_PASSWORD_ENTRY (accessible) || + GTK_IS_SPIN_BUTTON (accessible)) + return &entry_vtable; + else if (GTK_IS_TEXT_VIEW (accessible)) + return &text_view_vtable; + + return NULL; +} + diff --git a/gtk/a11y/gtkatspieditabletextprivate.h b/gtk/a11y/gtkatspieditabletextprivate.h new file mode 100644 index 0000000000..d673384371 --- /dev/null +++ b/gtk/a11y/gtkatspieditabletextprivate.h @@ -0,0 +1,30 @@ +/* gtkatspieditabletextprivate.h: AT-SPI EditableText implementation + * + * Copyright 2020 Red Hat, Inc. + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <gio/gio.h> +#include "gtkaccessible.h" + +G_BEGIN_DECLS + +const GDBusInterfaceVTable *gtk_atspi_get_editable_text_vtable (GtkAccessible *accessible); + +G_END_DECLS diff --git a/gtk/a11y/gtkatspipango.c b/gtk/a11y/gtkatspipango.c new file mode 100644 index 0000000000..1171c8758b --- /dev/null +++ b/gtk/a11y/gtkatspipango.c @@ -0,0 +1,1257 @@ +/* gtkatspipango.c - pango-related utilities for AT-SPI + * + * Copyright (c) 2010 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>.Free + */ + +#include "config.h" +#include "gtkatspipangoprivate.h" + +const char * +pango_style_to_string (PangoStyle style) +{ + switch (style) + { + case PANGO_STYLE_NORMAL: + return "normal"; + case PANGO_STYLE_OBLIQUE: + return "oblique"; + case PANGO_STYLE_ITALIC: + return "italic"; + default: + g_assert_not_reached (); + } +} + +const char * +pango_variant_to_string (PangoVariant variant) +{ + switch (variant) + { + case PANGO_VARIANT_NORMAL: + return "normal"; + case PANGO_VARIANT_SMALL_CAPS: + return "small_caps"; + default: + g_assert_not_reached (); + } +} + +const char * +pango_stretch_to_string (PangoStretch stretch) +{ + switch (stretch) + { + case PANGO_STRETCH_ULTRA_CONDENSED: + return "ultra_condensed"; + case PANGO_STRETCH_EXTRA_CONDENSED: + return "extra_condensed"; + case PANGO_STRETCH_CONDENSED: + return "condensed"; + case PANGO_STRETCH_SEMI_CONDENSED: + return "semi_condensed"; + case PANGO_STRETCH_NORMAL: + return "normal"; + case PANGO_STRETCH_SEMI_EXPANDED: + return "semi_expanded"; + case PANGO_STRETCH_EXPANDED: + return "expanded"; + case PANGO_STRETCH_EXTRA_EXPANDED: + return "extra_expanded"; + case PANGO_STRETCH_ULTRA_EXPANDED: + return "ultra_expanded"; + default: + g_assert_not_reached (); + } +} + +const char * +pango_underline_to_string (PangoUnderline value) +{ + switch (value) + { + case PANGO_UNDERLINE_NONE: + return "none"; + case PANGO_UNDERLINE_SINGLE: + case PANGO_UNDERLINE_SINGLE_LINE: + return "single"; + case PANGO_UNDERLINE_DOUBLE: + case PANGO_UNDERLINE_DOUBLE_LINE: + return "double"; + case PANGO_UNDERLINE_LOW: + return "low"; + case PANGO_UNDERLINE_ERROR: + case PANGO_UNDERLINE_ERROR_LINE: + return "error"; + default: + g_assert_not_reached (); + } +} + +const char * +pango_wrap_mode_to_string (PangoWrapMode mode) +{ + switch (mode) + { + case PANGO_WRAP_WORD: + return "word"; + case PANGO_WRAP_CHAR: + return "char"; + case PANGO_WRAP_WORD_CHAR: + return "word-char"; + default: + g_assert_not_reached (); + } +} + +void +gtk_pango_get_font_attributes (PangoFontDescription *font, + GVariantBuilder *builder) +{ + char buf[60]; + + g_variant_builder_add (builder, "{ss}", "style", + pango_style_to_string (pango_font_description_get_style (font))); + g_variant_builder_add (builder, "{ss}", "variant", + pango_variant_to_string (pango_font_description_get_variant (font))); + g_variant_builder_add (builder, "{ss}", "stretch", + pango_stretch_to_string (pango_font_description_get_stretch (font))); + g_variant_builder_add (builder, "{ss}", "family-name", + pango_font_description_get_family (font)); + + g_snprintf (buf, 60, "%d", pango_font_description_get_weight (font)); + g_variant_builder_add (builder, "{ss}", "weight", buf); + g_snprintf (buf, 60, "%i", pango_font_description_get_size (font) / PANGO_SCALE); + g_variant_builder_add (builder, "{ss}", "size", buf); +} + +/* + * gtk_pango_get_default_attributes: + * @attributes: a #AtkAttributeSet to add the attributes to + * @layout: the #PangoLayout from which to get attributes + * + * Adds the default text attributes from @layout to @attributes, + * after translating them from Pango attributes to atspi attributes. + * + * This is a convenience function that can be used to implement + * support for the #AtkText interface in widgets using Pango + * layouts. + * + * Returns: the modified @attributes + */ +void +gtk_pango_get_default_attributes (PangoLayout *layout, + GVariantBuilder *builder) +{ + PangoContext *context; + const char *val; + PangoAlignment align; + PangoWrapMode mode; + + context = pango_layout_get_context (layout); + if (context) + { + PangoLanguage *language; + PangoFontDescription *font; + + language = pango_context_get_language (context); + if (language) + g_variant_builder_add (builder, "{ss}", "language", + pango_language_to_string (language)); + + font = pango_context_get_font_description (context); + if (font) + gtk_pango_get_font_attributes (font, builder); + } + if (pango_layout_get_justify (layout)) + { + val = "fill"; + } + else + { + align = pango_layout_get_alignment (layout); + if (align == PANGO_ALIGN_LEFT) + val = "left"; + else if (align == PANGO_ALIGN_CENTER) + val = "center"; + else + val = "right"; + } + g_variant_builder_add (builder, "{ss}", "justification", val); + + mode = pango_layout_get_wrap (layout); + g_variant_builder_add (builder, "{ss}", "wrap-mode", + pango_wrap_mode_to_string (mode)); + + g_variant_builder_add (builder, "{ss}", "strikethrough", "false"); + g_variant_builder_add (builder, "{ss}", "underline", "false"); + g_variant_builder_add (builder, "{ss}", "rise", "0"); + g_variant_builder_add (builder, "{ss}", "scale", "1"); + g_variant_builder_add (builder, "{ss}", "bg-full-height", "0"); + g_variant_builder_add (builder, "{ss}", "pixels-inside-wrap", "0"); + g_variant_builder_add (builder, "{ss}", "pixels-below-lines", "0"); + g_variant_builder_add (builder, "{ss}", "pixels-above-lines", "0"); + g_variant_builder_add (builder, "{ss}", "editable", "false"); + g_variant_builder_add (builder, "{ss}", "invisible", "false"); + g_variant_builder_add (builder, "{ss}", "indent", "0"); + g_variant_builder_add (builder, "{ss}", "right-margin", "0"); + g_variant_builder_add (builder, "{ss}", "left-margin", "0"); +} + +/* + * gtk_pango_get_run_attributes: + * @layout: the #PangoLayout to get the attributes from + * @builder: GVariantBuilder to add to + * @offset: the offset at which the attributes are wanted + * @start_offset: return location for the starting offset + * of the current run + * @end_offset: return location for the ending offset of the + * current run + * + * Finds the “run” around index (i.e. the maximal range of characters + * where the set of applicable attributes remains constant) and + * returns the starting and ending offsets for it. + * + * The attributes for the run are added to @attributes, after + * translating them from Pango attributes to atspi attributes. + * + * This is a convenience function that can be used to implement + * support for the #AtkText interface in widgets using Pango + * layouts. + */ +void +gtk_pango_get_run_attributes (PangoLayout *layout, + GVariantBuilder *builder, + int offset, + int *start_offset, + int *end_offset) +{ + PangoAttrIterator *iter; + PangoAttrList *attr; + PangoAttrString *pango_string; + PangoAttrInt *pango_int; + PangoAttrColor *pango_color; + PangoAttrLanguage *pango_lang; + PangoAttrFloat *pango_float; + int index, start_index, end_index; + gboolean is_next; + glong len; + const char *text; + char *value; + const char *val; + + text = pango_layout_get_text (layout); + len = g_utf8_strlen (text, -1); + + /* Grab the attributes of the PangoLayout, if any */ + attr = pango_layout_get_attributes (layout); + + if (attr == NULL) + { + *start_offset = 0; + *end_offset = len; + return; + } + + iter = pango_attr_list_get_iterator (attr); + /* Get invariant range offsets */ + /* If offset out of range, set offset in range */ + if (offset > len) + offset = len; + else if (offset < 0) + offset = 0; + + index = g_utf8_offset_to_pointer (text, offset) - text; + pango_attr_iterator_range (iter, &start_index, &end_index); + is_next = TRUE; + while (is_next) + { + if (index >= start_index && index < end_index) + { + *start_offset = g_utf8_pointer_to_offset (text, text + start_index); + if (end_index == G_MAXINT) /* Last iterator */ + end_index = len; + + *end_offset = g_utf8_pointer_to_offset (text, text + end_index); + break; + } + is_next = pango_attr_iterator_next (iter); + pango_attr_iterator_range (iter, &start_index, &end_index); + } + + /* Get attributes */ + pango_string = (PangoAttrString *) pango_attr_iterator_get (iter, PANGO_ATTR_FAMILY); + if (pango_string != NULL) + { + value = g_strdup_printf ("%s", pango_string->value); + g_variant_builder_add (builder, "{ss}", "family-name", value); + g_free (value); + } + + pango_int = (PangoAttrInt *) pango_attr_iterator_get (iter, PANGO_ATTR_STYLE); + if (pango_int != NULL) + g_variant_builder_add (builder, "{ss}", "style", pango_style_to_string (pango_int->value)); + + pango_int = (PangoAttrInt *) pango_attr_iterator_get (iter, PANGO_ATTR_WEIGHT); + if (pango_int != NULL) + { + value = g_strdup_printf ("%i", pango_int->value); + g_variant_builder_add (builder, "{ss}", "weight", value); + g_free (value); + } + + pango_int = (PangoAttrInt *) pango_attr_iterator_get (iter, PANGO_ATTR_VARIANT); + if (pango_int != NULL) + g_variant_builder_add (builder, "{ss}", "variant", + pango_variant_to_string (pango_int->value)); + + pango_int = (PangoAttrInt *) pango_attr_iterator_get (iter, PANGO_ATTR_STRETCH); + if (pango_int != NULL) + g_variant_builder_add (builder, "{ss}", "stretch", + pango_stretch_to_string (pango_int->value)); + + pango_int = (PangoAttrInt *) pango_attr_iterator_get (iter, PANGO_ATTR_SIZE); + if (pango_int != NULL) + { + value = g_strdup_printf ("%i", pango_int->value / PANGO_SCALE); + g_variant_builder_add (builder, "{ss}", "size", value); + g_free (value); + } + + pango_int = (PangoAttrInt *) pango_attr_iterator_get (iter, PANGO_ATTR_UNDERLINE); + if (pango_int != NULL) + g_variant_builder_add (builder, "{ss}", "underline", + pango_underline_to_string (pango_int->value)); + + pango_int = (PangoAttrInt *) pango_attr_iterator_get (iter, PANGO_ATTR_STRIKETHROUGH); + if (pango_int != NULL) + { + if (pango_int->value) + val = "true"; + else + val = "false"; + g_variant_builder_add (builder, "{ss}", "strikethrough", val); + } + + pango_int = (PangoAttrInt *) pango_attr_iterator_get (iter, PANGO_ATTR_RISE); + if (pango_int != NULL) + { + value = g_strdup_printf ("%i", pango_int->value); + g_variant_builder_add (builder, "{ss}", "rise", value); + g_free (value); + } + + pango_lang = (PangoAttrLanguage *) pango_attr_iterator_get (iter, PANGO_ATTR_LANGUAGE); + if (pango_lang != NULL) + { + g_variant_builder_add (builder, "{ss}", "language", + pango_language_to_string (pango_lang->value)); + } + + pango_float = (PangoAttrFloat *) pango_attr_iterator_get (iter, PANGO_ATTR_SCALE); + if (pango_float != NULL) + { + value = g_strdup_printf ("%g", pango_float->value); + g_variant_builder_add (builder, "{ss}", "scale", value); + g_free (value); + } + + pango_color = (PangoAttrColor *) pango_attr_iterator_get (iter, PANGO_ATTR_FOREGROUND); + if (pango_color != NULL) + { + value = g_strdup_printf ("%u,%u,%u", + pango_color->color.red, + pango_color->color.green, + pango_color->color.blue); + g_variant_builder_add (builder, "{ss}", "fg-color", value); + g_free (value); + } + + pango_color = (PangoAttrColor *) pango_attr_iterator_get (iter, PANGO_ATTR_BACKGROUND); + if (pango_color != NULL) + { + value = g_strdup_printf ("%u,%u,%u", + pango_color->color.red, + pango_color->color.green, + pango_color->color.blue); + g_variant_builder_add (builder, "{ss}", "bg-color", value); + g_free (value); + } + pango_attr_iterator_destroy (iter); +} + +/* + * gtk_pango_move_chars: + * @layout: a #PangoLayout + * @offset: a character offset in @layout + * @count: the number of characters to move from @offset + * + * Returns the position that is @count characters from the + * given @offset. @count may be positive or negative. + * + * For the purpose of this function, characters are defined + * by what Pango considers cursor positions. + * + * Returns: the new position + */ +static int +gtk_pango_move_chars (PangoLayout *layout, + int offset, + int count) +{ + const PangoLogAttr *attrs; + int n_attrs; + + attrs = pango_layout_get_log_attrs_readonly (layout, &n_attrs); + + while (count > 0 && offset < n_attrs - 1) + { + do + offset++; + while (offset < n_attrs - 1 && !attrs[offset].is_cursor_position); + + count--; + } + while (count < 0 && offset > 0) + { + do + offset--; + while (offset > 0 && !attrs[offset].is_cursor_position); + + count++; + } + + return offset; +} + +/* + * gtk_pango_move_words: + * @layout: a #PangoLayout + * @offset: a character offset in @layout + * @count: the number of words to move from @offset + * + * Returns the position that is @count words from the + * given @offset. @count may be positive or negative. + * + * If @count is positive, the returned position will + * be a word end, otherwise it will be a word start. + * See the Pango documentation for details on how + * word starts and ends are defined. + * + * Returns: the new position + */ +static int +gtk_pango_move_words (PangoLayout *layout, + int offset, + int count) +{ + const PangoLogAttr *attrs; + int n_attrs; + + attrs = pango_layout_get_log_attrs_readonly (layout, &n_attrs); + + while (count > 0 && offset < n_attrs - 1) + { + do + offset++; + while (offset < n_attrs - 1 && !attrs[offset].is_word_end); + + count--; + } + while (count < 0 && offset > 0) + { + do + offset--; + while (offset > 0 && !attrs[offset].is_word_start); + + count++; + } + + return offset; +} + +/* + * gtk_pango_move_sentences: + * @layout: a #PangoLayout + * @offset: a character offset in @layout + * @count: the number of sentences to move from @offset + * + * Returns the position that is @count sentences from the + * given @offset. @count may be positive or negative. + * + * If @count is positive, the returned position will + * be a sentence end, otherwise it will be a sentence start. + * See the Pango documentation for details on how + * sentence starts and ends are defined. + * + * Returns: the new position + */ +static int +gtk_pango_move_sentences (PangoLayout *layout, + int offset, + int count) +{ + const PangoLogAttr *attrs; + int n_attrs; + + attrs = pango_layout_get_log_attrs_readonly (layout, &n_attrs); + + while (count > 0 && offset < n_attrs - 1) + { + do + offset++; + while (offset < n_attrs - 1 && !attrs[offset].is_sentence_end); + + count--; + } + while (count < 0 && offset > 0) + { + do + offset--; + while (offset > 0 && !attrs[offset].is_sentence_start); + + count++; + } + + return offset; +} + +#if 0 +/* + * gtk_pango_move_lines: + * @layout: a #PangoLayout + * @offset: a character offset in @layout + * @count: the number of lines to move from @offset + * + * Returns the position that is @count lines from the + * given @offset. @count may be positive or negative. + * + * If @count is negative, the returned position will + * be the start of a line, else it will be the end of + * line. + * + * Returns: the new position + */ +static int +gtk_pango_move_lines (PangoLayout *layout, + int offset, + int count) +{ + GSList *lines, *l; + PangoLayoutLine *line; + int num; + const char *text; + int pos, line_pos; + int index; + int len; + + text = pango_layout_get_text (layout); + index = g_utf8_offset_to_pointer (text, offset) - text; + lines = pango_layout_get_lines (layout); + line = NULL; + + num = 0; + for (l = lines; l; l = l->next) + { + line = l->data; + if (index < line->start_index + line->length) + break; + num++; + } + + if (count < 0) + { + num += count; + if (num < 0) + num = 0; + + line = g_slist_nth_data (lines, num); + + return g_utf8_pointer_to_offset (text, text + line->start_index); + } + else + { + line_pos = index - line->start_index; + + len = g_slist_length (lines); + num += count; + if (num >= len || (count == 0 && num == len - 1)) + return g_utf8_strlen (text, -1) - 1; + + line = l->data; + pos = line->start_index + line_pos; + if (pos >= line->start_index + line->length) + pos = line->start_index + line->length - 1; + + return g_utf8_pointer_to_offset (text, text + pos); + } +} +#endif + +/* + * gtk_pango_is_inside_word: + * @layout: a #PangoLayout + * @offset: a character offset in @layout + * + * Returns whether the given position is inside + * a word. + * + * Returns: %TRUE if @offset is inside a word + */ +static gboolean +gtk_pango_is_inside_word (PangoLayout *layout, + int offset) +{ + const PangoLogAttr *attrs; + int n_attrs; + + attrs = pango_layout_get_log_attrs_readonly (layout, &n_attrs); + + while (offset >= 0 && + !(attrs[offset].is_word_start || attrs[offset].is_word_end)) + offset--; + + if (offset >= 0) + return attrs[offset].is_word_start; + + return FALSE; +} + +/* + * gtk_pango_is_inside_sentence: + * @layout: a #PangoLayout + * @offset: a character offset in @layout + * + * Returns whether the given position is inside + * a sentence. + * + * Returns: %TRUE if @offset is inside a sentence + */ +static gboolean +gtk_pango_is_inside_sentence (PangoLayout *layout, + int offset) +{ + const PangoLogAttr *attrs; + int n_attrs; + + attrs = pango_layout_get_log_attrs_readonly (layout, &n_attrs); + + while (offset >= 0 && + !(attrs[offset].is_sentence_start || attrs[offset].is_sentence_end)) + offset--; + + if (offset >= 0) + return attrs[offset].is_sentence_start; + + return FALSE; +} + +static void +pango_layout_get_line_before (PangoLayout *layout, + int offset, + AtspiTextBoundaryType boundary_type, + int *start_offset, + int *end_offset) +{ + PangoLayoutIter *iter; + PangoLayoutLine *line, *prev_line = NULL, *prev_prev_line = NULL; + int index, start_index, end_index; + const char *text; + gboolean found = FALSE; + + text = pango_layout_get_text (layout); + index = g_utf8_offset_to_pointer (text, offset) - text; + iter = pango_layout_get_iter (layout); + do + { + line = pango_layout_iter_get_line (iter); + start_index = line->start_index; + end_index = start_index + line->length; + + if (index >= start_index && index <= end_index) + { + /* Found line for offset */ + if (prev_line) + { + switch (boundary_type) + { + case ATSPI_TEXT_BOUNDARY_LINE_START: + end_index = start_index; + start_index = prev_line->start_index; + break; + case ATSPI_TEXT_BOUNDARY_LINE_END: + if (prev_prev_line) + start_index = prev_prev_line->start_index + prev_prev_line->length; + else + start_index = 0; + end_index = prev_line->start_index + prev_line->length; + break; + case ATSPI_TEXT_BOUNDARY_CHAR: + case ATSPI_TEXT_BOUNDARY_WORD_START: + case ATSPI_TEXT_BOUNDARY_WORD_END: + case ATSPI_TEXT_BOUNDARY_SENTENCE_START: + case ATSPI_TEXT_BOUNDARY_SENTENCE_END: + default: + g_assert_not_reached(); + } + } + else + start_index = end_index = 0; + + found = TRUE; + break; + } + + prev_prev_line = prev_line; + prev_line = line; + } + while (pango_layout_iter_next_line (iter)); + + if (!found) + { + start_index = prev_line->start_index + prev_line->length; + end_index = start_index; + } + pango_layout_iter_free (iter); + + *start_offset = g_utf8_pointer_to_offset (text, text + start_index); + *end_offset = g_utf8_pointer_to_offset (text, text + end_index); +} + +static void +pango_layout_get_line_at (PangoLayout *layout, + int offset, + AtspiTextBoundaryType boundary_type, + int *start_offset, + int *end_offset) +{ + PangoLayoutIter *iter; + PangoLayoutLine *line, *prev_line = NULL; + int index, start_index, end_index; + const char *text; + gboolean found = FALSE; + + text = pango_layout_get_text (layout); + index = g_utf8_offset_to_pointer (text, offset) - text; + iter = pango_layout_get_iter (layout); + do + { + line = pango_layout_iter_get_line (iter); + start_index = line->start_index; + end_index = start_index + line->length; + + if (index >= start_index && index <= end_index) + { + /* Found line for offset */ + switch (boundary_type) + { + case ATSPI_TEXT_BOUNDARY_LINE_START: + if (pango_layout_iter_next_line (iter)) + end_index = pango_layout_iter_get_line (iter)->start_index; + break; + case ATSPI_TEXT_BOUNDARY_LINE_END: + if (prev_line) + start_index = prev_line->start_index + prev_line->length; + break; + case ATSPI_TEXT_BOUNDARY_CHAR: + case ATSPI_TEXT_BOUNDARY_WORD_START: + case ATSPI_TEXT_BOUNDARY_WORD_END: + case ATSPI_TEXT_BOUNDARY_SENTENCE_START: + case ATSPI_TEXT_BOUNDARY_SENTENCE_END: + default: + g_assert_not_reached(); + } + + found = TRUE; + break; + } + + prev_line = line; + } + while (pango_layout_iter_next_line (iter)); + + if (!found) + { + start_index = prev_line->start_index + prev_line->length; + end_index = start_index; + } + pango_layout_iter_free (iter); + + *start_offset = g_utf8_pointer_to_offset (text, text + start_index); + *end_offset = g_utf8_pointer_to_offset (text, text + end_index); +} + +static void +pango_layout_get_line_after (PangoLayout *layout, + int offset, + AtspiTextBoundaryType boundary_type, + int *start_offset, + int *end_offset) +{ + PangoLayoutIter *iter; + PangoLayoutLine *line, *prev_line = NULL; + int index, start_index, end_index; + const char *text; + gboolean found = FALSE; + + text = pango_layout_get_text (layout); + index = g_utf8_offset_to_pointer (text, offset) - text; + iter = pango_layout_get_iter (layout); + do + { + line = pango_layout_iter_get_line (iter); + start_index = line->start_index; + end_index = start_index + line->length; + + if (index >= start_index && index <= end_index) + { + /* Found line for offset */ + if (pango_layout_iter_next_line (iter)) + { + line = pango_layout_iter_get_line (iter); + switch (boundary_type) + { + case ATSPI_TEXT_BOUNDARY_LINE_START: + start_index = line->start_index; + if (pango_layout_iter_next_line (iter)) + end_index = pango_layout_iter_get_line (iter)->start_index; + else + end_index = start_index + line->length; + break; + case ATSPI_TEXT_BOUNDARY_LINE_END: + start_index = end_index; + end_index = line->start_index + line->length; + break; + case ATSPI_TEXT_BOUNDARY_CHAR: + case ATSPI_TEXT_BOUNDARY_WORD_START: + case ATSPI_TEXT_BOUNDARY_WORD_END: + case ATSPI_TEXT_BOUNDARY_SENTENCE_START: + case ATSPI_TEXT_BOUNDARY_SENTENCE_END: + default: + g_assert_not_reached(); + } + } + else + start_index = end_index; + + found = TRUE; + break; + } + + prev_line = line; + } + while (pango_layout_iter_next_line (iter)); + + if (!found) + { + start_index = prev_line->start_index + prev_line->length; + end_index = start_index; + } + pango_layout_iter_free (iter); + + *start_offset = g_utf8_pointer_to_offset (text, text + start_index); + *end_offset = g_utf8_pointer_to_offset (text, text + end_index); +} + +/* + * gtk_pango_get_text_before: + * @layout: a #PangoLayout + * @offset: a character offset in @layout + * @boundary_type: a #AtspiTextBoundaryType + * @start_offset: return location for the start of the returned text + * @end_offset: return location for the end of the return text + * + * Gets a slice of the text from @layout before @offset. + * + * The @boundary_type determines the size of the returned slice of + * text. For the exact semantics of this function, see + * atk_text_get_text_before_offset(). + * + * Returns: a newly allocated string containing a slice of text + * from layout. Free with g_free(). + */ +char * +gtk_pango_get_text_before (PangoLayout *layout, + int offset, + AtspiTextBoundaryType boundary_type, + int *start_offset, + int *end_offset) +{ + const char *text; + int start, end; + const PangoLogAttr *attrs; + int n_attrs; + + text = pango_layout_get_text (layout); + + if (text[0] == 0) + { + *start_offset = 0; + *end_offset = 0; + return g_strdup (""); + } + + attrs = pango_layout_get_log_attrs_readonly (layout, &n_attrs); + + start = offset; + end = start; + + switch (boundary_type) + { + case ATSPI_TEXT_BOUNDARY_CHAR: + start = gtk_pango_move_chars (layout, start, -1); + break; + + case ATSPI_TEXT_BOUNDARY_WORD_START: + if (!attrs[start].is_word_start) + start = gtk_pango_move_words (layout, start, -1); + end = start; + start = gtk_pango_move_words (layout, start, -1); + break; + + case ATSPI_TEXT_BOUNDARY_WORD_END: + if (gtk_pango_is_inside_word (layout, start) && + !attrs[start].is_word_start) + start = gtk_pango_move_words (layout, start, -1); + while (!attrs[start].is_word_end && start > 0) + start = gtk_pango_move_chars (layout, start, -1); + end = start; + start = gtk_pango_move_words (layout, start, -1); + while (!attrs[start].is_word_end && start > 0) + start = gtk_pango_move_chars (layout, start, -1); + break; + + case ATSPI_TEXT_BOUNDARY_SENTENCE_START: + if (!attrs[start].is_sentence_start) + start = gtk_pango_move_sentences (layout, start, -1); + end = start; + start = gtk_pango_move_sentences (layout, start, -1); + break; + + case ATSPI_TEXT_BOUNDARY_SENTENCE_END: + if (gtk_pango_is_inside_sentence (layout, start) && + !attrs[start].is_sentence_start) + start = gtk_pango_move_sentences (layout, start, -1); + while (!attrs[start].is_sentence_end && start > 0) + start = gtk_pango_move_chars (layout, start, -1); + end = start; + start = gtk_pango_move_sentences (layout, start, -1); + while (!attrs[start].is_sentence_end && start > 0) + start = gtk_pango_move_chars (layout, start, -1); + break; + + case ATSPI_TEXT_BOUNDARY_LINE_START: + case ATSPI_TEXT_BOUNDARY_LINE_END: + pango_layout_get_line_before (layout, offset, boundary_type, &start, &end); + break; + + default: + g_assert_not_reached (); + break; + } + + *start_offset = start; + *end_offset = end; + + g_assert (start <= end); + + return g_utf8_substring (text, start, end); +} + +/* + * gtk_pango_get_text_after: + * @layout: a #PangoLayout + * @offset: a character offset in @layout + * @boundary_type: a #AtspiTextBoundaryType + * @start_offset: return location for the start of the returned text + * @end_offset: return location for the end of the return text + * + * Gets a slice of the text from @layout after @offset. + * + * The @boundary_type determines the size of the returned slice of + * text. For the exact semantics of this function, see + * atk_text_get_text_after_offset(). + * + * Returns: a newly allocated string containing a slice of text + * from layout. Free with g_free(). + */ +char * +gtk_pango_get_text_after (PangoLayout *layout, + int offset, + AtspiTextBoundaryType boundary_type, + int *start_offset, + int *end_offset) +{ + const char *text; + int start, end; + const PangoLogAttr *attrs; + int n_attrs; + + text = pango_layout_get_text (layout); + + if (text[0] == 0) + { + *start_offset = 0; + *end_offset = 0; + return g_strdup (""); + } + + attrs = pango_layout_get_log_attrs_readonly (layout, &n_attrs); + + start = offset; + end = start; + + switch (boundary_type) + { + case ATSPI_TEXT_BOUNDARY_CHAR: + start = gtk_pango_move_chars (layout, start, 1); + end = start; + end = gtk_pango_move_chars (layout, end, 1); + break; + + case ATSPI_TEXT_BOUNDARY_WORD_START: + if (gtk_pango_is_inside_word (layout, end)) + end = gtk_pango_move_words (layout, end, 1); + while (!attrs[end].is_word_start && end < n_attrs - 1) + end = gtk_pango_move_chars (layout, end, 1); + start = end; + if (end < n_attrs - 1) + { + end = gtk_pango_move_words (layout, end, 1); + while (!attrs[end].is_word_start && end < n_attrs - 1) + end = gtk_pango_move_chars (layout, end, 1); + } + break; + + case ATSPI_TEXT_BOUNDARY_WORD_END: + end = gtk_pango_move_words (layout, end, 1); + start = end; + if (end < n_attrs - 1) + end = gtk_pango_move_words (layout, end, 1); + break; + + case ATSPI_TEXT_BOUNDARY_SENTENCE_START: + if (gtk_pango_is_inside_sentence (layout, end)) + end = gtk_pango_move_sentences (layout, end, 1); + while (!attrs[end].is_sentence_start && end < n_attrs - 1) + end = gtk_pango_move_chars (layout, end, 1); + start = end; + if (end < n_attrs - 1) + { + end = gtk_pango_move_sentences (layout, end, 1); + while (!attrs[end].is_sentence_start && end < n_attrs - 1) + end = gtk_pango_move_chars (layout, end, 1); + } + break; + + case ATSPI_TEXT_BOUNDARY_SENTENCE_END: + end = gtk_pango_move_sentences (layout, end, 1); + start = end; + if (end < n_attrs - 1) + end = gtk_pango_move_sentences (layout, end, 1); + break; + + case ATSPI_TEXT_BOUNDARY_LINE_START: + case ATSPI_TEXT_BOUNDARY_LINE_END: + pango_layout_get_line_after (layout, offset, boundary_type, &start, &end); + break; + + default: + g_assert_not_reached (); + break; + } + + *start_offset = start; + *end_offset = end; + + g_assert (start <= end); + + return g_utf8_substring (text, start, end); +} + +/* + * gtk_pango_get_text_at: + * @layout: a #PangoLayout + * @offset: a character offset in @layout + * @boundary_type: a #AtspiTextBoundaryType + * @start_offset: return location for the start of the returned text + * @end_offset: return location for the end of the return text + * + * Gets a slice of the text from @layout at @offset. + * + * The @boundary_type determines the size of the returned slice of + * text. For the exact semantics of this function, see + * atk_text_get_text_after_offset(). + * + * Returns: a newly allocated string containing a slice of text + * from layout. Free with g_free(). + */ +char * +gtk_pango_get_text_at (PangoLayout *layout, + int offset, + AtspiTextBoundaryType boundary_type, + int *start_offset, + int *end_offset) +{ + const char *text; + int start, end; + const PangoLogAttr *attrs; + int n_attrs; + + text = pango_layout_get_text (layout); + + if (text[0] == 0) + { + *start_offset = 0; + *end_offset = 0; + return g_strdup (""); + } + + attrs = pango_layout_get_log_attrs_readonly (layout, &n_attrs); + + start = offset; + end = start; + + switch (boundary_type) + { + case ATSPI_TEXT_BOUNDARY_CHAR: + end = gtk_pango_move_chars (layout, end, 1); + break; + + case ATSPI_TEXT_BOUNDARY_WORD_START: + if (!attrs[start].is_word_start) + start = gtk_pango_move_words (layout, start, -1); + if (gtk_pango_is_inside_word (layout, end)) + end = gtk_pango_move_words (layout, end, 1); + while (!attrs[end].is_word_start && end < n_attrs - 1) + end = gtk_pango_move_chars (layout, end, 1); + break; + + case ATSPI_TEXT_BOUNDARY_WORD_END: + if (gtk_pango_is_inside_word (layout, start) && + !attrs[start].is_word_start) + start = gtk_pango_move_words (layout, start, -1); + while (!attrs[start].is_word_end && start > 0) + start = gtk_pango_move_chars (layout, start, -1); + end = gtk_pango_move_words (layout, end, 1); + break; + + case ATSPI_TEXT_BOUNDARY_SENTENCE_START: + if (!attrs[start].is_sentence_start) + start = gtk_pango_move_sentences (layout, start, -1); + if (gtk_pango_is_inside_sentence (layout, end)) + end = gtk_pango_move_sentences (layout, end, 1); + while (!attrs[end].is_sentence_start && end < n_attrs - 1) + end = gtk_pango_move_chars (layout, end, 1); + break; + + case ATSPI_TEXT_BOUNDARY_SENTENCE_END: + if (gtk_pango_is_inside_sentence (layout, start) && + !attrs[start].is_sentence_start) + start = gtk_pango_move_sentences (layout, start, -1); + while (!attrs[start].is_sentence_end && start > 0) + start = gtk_pango_move_chars (layout, start, -1); + end = gtk_pango_move_sentences (layout, end, 1); + break; + + case ATSPI_TEXT_BOUNDARY_LINE_START: + case ATSPI_TEXT_BOUNDARY_LINE_END: + pango_layout_get_line_at (layout, offset, boundary_type, &start, &end); + break; + + default: + g_assert_not_reached (); + break; + } + + *start_offset = start; + *end_offset = end; + + g_assert (start <= end); + + return g_utf8_substring (text, start, end); +} + +char *gtk_pango_get_string_at (PangoLayout *layout, + int offset, + AtspiTextGranularity granularity, + int *start_offset, + int *end_offset) +{ + const char *text; + int start, end; + const PangoLogAttr *attrs; + int n_attrs; + + text = pango_layout_get_text (layout); + + if (text[0] == 0) + { + *start_offset = 0; + *end_offset = 0; + return g_strdup (""); + } + + attrs = pango_layout_get_log_attrs_readonly (layout, &n_attrs); + + start = offset; + end = start; + + switch (granularity) + { + case ATSPI_TEXT_GRANULARITY_CHAR: + end = gtk_pango_move_chars (layout, end, 1); + break; + + case ATSPI_TEXT_GRANULARITY_WORD: + if (!attrs[start].is_word_start) + start = gtk_pango_move_words (layout, start, -1); + if (gtk_pango_is_inside_word (layout, end)) + end = gtk_pango_move_words (layout, end, 1); + while (!attrs[end].is_word_start && end < n_attrs - 1) + end = gtk_pango_move_chars (layout, end, 1); + break; + + case ATSPI_TEXT_GRANULARITY_SENTENCE: + if (!attrs[start].is_sentence_start) + start = gtk_pango_move_sentences (layout, start, -1); + if (gtk_pango_is_inside_sentence (layout, end)) + end = gtk_pango_move_sentences (layout, end, 1); + while (!attrs[end].is_sentence_start && end < n_attrs - 1) + end = gtk_pango_move_chars (layout, end, 1); + break; + + case ATSPI_TEXT_GRANULARITY_LINE: + pango_layout_get_line_at (layout, offset, ATSPI_TEXT_BOUNDARY_LINE_START, &start, &end); + break; + + case ATSPI_TEXT_GRANULARITY_PARAGRAPH: + /* FIXME: In theory, a layout can hold more than one paragraph */ + start = 0; + end = g_utf8_strlen (text, -1); + break; + + default: + g_assert_not_reached (); + break; + } + + *start_offset = start; + *end_offset = end; + + g_assert (start <= end); + + return g_utf8_substring (text, start, end); +} diff --git a/gtk/a11y/gtkatspipangoprivate.h b/gtk/a11y/gtkatspipangoprivate.h new file mode 100644 index 0000000000..e9e91d292f --- /dev/null +++ b/gtk/a11y/gtkatspipangoprivate.h @@ -0,0 +1,62 @@ +/* gtkatspipangoprivate.h: Utilities for pango and AT-SPI + * Copyright 2020 Red Hat, Inc + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <pango/pangocairo.h> +#include "gtkatspiprivate.h" + +G_BEGIN_DECLS + +const char *pango_wrap_mode_to_string (PangoWrapMode mode); +const char *pango_underline_to_string (PangoUnderline underline); +const char *pango_stretch_to_string (PangoStretch stretch); +const char *pango_style_to_string (PangoStyle style); +const char *pango_variant_to_string (PangoVariant variant); + +void gtk_pango_get_font_attributes (PangoFontDescription *font, + GVariantBuilder *builder); +void gtk_pango_get_default_attributes (PangoLayout *layout, + GVariantBuilder *builder); +void gtk_pango_get_run_attributes (PangoLayout *layout, + GVariantBuilder *builder, + int offset, + int *start_offset, + int *end_offset); + +char *gtk_pango_get_text_before (PangoLayout *layout, + int offset, + AtspiTextBoundaryType boundary_type, + int *start_offset, + int *end_offset); +char *gtk_pango_get_text_at (PangoLayout *layout, + int offset, + AtspiTextBoundaryType boundary_type, + int *start_offset, + int *end_offset); +char *gtk_pango_get_text_after (PangoLayout *layout, + int offset, + AtspiTextBoundaryType boundary_type, + int *start_offset, + int *end_offset); +char *gtk_pango_get_string_at (PangoLayout *layout, + int offset, + AtspiTextGranularity granularity, + int *start_offset, + int *end_offset); + +G_END_DECLS diff --git a/gtk/a11y/gtkatspiprivate.h b/gtk/a11y/gtkatspiprivate.h new file mode 100644 index 0000000000..c3fa225df8 --- /dev/null +++ b/gtk/a11y/gtkatspiprivate.h @@ -0,0 +1,249 @@ +/* gtkatspiprivate.h: AT-SPI shared types + * + * Copyright 2020 GNOME Foundation + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <glib.h> + +G_BEGIN_DECLS + +typedef enum { + ATSPI_ROLE_INVALID, + ATSPI_ROLE_ACCELERATOR_LABEL, + ATSPI_ROLE_ALERT, + ATSPI_ROLE_ANIMATION, + ATSPI_ROLE_ARROW, + ATSPI_ROLE_CALENDAR, + ATSPI_ROLE_CANVAS, + ATSPI_ROLE_CHECK_BOX, + ATSPI_ROLE_CHECK_MENU_ITEM, + ATSPI_ROLE_COLOR_CHOOSER, + ATSPI_ROLE_COLUMN_HEADER, + ATSPI_ROLE_COMBO_BOX, + ATSPI_ROLE_DATE_EDITOR, + ATSPI_ROLE_DESKTOP_ICON, + ATSPI_ROLE_DESKTOP_FRAME, + ATSPI_ROLE_DIAL, + ATSPI_ROLE_DIALOG, + ATSPI_ROLE_DIRECTORY_PANE, + ATSPI_ROLE_DRAWING_AREA, + ATSPI_ROLE_FILE_CHOOSER, + ATSPI_ROLE_FILLER, + ATSPI_ROLE_FOCUS_TRAVERSABLE, + ATSPI_ROLE_FONT_CHOOSER, + ATSPI_ROLE_FRAME, + ATSPI_ROLE_GLASS_PANE, + ATSPI_ROLE_HTML_CONTAINER, + ATSPI_ROLE_ICON, + ATSPI_ROLE_IMAGE, + ATSPI_ROLE_INTERNAL_FRAME, + ATSPI_ROLE_LABEL, + ATSPI_ROLE_LAYERED_PANE, + ATSPI_ROLE_LIST, + ATSPI_ROLE_LIST_ITEM, + ATSPI_ROLE_MENU, + ATSPI_ROLE_MENU_BAR, + ATSPI_ROLE_MENU_ITEM, + ATSPI_ROLE_OPTION_PANE, + ATSPI_ROLE_PAGE_TAB, + ATSPI_ROLE_PAGE_TAB_LIST, + ATSPI_ROLE_PANEL, + ATSPI_ROLE_PASSWORD_TEXT, + ATSPI_ROLE_POPUP_MENU, + ATSPI_ROLE_PROGRESS_BAR, + ATSPI_ROLE_PUSH_BUTTON, + ATSPI_ROLE_RADIO_BUTTON, + ATSPI_ROLE_RADIO_MENU_ITEM, + ATSPI_ROLE_ROOT_PANE, + ATSPI_ROLE_ROW_HEADER, + ATSPI_ROLE_SCROLL_BAR, + ATSPI_ROLE_SCROLL_PANE, + ATSPI_ROLE_SEPARATOR, + ATSPI_ROLE_SLIDER, + ATSPI_ROLE_SPIN_BUTTON, + ATSPI_ROLE_SPLIT_PANE, + ATSPI_ROLE_STATUS_BAR, + ATSPI_ROLE_TABLE, + ATSPI_ROLE_TABLE_CELL, + ATSPI_ROLE_TABLE_COLUMN_HEADER, + ATSPI_ROLE_TABLE_ROW_HEADER, + ATSPI_ROLE_TEAROFF_MENU_ITEM, + ATSPI_ROLE_TERMINAL, + ATSPI_ROLE_TEXT, + ATSPI_ROLE_TOGGLE_BUTTON, + ATSPI_ROLE_TOOL_BAR, + ATSPI_ROLE_TOOL_TIP, + ATSPI_ROLE_TREE, + ATSPI_ROLE_TREE_TABLE, + ATSPI_ROLE_UNKNOWN, + ATSPI_ROLE_VIEWPORT, + ATSPI_ROLE_WINDOW, + ATSPI_ROLE_EXTENDED, + ATSPI_ROLE_HEADER, + ATSPI_ROLE_FOOTER, + ATSPI_ROLE_PARAGRAPH, + ATSPI_ROLE_RULER, + ATSPI_ROLE_APPLICATION, + ATSPI_ROLE_AUTOCOMPLETE, + ATSPI_ROLE_EDITBAR, + ATSPI_ROLE_EMBEDDED, + ATSPI_ROLE_ENTRY, + ATSPI_ROLE_CHART, + ATSPI_ROLE_CAPTION, + ATSPI_ROLE_DOCUMENT_FRAME, + ATSPI_ROLE_HEADING, + ATSPI_ROLE_PAGE, + ATSPI_ROLE_SECTION, + ATSPI_ROLE_REDUNDANT_OBJECT, + ATSPI_ROLE_FORM, + ATSPI_ROLE_LINK, + ATSPI_ROLE_INPUT_METHOD_WINDOW, + ATSPI_ROLE_TABLE_ROW, + ATSPI_ROLE_TREE_ITEM, + ATSPI_ROLE_DOCUMENT_SPREADSHEET, + ATSPI_ROLE_DOCUMENT_PRESENTATION, + ATSPI_ROLE_DOCUMENT_TEXT, + ATSPI_ROLE_DOCUMENT_WEB, + ATSPI_ROLE_DOCUMENT_EMAIL, + ATSPI_ROLE_COMMENT, + ATSPI_ROLE_LIST_BOX, + ATSPI_ROLE_GROUPING, + ATSPI_ROLE_IMAGE_MAP, + ATSPI_ROLE_NOTIFICATION, + ATSPI_ROLE_INFO_BAR, + ATSPI_ROLE_LEVEL_BAR, + ATSPI_ROLE_TITLE_BAR, + ATSPI_ROLE_BLOCK_QUOTE, + ATSPI_ROLE_AUDIO, + ATSPI_ROLE_VIDEO, + ATSPI_ROLE_DEFINITION, + ATSPI_ROLE_ARTICLE, + ATSPI_ROLE_LANDMARK, + ATSPI_ROLE_LOG, + ATSPI_ROLE_MARQUEE, + ATSPI_ROLE_MATH, + ATSPI_ROLE_RATING, + ATSPI_ROLE_TIMER, + ATSPI_ROLE_STATIC, + ATSPI_ROLE_MATH_FRACTION, + ATSPI_ROLE_MATH_ROOT, + ATSPI_ROLE_SUBSCRIPT, + ATSPI_ROLE_SUPERSCRIPT, + ATSPI_ROLE_DESCRIPTION_LIST, + ATSPI_ROLE_DESCRIPTION_TERM, + ATSPI_ROLE_DESCRIPTION_VALUE, + ATSPI_ROLE_FOOTNOTE, + ATSPI_ROLE_CONTENT_DELETION, + ATSPI_ROLE_CONTENT_INSERTION, + ATSPI_ROLE_MARK, + ATSPI_ROLE_SUGGESTION, + ATSPI_ROLE_LAST_DEFINED, +} AtspiRole; + +typedef enum { + ATSPI_STATE_INVALID, + ATSPI_STATE_ACTIVE, + ATSPI_STATE_ARMED, + ATSPI_STATE_BUSY, + ATSPI_STATE_CHECKED, + ATSPI_STATE_COLLAPSED, + ATSPI_STATE_DEFUNCT, + ATSPI_STATE_EDITABLE, + ATSPI_STATE_ENABLED, + ATSPI_STATE_EXPANDABLE, + ATSPI_STATE_EXPANDED, + ATSPI_STATE_FOCUSABLE, + ATSPI_STATE_FOCUSED, + ATSPI_STATE_HAS_TOOLTIP, + ATSPI_STATE_HORIZONTAL, + ATSPI_STATE_ICONIFIED, + ATSPI_STATE_MODAL, + ATSPI_STATE_MULTI_LINE, + ATSPI_STATE_MULTISELECTABLE, + ATSPI_STATE_OPAQUE, + ATSPI_STATE_PRESSED, + ATSPI_STATE_RESIZABLE, + ATSPI_STATE_SELECTABLE, + ATSPI_STATE_SELECTED, + ATSPI_STATE_SENSITIVE, + ATSPI_STATE_SHOWING, + ATSPI_STATE_SINGLE_LINE, + ATSPI_STATE_STALE, + ATSPI_STATE_TRANSIENT, + ATSPI_STATE_VERTICAL, + ATSPI_STATE_VISIBLE, + ATSPI_STATE_MANAGES_DESCENDANTS, + ATSPI_STATE_INDETERMINATE, + ATSPI_STATE_REQUIRED, + ATSPI_STATE_TRUNCATED, + ATSPI_STATE_ANIMATED, + ATSPI_STATE_INVALID_ENTRY, + ATSPI_STATE_SUPPORTS_AUTOCOMPLETION, + ATSPI_STATE_SELECTABLE_TEXT, + ATSPI_STATE_IS_DEFAULT, + ATSPI_STATE_VISITED, + ATSPI_STATE_CHECKABLE, + ATSPI_STATE_HAS_POPUP, + ATSPI_STATE_READ_ONLY, + ATSPI_STATE_LAST_DEFINED, +} AtspiStateType; + +typedef enum { + ATSPI_RELATION_NULL, + ATSPI_RELATION_LABEL_FOR, + ATSPI_RELATION_LABELLED_BY, + ATSPI_RELATION_CONTROLLER_FOR, + ATSPI_RELATION_CONTROLLED_BY, + ATSPI_RELATION_MEMBER_OF, + ATSPI_RELATION_TOOLTIP_FOR, + ATSPI_RELATION_NODE_CHILD_OF, + ATSPI_RELATION_NODE_PARENT_OF, + ATSPI_RELATION_EXTENDED, + ATSPI_RELATION_FLOWS_TO, + ATSPI_RELATION_FLOWS_FROM, + ATSPI_RELATION_SUBWINDOW_OF, + ATSPI_RELATION_EMBEDS, + ATSPI_RELATION_EMBEDDED_BY, + ATSPI_RELATION_POPUP_FOR, + ATSPI_RELATION_PARENT_WINDOW_OF, + ATSPI_RELATION_DESCRIPTION_FOR, + ATSPI_RELATION_DESCRIBED_BY, + ATSPI_RELATION_LAST_DEFINED, +} AtspiRelationType; + +typedef enum { + ATSPI_TEXT_BOUNDARY_CHAR, + ATSPI_TEXT_BOUNDARY_WORD_START, + ATSPI_TEXT_BOUNDARY_WORD_END, + ATSPI_TEXT_BOUNDARY_SENTENCE_START, + ATSPI_TEXT_BOUNDARY_SENTENCE_END, + ATSPI_TEXT_BOUNDARY_LINE_START, + ATSPI_TEXT_BOUNDARY_LINE_END, +} AtspiTextBoundaryType; + +typedef enum { + ATSPI_TEXT_GRANULARITY_CHAR, + ATSPI_TEXT_GRANULARITY_WORD, + ATSPI_TEXT_GRANULARITY_SENTENCE, + ATSPI_TEXT_GRANULARITY_LINE, + ATSPI_TEXT_GRANULARITY_PARAGRAPH +} AtspiTextGranularity; + +G_END_DECLS diff --git a/gtk/a11y/gtkatspiroot.c b/gtk/a11y/gtkatspiroot.c new file mode 100644 index 0000000000..75290623ea --- /dev/null +++ b/gtk/a11y/gtkatspiroot.c @@ -0,0 +1,603 @@ +/* gtkatspiroot.c: AT-SPI root object + * + * Copyright 2020 GNOME Foundation + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include "gtkatspirootprivate.h" + +#include "gtkatspicontextprivate.h" +#include "gtkaccessibleprivate.h" +#include "gtkatspiprivate.h" +#include "gtkatspiutilsprivate.h" + +#include "gtkdebug.h" +#include "gtkwindow.h" + +#include "a11y/atspi/atspi-accessible.h" +#include "a11y/atspi/atspi-application.h" + +#include <locale.h> + +#include <glib/gi18n-lib.h> +#include <gio/gio.h> + +#define ATSPI_VERSION "2.1" + +#define ATSPI_PATH_PREFIX "/org/a11y/atspi" +#define ATSPI_ROOT_PATH ATSPI_PATH_PREFIX "/accessible/root" +#define ATSPI_CACHE_PATH ATSPI_PATH_PREFIX "/cache" + +struct _GtkAtSpiRoot +{ + GObject parent_instance; + + char *bus_address; + GDBusConnection *connection; + + const char *root_path; + + const char *toolkit_name; + const char *version; + const char *atspi_version; + + char *desktop_name; + char *desktop_path; + + gint32 application_id; + + GtkAtSpiCache *cache; + + GListModel *toplevels; +}; + +enum +{ + PROP_BUS_ADDRESS = 1, + + N_PROPS +}; + +static GParamSpec *obj_props[N_PROPS]; + +G_DEFINE_TYPE (GtkAtSpiRoot, gtk_at_spi_root, G_TYPE_OBJECT) + +static void +gtk_at_spi_root_finalize (GObject *gobject) +{ + GtkAtSpiRoot *self = GTK_AT_SPI_ROOT (gobject); + + g_free (self->bus_address); + g_free (self->desktop_name); + g_free (self->desktop_path); + + G_OBJECT_CLASS (gtk_at_spi_root_parent_class)->dispose (gobject); +} + +static void +gtk_at_spi_root_dispose (GObject *gobject) +{ + GtkAtSpiRoot *self = GTK_AT_SPI_ROOT (gobject); + + g_clear_object (&self->cache); + g_clear_object (&self->connection); + + G_OBJECT_CLASS (gtk_at_spi_root_parent_class)->dispose (gobject); +} + +static void +gtk_at_spi_root_set_property (GObject *gobject, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkAtSpiRoot *self = GTK_AT_SPI_ROOT (gobject); + + switch (prop_id) + { + case PROP_BUS_ADDRESS: + self->bus_address = g_value_dup_string (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + } +} + +static void +gtk_at_spi_root_get_property (GObject *gobject, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GtkAtSpiRoot *self = GTK_AT_SPI_ROOT (gobject); + + switch (prop_id) + { + case PROP_BUS_ADDRESS: + g_value_set_string (value, self->bus_address); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + } +} + +static void +handle_application_method (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *method_name, + GVariant *parameters, + GDBusMethodInvocation *invocation, + gpointer user_data) +{ + if (g_strcmp0 (method_name, "GetLocale") == 0) + { + guint lctype; + const char *locale; + + int types[] = { + LC_MESSAGES, LC_COLLATE, LC_CTYPE, LC_MONETARY, LC_NUMERIC, LC_TIME + }; + + g_variant_get (parameters, "(u)", &lctype); + if (lctype >= G_N_ELEMENTS (types)) + { + g_dbus_method_invocation_return_error (invocation, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + "Not a known locale facet: %u", lctype); + return; + } + + locale = setlocale (types[lctype], NULL); + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", locale)); + } +} + +static GVariant * +handle_application_get_property (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *property_name, + GError **error, + gpointer user_data) +{ + GtkAtSpiRoot *self = user_data; + GVariant *res = NULL; + + if (g_strcmp0 (property_name, "Id") == 0) + res = g_variant_new_int32 (self->application_id); + else if (g_strcmp0 (property_name, "ToolkitName") == 0) + res = g_variant_new_string (self->toolkit_name); + else if (g_strcmp0 (property_name, "Version") == 0) + res = g_variant_new_string (self->version); + else if (g_strcmp0 (property_name, "AtspiVersion") == 0) + res = g_variant_new_string (self->atspi_version); + else + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + "Unknown property '%s'", property_name); + + return res; +} + +static gboolean +handle_application_set_property (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *property_name, + GVariant *value, + GError **error, + gpointer user_data) +{ + GtkAtSpiRoot *self = user_data; + + if (g_strcmp0 (property_name, "Id") == 0) + { + g_variant_get (value, "i", &(self->application_id)); + } + else + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + "Invalid property '%s'", property_name); + return FALSE; + } + + return TRUE; +} + +static void +handle_accessible_method (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *method_name, + GVariant *parameters, + GDBusMethodInvocation *invocation, + gpointer user_data) +{ + GtkAtSpiRoot *self = user_data; + + if (g_strcmp0 (method_name, "GetRole") == 0) + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(u)", ATSPI_ROLE_APPLICATION)); + else if (g_strcmp0 (method_name, "GetRoleName") == 0) + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", "application")); + else if (g_strcmp0 (method_name, "GetLocalizedRoleName") == 0) + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", C_("accessibility", "application"))); + else if (g_strcmp0 (method_name, "GetState") == 0) + { + GVariantBuilder builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("(au)")); + + g_variant_builder_open (&builder, G_VARIANT_TYPE ("au")); + g_variant_builder_add (&builder, "u", 0); + g_variant_builder_add (&builder, "u", 0); + g_variant_builder_close (&builder); + + g_dbus_method_invocation_return_value (invocation, g_variant_builder_end (&builder)); + } + else if (g_strcmp0 (method_name, "GetAttributes") == 0) + { + GVariantBuilder builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("(a{ss})")); + + g_variant_builder_open (&builder, G_VARIANT_TYPE ("a{ss}")); + g_variant_builder_add (&builder, "{ss}", "toolkit", "GTK"); + g_variant_builder_close (&builder); + + g_dbus_method_invocation_return_value (invocation, g_variant_builder_end (&builder)); + } + else if (g_strcmp0 (method_name, "GetApplication") == 0) + { + g_dbus_method_invocation_return_value (invocation, + g_variant_new ("((so))", + self->desktop_name, + self->desktop_path)); + } + else if (g_strcmp0 (method_name, "GetChildAtIndex") == 0) + { + int idx, real_idx = 0; + + g_variant_get (parameters, "(i)", &idx); + + GtkWidget *window = NULL; + guint n_toplevels = g_list_model_get_n_items (self->toplevels); + for (guint i = 0; i < n_toplevels; i++) + { + window = g_list_model_get_item (self->toplevels, i); + + g_object_unref (window); + + if (!gtk_widget_get_visible (window)) + continue; + + if (real_idx == idx) + break; + + real_idx += 1; + } + + if (window == NULL) + return; + + GtkATContext *context = gtk_accessible_get_at_context (GTK_ACCESSIBLE (window)); + + const char *name = g_dbus_connection_get_unique_name (self->connection); + const char *path = gtk_at_spi_context_get_context_path (GTK_AT_SPI_CONTEXT (context)); + + g_dbus_method_invocation_return_value (invocation, g_variant_new ("((so))", name, path)); + } + else if (g_strcmp0 (method_name, "GetChildren") == 0) + { + GVariantBuilder builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("a(so)")); + + guint n_toplevels = g_list_model_get_n_items (self->toplevels); + for (guint i = 0; i < n_toplevels; i++) + { + GtkWidget *window = g_list_model_get_item (self->toplevels, i); + + g_object_unref (window); + + if (!gtk_widget_get_visible (window)) + continue; + + GtkATContext *context = gtk_accessible_get_at_context (GTK_ACCESSIBLE (window)); + const char *name = g_dbus_connection_get_unique_name (self->connection); + const char *path = gtk_at_spi_context_get_context_path (GTK_AT_SPI_CONTEXT (context)); + + g_variant_builder_add (&builder, "(so)", name, path); + } + + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(a(so))", &builder)); + } + else if (g_strcmp0 (method_name, "GetIndexInParent") == 0) + { + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(i)", -1)); + } + else if (g_strcmp0 (method_name, "GetRelationSet") == 0) + { + GVariantBuilder builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("a(ua(so))")); + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(a(ua(so)))", &builder)); + } + else if (g_strcmp0 (method_name, "GetInterfaces") == 0) + { + GVariantBuilder builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("as")); + + g_variant_builder_add (&builder, "s", "org.a11y.atspi.Accessible"); + g_variant_builder_add (&builder, "s", "org.a11y.atspi.Application"); + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(as)", &builder)); + } +} + +static GVariant * +handle_accessible_get_property (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *property_name, + GError **error, + gpointer user_data) +{ + GtkAtSpiRoot *self = user_data; + GVariant *res = NULL; + + if (g_strcmp0 (property_name, "Name") == 0) + res = g_variant_new_string (g_get_prgname () ? g_get_prgname () : "Unnamed"); + else if (g_strcmp0 (property_name, "Description") == 0) + res = g_variant_new_string (g_get_application_name () ? g_get_application_name () : "No description"); + else if (g_strcmp0 (property_name, "Locale") == 0) + res = g_variant_new_string (setlocale (LC_MESSAGES, NULL)); + else if (g_strcmp0 (property_name, "AccessibleId") == 0) + res = g_variant_new_string (""); + else if (g_strcmp0 (property_name, "Parent") == 0) + res = g_variant_new ("(so)", self->desktop_name, self->desktop_path); + else if (g_strcmp0 (property_name, "ChildCount") == 0) + { + guint n_toplevels = g_list_model_get_n_items (self->toplevels); + int n_children = 0; + + for (guint i = 0; i < n_toplevels; i++) + { + GtkWidget *window = g_list_model_get_item (self->toplevels, i); + + if (gtk_widget_get_visible (window)) + n_children += 1; + + g_object_unref (window); + } + + res = g_variant_new_int32 (n_children); + } + else + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + "Unknown property '%s'", property_name); + + return res; +} + +static const GDBusInterfaceVTable root_application_vtable = { + handle_application_method, + handle_application_get_property, + handle_application_set_property, +}; + +static const GDBusInterfaceVTable root_accessible_vtable = { + handle_accessible_method, + handle_accessible_get_property, + NULL, +}; + +static void +on_registration_reply (GObject *gobject, + GAsyncResult *result, + gpointer user_data) +{ + GtkAtSpiRoot *self = user_data; + + GError *error = NULL; + GVariant *reply = g_dbus_connection_call_finish (G_DBUS_CONNECTION (gobject), result, &error); + + if (error != NULL) + { + g_critical ("Unable to register the application: %s", error->message); + g_error_free (error); + return; + } + + if (reply != NULL) + { + g_variant_get (reply, "((so))", + &self->desktop_name, + &self->desktop_path); + g_variant_unref (reply); + + GTK_NOTE (A11Y, g_message ("Connected to the a11y registry at (%s, %s)", + self->desktop_name, + self->desktop_path)); + } + + /* Register the cache object */ + self->cache = gtk_at_spi_cache_new (self->connection, ATSPI_CACHE_PATH); + + /* Monitor the top levels */ + self->toplevels = gtk_window_get_toplevels (); +} + +static void +gtk_at_spi_root_register (GtkAtSpiRoot *self) +{ + /* Register the root element; every application has a single root, so we only + * need to do this once. + * + * The root element is used to advertise our existence on the accessibility + * bus, and it's the entry point to the accessible objects tree. + * + * The announcement is split into two phases: + * + * 1. we register the org.a11y.atspi.Application and org.a11y.atspi.Accessible + * interfaces at the well-known object path + * 2. we invoke the org.a11y.atspi.Socket.Embed method with the connection's + * unique name and the object path + * 3. the ATSPI registry daemon will set the org.a11y.atspi.Application.Id + * property on the given object path + * 4. the registration concludes when the Embed method returns us the desktop + * name and object path + */ + self->toolkit_name = "GTK"; + self->version = PACKAGE_VERSION; + self->atspi_version = ATSPI_VERSION; + self->root_path = ATSPI_ROOT_PATH; + + g_dbus_connection_register_object (self->connection, + self->root_path, + (GDBusInterfaceInfo *) &atspi_application_interface, + &root_application_vtable, + self, + NULL, + NULL); + g_dbus_connection_register_object (self->connection, + self->root_path, + (GDBusInterfaceInfo *) &atspi_accessible_interface, + &root_accessible_vtable, + self, + NULL, + NULL); + + GTK_NOTE (A11Y, g_message ("Registering (%s, %s) on the a11y bus", + g_dbus_connection_get_unique_name (self->connection), + self->root_path)); + + g_dbus_connection_call (self->connection, + "org.a11y.atspi.Registry", + ATSPI_ROOT_PATH, + "org.a11y.atspi.Socket", + "Embed", + g_variant_new ("((so))", + g_dbus_connection_get_unique_name (self->connection), + self->root_path + ), + G_VARIANT_TYPE ("((so))"), + G_DBUS_CALL_FLAGS_NONE, -1, + NULL, + on_registration_reply, + self); +} + +static void +gtk_at_spi_root_constructed (GObject *gobject) +{ + GtkAtSpiRoot *self = GTK_AT_SPI_ROOT (gobject); + + GError *error = NULL; + + /* The accessibility bus is a fully managed bus */ + self->connection = + g_dbus_connection_new_for_address_sync (self->bus_address, + G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT | + G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION, + NULL, NULL, + &error); + + if (error != NULL) + { + g_critical ("Unable to connect to the accessibility bus at '%s': %s", + self->bus_address, + error->message); + g_error_free (error); + goto out; + } + + gtk_at_spi_root_register (self); + +out: + G_OBJECT_CLASS (gtk_at_spi_root_parent_class)->constructed (gobject); +} + +static void +gtk_at_spi_root_class_init (GtkAtSpiRootClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->constructed = gtk_at_spi_root_constructed; + gobject_class->set_property = gtk_at_spi_root_set_property; + gobject_class->get_property = gtk_at_spi_root_get_property; + gobject_class->dispose = gtk_at_spi_root_dispose; + gobject_class->finalize = gtk_at_spi_root_finalize; + + obj_props[PROP_BUS_ADDRESS] = + g_param_spec_string ("bus-address", NULL, NULL, + NULL, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (gobject_class, N_PROPS, obj_props); +} + +static void +gtk_at_spi_root_init (GtkAtSpiRoot *self) +{ +} + +GtkAtSpiRoot * +gtk_at_spi_root_new (const char *bus_address) +{ + g_return_val_if_fail (bus_address != NULL, NULL); + + return g_object_new (GTK_TYPE_AT_SPI_ROOT, + "bus-address", bus_address, + NULL); +} + +GDBusConnection * +gtk_at_spi_root_get_connection (GtkAtSpiRoot *self) +{ + g_return_val_if_fail (GTK_IS_AT_SPI_ROOT (self), NULL); + + return self->connection; +} + +GtkAtSpiCache * +gtk_at_spi_root_get_cache (GtkAtSpiRoot *self) +{ + g_return_val_if_fail (GTK_IS_AT_SPI_ROOT (self), NULL); + + return self->cache; +} + +/*< private > + * gtk_at_spi_root_to_ref: + * @self: a #GtkAtSpiRoot + * + * Returns an ATSPI object reference for the #GtkAtSpiRoot node. + * + * Returns: (transfer floating): a #GVariant with the root reference + */ +GVariant * +gtk_at_spi_root_to_ref (GtkAtSpiRoot *self) +{ + g_return_val_if_fail (GTK_IS_AT_SPI_ROOT (self), NULL); + + if (self->desktop_path == NULL) + return gtk_at_spi_null_ref (); + + return g_variant_new ("(so)", self->desktop_name, self->desktop_path); +} diff --git a/gtk/a11y/gtkatspirootprivate.h b/gtk/a11y/gtkatspirootprivate.h new file mode 100644 index 0000000000..0ce5e6b693 --- /dev/null +++ b/gtk/a11y/gtkatspirootprivate.h @@ -0,0 +1,45 @@ +/* gtkatspirootprivate.h: AT-SPI root object + * + * Copyright 2020 GNOME Foundation + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <gio/gio.h> + +#include "gtkatspicacheprivate.h" + +G_BEGIN_DECLS + +#define GTK_TYPE_AT_SPI_ROOT (gtk_at_spi_root_get_type()) + +G_DECLARE_FINAL_TYPE (GtkAtSpiRoot, gtk_at_spi_root, GTK, AT_SPI_ROOT, GObject) + +GtkAtSpiRoot * +gtk_at_spi_root_new (const char *bus_address); + +GDBusConnection * +gtk_at_spi_root_get_connection (GtkAtSpiRoot *self); + +GtkAtSpiCache * +gtk_at_spi_root_get_cache (GtkAtSpiRoot *self); + +GVariant * +gtk_at_spi_root_to_ref (GtkAtSpiRoot *self); + +G_END_DECLS diff --git a/gtk/a11y/gtkatspiselection.c b/gtk/a11y/gtkatspiselection.c new file mode 100644 index 0000000000..0260e5d56e --- /dev/null +++ b/gtk/a11y/gtkatspiselection.c @@ -0,0 +1,1060 @@ +/* gtkatspiselection.c: AT-SPI Selection implementation + * + * Copyright 2020 Red Hat, Inc. + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include "gtkatspiselectionprivate.h" + +#include "a11y/atspi/atspi-selection.h" + +#include "gtkatcontextprivate.h" +#include "gtkatspicontextprivate.h" +#include "gtkaccessibleprivate.h" +#include "gtkdebug.h" +#include "gtklistbase.h" +#include "gtklistbox.h" +#include "gtkflowbox.h" +#include "gtkcombobox.h" +#include "gtkstackswitcher.h" +#include "gtknotebook.h" +#include "gtklistview.h" +#include "gtkgridview.h" +#include "gtklistitem.h" +#include "gtkbitset.h" +#include "gtklistbaseprivate.h" +#include "gtklistitemwidgetprivate.h" + +#include <gio/gio.h> + +typedef struct { + int n; + GtkWidget *child; +} Counter; + +static void +find_nth (GtkWidget *box, + GtkWidget *child, + gpointer data) +{ + Counter *counter = data; + + if (counter->n == 0) + counter->child = child; + + counter->n--; +} + +static void +listbox_handle_method (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *method_name, + GVariant *parameters, + GDBusMethodInvocation *invocation, + gpointer user_data) +{ + GtkATContext *self = user_data; + GtkAccessible *accessible = gtk_at_context_get_accessible (self); + GtkWidget *widget = GTK_WIDGET (accessible); + + if (g_strcmp0 (method_name, "GetSelectedChild") == 0) + { + Counter counter; + int idx; + + g_variant_get (parameters, "(i)", &idx); + + counter.n = idx; + counter.child = NULL; + + gtk_list_box_selected_foreach (GTK_LIST_BOX (widget), (GtkListBoxForeachFunc)find_nth, &counter); + + if (counter.child == NULL) + g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "No selected child for %d", idx); + else + { + GtkATContext *ctx = gtk_accessible_get_at_context (GTK_ACCESSIBLE (counter.child)); + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(@(so))", gtk_at_spi_context_to_ref (GTK_AT_SPI_CONTEXT (ctx)))); + } + } + else if (g_strcmp0 (method_name, "SelectChild") == 0) + { + int idx; + GtkListBoxRow *row; + + g_variant_get (parameters, "(i)", &idx); + + row = gtk_list_box_get_row_at_index (GTK_LIST_BOX (widget), idx); + if (!row) + g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "No child at position %d", idx); + else + { + gboolean ret; + + gtk_list_box_select_row (GTK_LIST_BOX (widget), row); + ret = gtk_list_box_row_is_selected (row); + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret)); + } + } + else if (g_strcmp0 (method_name, "DeselectChild") == 0) + { + int idx; + GtkListBoxRow *row; + + g_variant_get (parameters, "(i)", &idx); + + row = gtk_list_box_get_row_at_index (GTK_LIST_BOX (widget), idx); + if (!row) + g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "No child at position %d", idx); + else + { + gboolean ret; + + gtk_list_box_unselect_row (GTK_LIST_BOX (widget), row); + ret = !gtk_list_box_row_is_selected (row); + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret)); + } + } + else if (g_strcmp0 (method_name, "DeselectSelectedChild") == 0) + { + Counter counter; + int idx; + + g_variant_get (parameters, "(i)", &idx); + + counter.n = idx; + counter.child = NULL; + + gtk_list_box_selected_foreach (GTK_LIST_BOX (widget), (GtkListBoxForeachFunc)find_nth, &counter); + + if (counter.child == NULL) + g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "No selected child for %d", idx); + else + { + gboolean ret; + + gtk_list_box_unselect_row (GTK_LIST_BOX (widget), GTK_LIST_BOX_ROW (counter.child)); + ret = !gtk_list_box_row_is_selected (GTK_LIST_BOX_ROW (counter.child)); + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret)); + } + } + else if (g_strcmp0 (method_name, "IsChildSelected") == 0) + { + int idx; + GtkListBoxRow *row; + + g_variant_get (parameters, "(i)", &idx); + + row = gtk_list_box_get_row_at_index (GTK_LIST_BOX (widget), idx); + if (!row) + g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "No child at position %d", idx); + else + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", gtk_list_box_row_is_selected (row))); + } + else if (g_strcmp0 (method_name, "SelectAll") == 0) + { + gtk_list_box_select_all (GTK_LIST_BOX (widget)); + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", TRUE)); + } + else if (g_strcmp0 (method_name, "ClearSelection") == 0) + { + gtk_list_box_unselect_all (GTK_LIST_BOX (widget)); + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", TRUE)); + } +} + +static void +count_selected (GtkWidget *box, + GtkWidget *child, + gpointer data) +{ + *(int *)data += 1; +} + +static GVariant * +listbox_get_property (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *property_name, + GError **error, + gpointer user_data) +{ + GtkATContext *self = GTK_AT_CONTEXT (user_data); + GtkAccessible *accessible = gtk_at_context_get_accessible (self); + GtkWidget *widget = GTK_WIDGET (accessible); + + if (g_strcmp0 (property_name, "NSelectedChildren") == 0) + { + int count = 0; + + gtk_list_box_selected_foreach (GTK_LIST_BOX (widget), (GtkListBoxForeachFunc)count_selected, &count); + + return g_variant_new_int32 (count); + } + + return NULL; +} + +static const GDBusInterfaceVTable listbox_vtable = { + listbox_handle_method, + listbox_get_property, + NULL +}; + + +static void +listview_handle_method (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *method_name, + GVariant *parameters, + GDBusMethodInvocation *invocation, + gpointer user_data) +{ + GtkATContext *self = user_data; + GtkAccessible *accessible = gtk_at_context_get_accessible (self); + GtkWidget *widget = GTK_WIDGET (accessible); + GtkSelectionModel *model = gtk_list_base_get_model (GTK_LIST_BASE (widget)); + + g_print ("list item %s %s\n", interface_name, method_name); + + if (g_strcmp0 (method_name, "GetSelectedChild") == 0) + { + int idx; + guint pos; + GtkBitset *set; + GtkWidget *child; + GtkListItem *item; + + g_variant_get (parameters, "(i)", &idx); + + set = gtk_selection_model_get_selection (model); + pos = gtk_bitset_get_nth (set, idx); + gtk_bitset_unref (set); + + for (child = gtk_widget_get_first_child (widget); + child; + child = gtk_widget_get_next_sibling (child)) + { + item = gtk_list_item_widget_get_list_item (GTK_LIST_ITEM_WIDGET (child)); + if (pos == gtk_list_item_get_position (item)) + break; + } + + if (child == NULL) + g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "No selected child for %d", idx); + else + { + GtkATContext *ctx = gtk_accessible_get_at_context (GTK_ACCESSIBLE (child)); + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(@(so))", gtk_at_spi_context_to_ref (GTK_AT_SPI_CONTEXT (ctx)))); + } + } + else if (g_strcmp0 (method_name, "SelectChild") == 0) + { + int idx; + gboolean ret; + + g_variant_get (parameters, "(i)", &idx); + + ret = gtk_selection_model_select_item (model, idx, FALSE); + + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret)); + } + else if (g_strcmp0 (method_name, "DeselectChild") == 0) + { + int idx; + gboolean ret; + + g_variant_get (parameters, "(i)", &idx); + + ret = gtk_selection_model_select_item (model, idx, FALSE); + + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret)); + } + else if (g_strcmp0 (method_name, "DeselectSelectedChild") == 0) + { + int idx; + guint pos; + GtkBitset *set; + gboolean ret; + + g_variant_get (parameters, "(i)", &idx); + + set = gtk_selection_model_get_selection (model); + pos = gtk_bitset_get_nth (set, idx); + gtk_bitset_unref (set); + + ret = gtk_selection_model_unselect_item (model, pos); + + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret)); + } + else if (g_strcmp0 (method_name, "IsChildSelected") == 0) + { + int idx; + gboolean ret; + + g_variant_get (parameters, "(i)", &idx); + + ret = gtk_selection_model_is_selected (model, idx); + + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret)); + } + else if (g_strcmp0 (method_name, "SelectAll") == 0) + { + gboolean ret; + + ret = gtk_selection_model_select_all (model); + + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret)); + } + else if (g_strcmp0 (method_name, "ClearSelection") == 0) + { + gboolean ret; + + ret = gtk_selection_model_unselect_all (model); + + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret)); + } +} + +static GVariant * +listview_get_property (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *property_name, + GError **error, + gpointer user_data) +{ + GtkATContext *self = GTK_AT_CONTEXT (user_data); + GtkAccessible *accessible = gtk_at_context_get_accessible (self); + GtkWidget *widget = GTK_WIDGET (accessible); + GtkSelectionModel *model = gtk_list_base_get_model (GTK_LIST_BASE (widget)); + + if (g_strcmp0 (property_name, "NSelectedChildren") == 0) + { + int count = 0; + GtkBitset *set; + + set = gtk_selection_model_get_selection (model); + count = gtk_bitset_get_size (set); + gtk_bitset_unref (set); + + return g_variant_new_int32 (count); + } + + return NULL; +} + +static const GDBusInterfaceVTable listview_vtable = { + listview_handle_method, + listview_get_property, + NULL +}; + + +static void +flowbox_handle_method (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *method_name, + GVariant *parameters, + GDBusMethodInvocation *invocation, + gpointer user_data) +{ + GtkATContext *self = user_data; + GtkAccessible *accessible = gtk_at_context_get_accessible (self); + GtkWidget *widget = GTK_WIDGET (accessible); + + if (g_strcmp0 (method_name, "GetSelectedChild") == 0) + { + Counter counter; + int idx; + + g_variant_get (parameters, "(i)", &idx); + + counter.n = idx; + counter.child = NULL; + + gtk_flow_box_selected_foreach (GTK_FLOW_BOX (widget), (GtkFlowBoxForeachFunc)find_nth, &counter); + + if (counter.child == NULL) + g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "No selected child for %d", idx); + else + { + GtkATContext *ctx = gtk_accessible_get_at_context (GTK_ACCESSIBLE (counter.child)); + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(@(so))", gtk_at_spi_context_to_ref (GTK_AT_SPI_CONTEXT (ctx)))); + } + } + else if (g_strcmp0 (method_name, "SelectChild") == 0) + { + int idx; + GtkFlowBoxChild *child; + + g_variant_get (parameters, "(i)", &idx); + + child = gtk_flow_box_get_child_at_index (GTK_FLOW_BOX (widget), idx); + if (!child) + g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "No child at position %d", idx); + else + { + gboolean ret; + + gtk_flow_box_select_child (GTK_FLOW_BOX (widget), child); + ret = gtk_flow_box_child_is_selected (child); + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret)); + } + } + else if (g_strcmp0 (method_name, "DeselectChild") == 0) + { + int idx; + GtkFlowBoxChild *child; + + g_variant_get (parameters, "(i)", &idx); + + child = gtk_flow_box_get_child_at_index (GTK_FLOW_BOX (widget), idx); + if (!child) + g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "No child at position %d", idx); + else + { + gboolean ret; + + gtk_flow_box_unselect_child (GTK_FLOW_BOX (widget), child); + ret = !gtk_flow_box_child_is_selected (child); + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret)); + } + } + else if (g_strcmp0 (method_name, "DeselectSelectedChild") == 0) + { + Counter counter; + int idx; + + g_variant_get (parameters, "(i)", &idx); + + counter.n = idx; + counter.child = NULL; + + gtk_flow_box_selected_foreach (GTK_FLOW_BOX (widget), (GtkFlowBoxForeachFunc)find_nth, &counter); + + if (counter.child == NULL) + g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "No selected child for %d", idx); + else + { + gboolean ret; + + gtk_flow_box_unselect_child (GTK_FLOW_BOX (widget), GTK_FLOW_BOX_CHILD (counter.child)); + ret = !gtk_flow_box_child_is_selected (GTK_FLOW_BOX_CHILD (counter.child)); + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret)); + } + } + else if (g_strcmp0 (method_name, "IsChildSelected") == 0) + { + int idx; + GtkFlowBoxChild *child; + + g_variant_get (parameters, "(i)", &idx); + + child = gtk_flow_box_get_child_at_index (GTK_FLOW_BOX (widget), idx); + if (!child) + g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "No child at position %d", idx); + else + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", gtk_flow_box_child_is_selected (child))); + } + else if (g_strcmp0 (method_name, "SelectAll") == 0) + { + gtk_flow_box_select_all (GTK_FLOW_BOX (widget)); + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", TRUE)); + } + else if (g_strcmp0 (method_name, "ClearSelection") == 0) + { + gtk_flow_box_unselect_all (GTK_FLOW_BOX (widget)); + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", TRUE)); + } +} + +static GVariant * +flowbox_get_property (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *property_name, + GError **error, + gpointer user_data) +{ + GtkATContext *self = GTK_AT_CONTEXT (user_data); + GtkAccessible *accessible = gtk_at_context_get_accessible (self); + GtkWidget *widget = GTK_WIDGET (accessible); + + if (g_strcmp0 (property_name, "NSelectedChildren") == 0) + { + int count = 0; + + gtk_flow_box_selected_foreach (GTK_FLOW_BOX (widget), (GtkFlowBoxForeachFunc)count_selected, &count); + + return g_variant_new_int32 (count); + } + + return NULL; +} + +static const GDBusInterfaceVTable flowbox_vtable = { + flowbox_handle_method, + flowbox_get_property, + NULL +}; + + +static void +combobox_handle_method (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *method_name, + GVariant *parameters, + GDBusMethodInvocation *invocation, + gpointer user_data) +{ + GtkATContext *self = user_data; + GtkAccessible *accessible = gtk_at_context_get_accessible (self); + GtkWidget *widget = GTK_WIDGET (accessible); + + if (g_strcmp0 (method_name, "GetSelectedChild") == 0) + { + /* Need to figure out what to do here */ + g_dbus_method_invocation_return_error_literal (invocation, G_DBUS_ERROR, G_DBUS_ERROR_NOT_SUPPORTED, ""); + } + else if (g_strcmp0 (method_name, "SelectChild") == 0) + { + int idx; + + g_variant_get (parameters, "(i)", &idx); + gtk_combo_box_set_active (GTK_COMBO_BOX (widget), idx); + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", TRUE)); + } + else if (g_strcmp0 (method_name, "DeselectChild") == 0) + { + int idx; + + g_variant_get (parameters, "(i)", &idx); + + gtk_combo_box_set_active (GTK_COMBO_BOX (widget), -1); + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", TRUE)); + } + else if (g_strcmp0 (method_name, "DeselectSelectedChild") == 0) + { + int idx; + + g_variant_get (parameters, "(i)", &idx); + if (idx == 0) + gtk_combo_box_set_active (GTK_COMBO_BOX (widget), -1); + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", idx == 0)); + } + else if (g_strcmp0 (method_name, "IsChildSelected") == 0) + { + int idx; + gboolean active; + + g_variant_get (parameters, "(i)", &idx); + active = idx = gtk_combo_box_get_active (GTK_COMBO_BOX (widget)); + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", active)); + } + else if (g_strcmp0 (method_name, "SelectAll") == 0) + { + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", FALSE)); + } + else if (g_strcmp0 (method_name, "ClearSelection") == 0) + { + gtk_combo_box_set_active (GTK_COMBO_BOX (widget), -1); + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", TRUE)); + } +} + +static GVariant * +combobox_get_property (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *property_name, + GError **error, + gpointer user_data) +{ + GtkATContext *self = GTK_AT_CONTEXT (user_data); + GtkAccessible *accessible = gtk_at_context_get_accessible (self); + GtkWidget *widget = GTK_WIDGET (accessible); + + if (g_strcmp0 (property_name, "NSelectedChildren") == 0) + { + if (gtk_combo_box_get_active (GTK_COMBO_BOX (widget))) + return g_variant_new_int32 (1); + else + return g_variant_new_int32 (0); + } + + return NULL; +} + +static const GDBusInterfaceVTable combobox_vtable = { + combobox_handle_method, + combobox_get_property, + NULL +}; + + +static void +stackswitcher_handle_method (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *method_name, + GVariant *parameters, + GDBusMethodInvocation *invocation, + gpointer user_data) +{ + GtkATContext *self = user_data; + GtkAccessible *accessible = gtk_at_context_get_accessible (self); + GtkWidget *widget = GTK_WIDGET (accessible); + GtkStack *stack = gtk_stack_switcher_get_stack (GTK_STACK_SWITCHER (widget)); + + if (g_strcmp0 (method_name, "GetSelectedChild") == 0) + { + guint i, n; + GtkSelectionModel *pages; + GtkWidget *child; + + pages = gtk_stack_get_pages (stack); + n = g_list_model_get_n_items (G_LIST_MODEL (pages)); + for (i = 0, child = gtk_widget_get_first_child (widget); + i < n && child; + i++, child = gtk_widget_get_next_sibling (child)) + { + if (gtk_selection_model_is_selected (pages, i)) + break; + } + g_object_unref (pages); + + if (child == NULL) + g_dbus_method_invocation_return_error_literal (invocation, + G_DBUS_ERROR, + G_DBUS_ERROR_INVALID_ARGS, + "No selected child"); + else + { + GtkATContext *ctx = gtk_accessible_get_at_context (GTK_ACCESSIBLE (child)); + g_dbus_method_invocation_return_value (invocation, + g_variant_new ("(@(so))", gtk_at_spi_context_to_ref (GTK_AT_SPI_CONTEXT (ctx)))); + } + } + else if (g_strcmp0 (method_name, "SelectChild") == 0) + { + int idx; + GtkSelectionModel *pages; + + g_variant_get (parameters, "(i)", &idx); + + pages = gtk_stack_get_pages (stack); + gtk_selection_model_select_item (pages, idx, TRUE); + g_object_unref (pages); + + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", TRUE)); + } + else if (g_strcmp0 (method_name, "DeselectChild") == 0) + { + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", FALSE)); + } + else if (g_strcmp0 (method_name, "DeselectSelectedChild") == 0) + { + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", FALSE)); + } + else if (g_strcmp0 (method_name, "IsChildSelected") == 0) + { + int idx; + GtkSelectionModel *pages; + gboolean active; + + g_variant_get (parameters, "(i)", &idx); + + pages = gtk_stack_get_pages (stack); + active = gtk_selection_model_is_selected (pages, idx); + g_object_unref (pages); + + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", active)); + } + else if (g_strcmp0 (method_name, "SelectAll") == 0) + { + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", FALSE)); + } + else if (g_strcmp0 (method_name, "ClearSelection") == 0) + { + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", FALSE)); + } +} + +static GVariant * +stackswitcher_get_property (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *property_name, + GError **error, + gpointer user_data) +{ + GtkATContext *self = GTK_AT_CONTEXT (user_data); + GtkAccessible *accessible = gtk_at_context_get_accessible (self); + GtkWidget *widget = GTK_WIDGET (accessible); + + if (g_strcmp0 (property_name, "NSelectedChildren") == 0) + { + GtkStack *stack = gtk_stack_switcher_get_stack (GTK_STACK_SWITCHER (widget)); + + if (stack == NULL || gtk_stack_get_visible_child (stack) == NULL) + return g_variant_new_int32 (0); + else + return g_variant_new_int32 (1); + } + + return NULL; +} + +static const GDBusInterfaceVTable stackswitcher_vtable = { + stackswitcher_handle_method, + stackswitcher_get_property, + NULL +}; + + +static void +notebook_handle_method (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *method_name, + GVariant *parameters, + GDBusMethodInvocation *invocation, + gpointer user_data) +{ + GtkATContext *self = user_data; + GtkAccessible *accessible = gtk_at_context_get_accessible (self); + GtkWidget *widget = GTK_WIDGET (accessible); + GtkWidget *notebook = gtk_widget_get_parent (gtk_widget_get_parent (widget)); + + g_print ("notebook %s %s\n", interface_name, method_name); + + if (g_strcmp0 (method_name, "GetSelectedChild") == 0) + { + int i; + GtkWidget *child; + + i = gtk_notebook_get_current_page (GTK_NOTEBOOK (notebook)); + + for (child = gtk_widget_get_first_child (widget); + child; + child = gtk_widget_get_next_sibling (widget)) + { + /* skip actions */ + if (gtk_accessible_get_accessible_role (GTK_ACCESSIBLE (child)) != GTK_ACCESSIBLE_ROLE_TAB) + continue; + + if (i == 0) + break; + + i--; + } + + if (child == NULL) + { + g_dbus_method_invocation_return_error_literal (invocation, + G_DBUS_ERROR, + G_DBUS_ERROR_INVALID_ARGS, + "No selected child"); + } + else + { + GtkATContext *ctx = gtk_accessible_get_at_context (GTK_ACCESSIBLE (child)); + g_dbus_method_invocation_return_value (invocation, + g_variant_new ("(@(so))", gtk_at_spi_context_to_ref (GTK_AT_SPI_CONTEXT (ctx)))); + } + } + else if (g_strcmp0 (method_name, "SelectChild") == 0) + { + int i; + GtkWidget *child; + + g_variant_get (parameters, "(i)", &i); + + /* skip an action widget */ + child = gtk_widget_get_first_child (widget); + if (gtk_accessible_get_accessible_role (GTK_ACCESSIBLE (child)) != GTK_ACCESSIBLE_ROLE_TAB) + i--; + + gtk_notebook_set_current_page (GTK_NOTEBOOK (notebook), i); + + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", TRUE)); + } + else if (g_strcmp0 (method_name, "DeselectChild") == 0) + { + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", FALSE)); + } + else if (g_strcmp0 (method_name, "DeselectSelectedChild") == 0) + { + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", FALSE)); + } + else if (g_strcmp0 (method_name, "IsChildSelected") == 0) + { + int i; + gboolean active; + GtkWidget *child; + + g_variant_get (parameters, "(i)", &i); + + /* skip an action widget */ + child = gtk_widget_get_first_child (widget); + if (gtk_accessible_get_accessible_role (GTK_ACCESSIBLE (child)) != GTK_ACCESSIBLE_ROLE_TAB) + i--; + + active = i == gtk_notebook_get_current_page (GTK_NOTEBOOK (notebook)); + + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", active)); + } + else if (g_strcmp0 (method_name, "SelectAll") == 0) + { + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", FALSE)); + } + else if (g_strcmp0 (method_name, "ClearSelection") == 0) + { + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", FALSE)); + } +} + +static GVariant * +notebook_get_property (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *property_name, + GError **error, + gpointer user_data) +{ + if (g_strcmp0 (property_name, "NSelectedChildren") == 0) + { + return g_variant_new_int32 (1); + } + + return NULL; +} + +static const GDBusInterfaceVTable notebook_vtable = { + notebook_handle_method, + notebook_get_property, + NULL +}; + +#define IS_NOTEBOOK_TAB_LIST(s,r) \ + ((r == GTK_ACCESSIBLE_ROLE_TAB_LIST) && \ + (gtk_widget_get_parent (GTK_WIDGET (s)) != NULL) && \ + GTK_IS_NOTEBOOK (gtk_widget_get_parent (gtk_widget_get_parent (GTK_WIDGET (s))))) + +const GDBusInterfaceVTable * +gtk_atspi_get_selection_vtable (GtkAccessible *accessible, + GtkAccessibleRole role) +{ + if (GTK_IS_LIST_BOX (accessible)) + return &listbox_vtable; + else if (GTK_IS_LIST_VIEW (accessible) || + GTK_IS_GRID_VIEW (accessible)) + { + g_print ("using listview vtable\n"); + return &listview_vtable; + } + else if (GTK_IS_FLOW_BOX (accessible)) + return &flowbox_vtable; + else if (GTK_IS_COMBO_BOX (accessible)) + return &combobox_vtable; + else if (GTK_IS_STACK_SWITCHER (accessible)) + return &stackswitcher_vtable; + else if (IS_NOTEBOOK_TAB_LIST (accessible, role)) + return ¬ebook_vtable; + + return NULL; +} + +typedef struct { + GtkAtspiSelectionCallback *changed; + gpointer data; +} SelectionChanged; + +typedef struct { + GtkSelectionModel *model; + GtkAtspiSelectionCallback *changed; + gpointer data; +} ListViewData; + +static void +update_model (ListViewData *data, + GtkSelectionModel *model) +{ + if (data->model) + g_signal_handlers_disconnect_by_func (data->model, data->changed, data->data); + + g_set_object (&data->model, model); + + if (data->model) + g_signal_connect_swapped (data->model, "selection-changed", G_CALLBACK (data->changed), data->data); +} + +static void +list_view_data_free (gpointer user_data) +{ + ListViewData *data = user_data; + update_model (data, NULL); + g_free (data); +} + +static void +model_changed (GtkListBase *list, + GParamSpec *pspec, + gpointer unused) +{ + ListViewData *data; + + data = (ListViewData *)g_object_get_data (G_OBJECT (list), "accessible-selection-data"); + update_model (data, gtk_list_base_get_model (list)); +} + +void +gtk_atspi_connect_selection_signals (GtkAccessible *accessible, + GtkAtspiSelectionCallback selection_changed, + gpointer data) +{ + if (GTK_IS_LIST_BOX (accessible)) + { + SelectionChanged *changed; + + changed = g_new (SelectionChanged, 1); + changed->changed = selection_changed; + changed->data = data; + + g_object_set_data_full (G_OBJECT (accessible), "accessible-selection-data", changed, g_free); + + g_signal_connect_swapped (accessible, "selected-rows-changed", G_CALLBACK (selection_changed), data); + } + else if (GTK_IS_FLOW_BOX (accessible)) + { + SelectionChanged *changed; + + changed = g_new (SelectionChanged, 1); + changed->changed = selection_changed; + changed->data = data; + + g_object_set_data_full (G_OBJECT (accessible), "accessible-selection-data", changed, g_free); + + g_signal_connect_swapped (accessible, "selected-children-changed", G_CALLBACK (selection_changed), data); + } + else if (GTK_IS_COMBO_BOX (accessible)) + { + SelectionChanged *changed; + + changed = g_new (SelectionChanged, 1); + changed->changed = selection_changed; + changed->data = data; + + g_object_set_data_full (G_OBJECT (accessible), "accessible-selection-data", changed, g_free); + + g_signal_connect_swapped (accessible, "changed", G_CALLBACK (selection_changed), data); + } + else if (GTK_IS_STACK_SWITCHER (accessible)) + { + SelectionChanged *changed; + + changed = g_new (SelectionChanged, 1); + changed->changed = selection_changed; + changed->data = data; + + g_object_set_data_full (G_OBJECT (accessible), "accessible-selection-data", changed, g_free); + + g_signal_connect_swapped (accessible, "notify::visible-child", G_CALLBACK (selection_changed), data); + } + else if (IS_NOTEBOOK_TAB_LIST (accessible, GTK_AT_CONTEXT (data)->accessible_role)) + { + GtkWidget *notebook = gtk_widget_get_parent (gtk_widget_get_parent (GTK_WIDGET (accessible))); + SelectionChanged *changed; + + changed = g_new (SelectionChanged, 1); + changed->changed = selection_changed; + changed->data = data; + + g_object_set_data_full (G_OBJECT (accessible), "accessible-selection-data", changed, g_free); + + g_signal_connect_swapped (notebook, "notify::page", G_CALLBACK (selection_changed), data); + } + else if (GTK_IS_LIST_VIEW (accessible) || + GTK_IS_GRID_VIEW (accessible)) + { + ListViewData *changed; + + changed = g_new0 (ListViewData, 1); + changed->changed = selection_changed; + changed->data = data; + + g_object_set_data_full (G_OBJECT (accessible), "accessible-selection-data", changed, list_view_data_free); + + g_signal_connect (accessible, "notify::model", G_CALLBACK (model_changed), NULL); + model_changed (GTK_LIST_BASE (accessible), NULL, changed); + } +} + +void +gtk_atspi_disconnect_selection_signals (GtkAccessible *accessible) +{ + if (GTK_IS_LIST_BOX (accessible) || + GTK_IS_FLOW_BOX (accessible) || + GTK_IS_COMBO_BOX (accessible) || + GTK_IS_STACK_SWITCHER (accessible)) + { + SelectionChanged *changed; + + changed = g_object_get_data (G_OBJECT (accessible), "accessible-selection-data"); + + g_signal_handlers_disconnect_by_func (accessible, changed->changed, changed->data); + + g_object_set_data (G_OBJECT (accessible), "accessible-selection-data", NULL); + } + else if (IS_NOTEBOOK_TAB_LIST (accessible, gtk_accessible_get_accessible_role (accessible))) + { + GtkWidget *notebook = gtk_widget_get_parent (gtk_widget_get_parent (GTK_WIDGET (accessible))); + SelectionChanged *changed; + + changed = g_object_get_data (G_OBJECT (accessible), "accessible-selection-data"); + + g_signal_handlers_disconnect_by_func (notebook, changed->changed, changed->data); + + g_object_set_data (G_OBJECT (accessible), "accessible-selection-data", NULL); + } + else if (GTK_IS_LIST_VIEW (accessible) || + GTK_IS_GRID_VIEW (accessible)) + { + g_signal_handlers_disconnect_by_func (accessible, model_changed, NULL); + + g_object_set_data (G_OBJECT (accessible), "accessible-selection-data", NULL); + } +} diff --git a/gtk/a11y/gtkatspiselectionprivate.h b/gtk/a11y/gtkatspiselectionprivate.h new file mode 100644 index 0000000000..2a9da6738e --- /dev/null +++ b/gtk/a11y/gtkatspiselectionprivate.h @@ -0,0 +1,39 @@ +/* gtkatspiselectionprivate.h: AT-SPI Selection implementation + * + * Copyright 2020 Red Hat, Inc. + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <gio/gio.h> +#include "gtkaccessible.h" + +G_BEGIN_DECLS + +const GDBusInterfaceVTable *gtk_atspi_get_selection_vtable (GtkAccessible *accessible, + GtkAccessibleRole role); + +typedef void (GtkAtspiSelectionCallback) (gpointer data); + +void gtk_atspi_connect_selection_signals (GtkAccessible *accessible, + GtkAtspiSelectionCallback selection_changed, + gpointer data); +void gtk_atspi_disconnect_selection_signals (GtkAccessible *accessible); + + +G_END_DECLS diff --git a/gtk/a11y/gtkatspitext.c b/gtk/a11y/gtkatspitext.c new file mode 100644 index 0000000000..fe7741a39b --- /dev/null +++ b/gtk/a11y/gtkatspitext.c @@ -0,0 +1,1452 @@ +/* gtkatspitext.c: Text interface for GtkAtspiContext + * + * Copyright 2020 Red Hat, Inc + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include "gtkatspitextprivate.h" + +#include "gtkatspiprivate.h" +#include "gtkatspiutilsprivate.h" +#include "gtkatspipangoprivate.h" +#include "gtkatspitextbufferprivate.h" + +#include "a11y/atspi/atspi-text.h" + +#include "gtkatcontextprivate.h" +#include "gtkdebug.h" +#include "gtkeditable.h" +#include "gtklabelprivate.h" +#include "gtkentryprivate.h" +#include "gtksearchentryprivate.h" +#include "gtkpasswordentryprivate.h" +#include "gtkspinbuttonprivate.h" +#include "gtktextview.h" + +#include <gio/gio.h> + +static void +label_handle_method (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *method_name, + GVariant *parameters, + GDBusMethodInvocation *invocation, + gpointer user_data) +{ + GtkATContext *self = user_data; + GtkAccessible *accessible = gtk_at_context_get_accessible (self); + GtkWidget *widget = GTK_WIDGET (accessible); + + if (g_strcmp0 (method_name, "GetCaretOffset") == 0) + { + int offset; + + offset = _gtk_label_get_cursor_position (GTK_LABEL (widget)); + + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(i)", offset)); + } + else if (g_strcmp0 (method_name, "SetCaretOffset") == 0) + { + int offset; + gboolean ret; + + g_variant_get (parameters, "(i)", &offset); + + ret = gtk_label_get_selectable (GTK_LABEL (widget)); + if (ret) + gtk_label_select_region (GTK_LABEL (widget), offset, offset); + + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret)); + } + else if (g_strcmp0 (method_name, "GetText") == 0) + { + int start, end; + const char *text; + int len; + char *string; + + g_variant_get (parameters, "(ii)", &start, &end); + + text = gtk_label_get_text (GTK_LABEL (widget)); + len = g_utf8_strlen (text, -1); + + start = CLAMP (start, 0, len); + end = CLAMP (end, 0, len); + + if (end <= start) + string = g_strdup (""); + else + { + const char *p, *q; + p = g_utf8_offset_to_pointer (text, start); + q = g_utf8_offset_to_pointer (text, end); + string = g_strndup (p, q - p); + } + + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", string)); + g_free (string); + } + else if (g_strcmp0 (method_name, "GetTextBeforeOffset") == 0) + { + PangoLayout *layout = gtk_label_get_layout (GTK_LABEL (widget)); + int offset; + AtspiTextBoundaryType boundary_type; + char *string; + int start, end; + + g_variant_get (parameters, "(iu)", &offset, &boundary_type); + + string = gtk_pango_get_text_before (layout, offset, boundary_type, &start, &end); + + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(sii)", string, start, end)); + g_free (string); + } + else if (g_strcmp0 (method_name, "GetTextAtOffset") == 0) + { + PangoLayout *layout = gtk_label_get_layout (GTK_LABEL (widget)); + int offset; + AtspiTextBoundaryType boundary_type; + char *string; + int start, end; + + g_variant_get (parameters, "(iu)", &offset, &boundary_type); + + string = gtk_pango_get_text_at (layout, offset, boundary_type, &start, &end); + + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(sii)", string, start, end)); + g_free (string); + } + else if (g_strcmp0 (method_name, "GetTextAfterOffset") == 0) + { + PangoLayout *layout = gtk_label_get_layout (GTK_LABEL (widget)); + int offset; + AtspiTextBoundaryType boundary_type; + char *string; + int start, end; + + g_variant_get (parameters, "(iu)", &offset, &boundary_type); + + string = gtk_pango_get_text_after (layout, offset, boundary_type, &start, &end); + + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(sii)", string, start, end)); + g_free (string); + } + else if (g_strcmp0 (method_name, "GetCharacterAtOffset") == 0) + { + int offset; + const char *text; + gunichar ch = 0; + + g_variant_get (parameters, "(i)", &offset); + + text = gtk_label_get_text (GTK_LABEL (widget)); + + if (0 <= offset && offset < g_utf8_strlen (text, -1)) + ch = g_utf8_get_char (g_utf8_offset_to_pointer (text, offset)); + + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(i)", ch)); + } + else if (g_strcmp0 (method_name, "GetStringAtOffset") == 0) + { + PangoLayout *layout = gtk_label_get_layout (GTK_LABEL (widget)); + int offset; + AtspiTextGranularity granularity; + char *string; + int start, end; + + g_variant_get (parameters, "(iu)", &offset, &granularity); + + string = gtk_pango_get_string_at (layout, offset, granularity, &start, &end); + + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(sii)", string, start, end)); + g_free (string); + } + else if (g_strcmp0 (method_name, "GetAttributes") == 0) + { + PangoLayout *layout = gtk_label_get_layout (GTK_LABEL (widget)); + GVariantBuilder builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("a{ss}")); + int offset; + int start, end; + + g_variant_get (parameters, "(i)", &offset); + + gtk_pango_get_run_attributes (layout, &builder, offset, &start, &end); + + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(a{ss}ii)", &builder, start, end)); + } + else if (g_strcmp0 (method_name, "GetAttributeValue") == 0) + { + PangoLayout *layout = gtk_label_get_layout (GTK_LABEL (widget)); + GVariantBuilder builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("a{ss}")); + int offset; + const char *name; + int start, end; + GVariant *attrs; + const char *val; + + g_variant_get (parameters, "(i&s)", &offset, &name); + + gtk_pango_get_run_attributes (layout, &builder, offset, &start, &end); + + attrs = g_variant_builder_end (&builder); + if (!g_variant_lookup (attrs, name, "&s", &val)) + val = ""; + + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", val)); + g_variant_unref (attrs); + } + else if (g_strcmp0 (method_name, "GetAttributeRun") == 0) + { + PangoLayout *layout = gtk_label_get_layout (GTK_LABEL (widget)); + GVariantBuilder builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("a{ss}")); + int offset; + gboolean include_defaults; + int start, end; + + g_variant_get (parameters, "(ib)", &offset, &include_defaults); + + if (include_defaults) + gtk_pango_get_default_attributes (layout, &builder); + + gtk_pango_get_run_attributes (layout, &builder, offset, &start, &end); + + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(a{ss}ii)", &builder, start, end)); + } + else if (g_strcmp0 (method_name, "GetDefaultAttributes") == 0 || + g_strcmp0 (method_name, "GetDefaultAttributeSet") == 0) + { + PangoLayout *layout = gtk_label_get_layout (GTK_LABEL (widget)); + GVariantBuilder builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("a{ss}")); + + gtk_pango_get_default_attributes (layout, &builder); + + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(a{ss})", &builder)); + } + else if (g_strcmp0 (method_name, "GetNSelections") == 0) + { + int n = 0; + + if (gtk_label_get_selection_bounds (GTK_LABEL (widget), NULL, NULL)) + n = 1; + + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(i)", n)); + } + else if (g_strcmp0 (method_name, "GetSelection") == 0) + { + int num; + int start, end; + gboolean ret = TRUE; + + g_variant_get (parameters, "(i)", &num); + + if (num != 0) + ret = FALSE; + else + { + if (!gtk_label_get_selection_bounds (GTK_LABEL (widget), &start, &end)) + ret = FALSE; + } + + if (!ret) + g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "Not a valid selection: %d", num); + else + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(ii)", start, end)); + } + else if (g_strcmp0 (method_name, "AddSelection") == 0) + { + int start, end; + gboolean ret; + + g_variant_get (parameters, "(ii)", &start, &end); + + if (!gtk_label_get_selectable (GTK_LABEL (widget)) || + gtk_label_get_selection_bounds (GTK_LABEL (widget), NULL, NULL)) + { + ret = FALSE; + } + else + { + gtk_label_select_region (GTK_LABEL (widget), start, end); + ret = TRUE; + } + + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret)); + } + else if (g_strcmp0 (method_name, "RemoveSelection") == 0) + { + int num; + int start, end; + gboolean ret; + + g_variant_get (parameters, "(i)", &num); + + if (num != 0) + ret = FALSE; + else + { + if (!gtk_label_get_selectable (GTK_LABEL (widget)) || + !gtk_label_get_selection_bounds (GTK_LABEL (widget), &start, &end)) + { + ret = FALSE; + } + else + { + gtk_label_select_region (GTK_LABEL (widget), end, end); + ret = TRUE; + } + } + + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret)); + } + else if (g_strcmp0 (method_name, "SetSelection") == 0) + { + int num; + int start, end; + gboolean ret; + + g_variant_get (parameters, "(iii)", &num, &start, &end); + + if (num != 0) + ret = FALSE; + else + { + if (!gtk_label_get_selectable (GTK_LABEL (widget)) || + !gtk_label_get_selection_bounds (GTK_LABEL (widget), NULL, NULL)) + { + ret = FALSE; + } + else + { + gtk_label_select_region (GTK_LABEL (widget), start, end); + ret = TRUE; + } + } + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret)); + } + else if (g_strcmp0 (method_name, "GetCharacterExtents") == 0) + { + g_dbus_method_invocation_return_error_literal (invocation, G_DBUS_ERROR, G_DBUS_ERROR_NOT_SUPPORTED, ""); + } + else if (g_strcmp0 (method_name, "GetRangeExtents") == 0) + { + g_dbus_method_invocation_return_error_literal (invocation, G_DBUS_ERROR, G_DBUS_ERROR_NOT_SUPPORTED, ""); + } + else if (g_strcmp0 (method_name, "GetBoundedRanges") == 0) + { + g_dbus_method_invocation_return_error_literal (invocation, G_DBUS_ERROR, G_DBUS_ERROR_NOT_SUPPORTED, ""); + } + else if (g_strcmp0 (method_name, "ScrollSubstringTo") == 0) + { + g_dbus_method_invocation_return_error_literal (invocation, G_DBUS_ERROR, G_DBUS_ERROR_NOT_SUPPORTED, ""); + } + else if (g_strcmp0 (method_name, "ScrollSubstringToPoint") == 0) + { + g_dbus_method_invocation_return_error_literal (invocation, G_DBUS_ERROR, G_DBUS_ERROR_NOT_SUPPORTED, ""); + } +} + +static GVariant * +label_get_property (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *property_name, + GError **error, + gpointer user_data) +{ + GtkATContext *self = user_data; + GtkAccessible *accessible = gtk_at_context_get_accessible (self); + GtkWidget *widget = GTK_WIDGET (accessible); + + if (g_strcmp0 (property_name, "CharacterCount") == 0) + { + const char *text; + int len; + + text = gtk_label_get_text (GTK_LABEL (widget)); + len = g_utf8_strlen (text, -1); + + return g_variant_new_int32 (len); + } + else if (g_strcmp0 (property_name, "CaretOffset") == 0) + { + int offset; + + offset = _gtk_label_get_cursor_position (GTK_LABEL (widget)); + + return g_variant_new_int32 (offset); + } + + return NULL; +} + +static const GDBusInterfaceVTable label_vtable = { + label_handle_method, + label_get_property, + NULL, +}; + +static GtkText * +gtk_editable_get_text_widget (GtkWidget *widget) +{ + if (GTK_IS_ENTRY (widget)) + return gtk_entry_get_text_widget (GTK_ENTRY (widget)); + else if (GTK_IS_SEARCH_ENTRY (widget)) + return gtk_search_entry_get_text_widget (GTK_SEARCH_ENTRY (widget)); + else if (GTK_IS_PASSWORD_ENTRY (widget)) + return gtk_password_entry_get_text_widget (GTK_PASSWORD_ENTRY (widget)); + else if (GTK_IS_SPIN_BUTTON (widget)) + return gtk_spin_button_get_text_widget (GTK_SPIN_BUTTON (widget)); + + return NULL; +} + +static void +entry_handle_method (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *method_name, + GVariant *parameters, + GDBusMethodInvocation *invocation, + gpointer user_data) +{ + GtkATContext *self = user_data; + GtkAccessible *accessible = gtk_at_context_get_accessible (self); + GtkWidget *widget = GTK_WIDGET (accessible); + GtkText *text_widget = gtk_editable_get_text_widget (widget); + + if (g_strcmp0 (method_name, "GetCaretOffset") == 0) + { + int offset; + + offset = gtk_editable_get_position (GTK_EDITABLE (widget)); + + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(i)", offset)); + } + else if (g_strcmp0 (method_name, "SetCaretOffset") == 0) + { + int offset; + gboolean ret; + + g_variant_get (parameters, "(i)", &offset); + + gtk_editable_set_position (GTK_EDITABLE (widget), offset); + ret = TRUE; + + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret)); + } + else if (g_strcmp0 (method_name, "GetText") == 0) + { + int start, end; + const char *text; + int len; + char *string; + + g_variant_get (parameters, "(ii)", &start, &end); + + text = gtk_editable_get_text (GTK_EDITABLE (widget)); + len = g_utf8_strlen (text, -1); + + start = CLAMP (start, 0, len); + end = CLAMP (end, 0, len); + + if (end <= start) + string = g_strdup (""); + else + { + const char *p, *q; + p = g_utf8_offset_to_pointer (text, start); + q = g_utf8_offset_to_pointer (text, end); + string = g_strndup (p, q - p); + } + + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", string)); + g_free (string); + } + else if (g_strcmp0 (method_name, "GetTextBeforeOffset") == 0) + { + PangoLayout *layout = gtk_text_get_layout (text_widget); + int offset; + AtspiTextBoundaryType boundary_type; + char *string; + int start, end; + + g_variant_get (parameters, "(iu)", &offset, &boundary_type); + + string = gtk_pango_get_text_before (layout, offset, boundary_type, &start, &end); + + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(sii)", string, start, end)); + g_free (string); + } + else if (g_strcmp0 (method_name, "GetTextAtOffset") == 0) + { + PangoLayout *layout = gtk_text_get_layout (text_widget); + int offset; + AtspiTextBoundaryType boundary_type; + char *string; + int start, end; + + g_variant_get (parameters, "(iu)", &offset, &boundary_type); + + string = gtk_pango_get_text_at (layout, offset, boundary_type, &start, &end); + + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(sii)", string, start, end)); + g_free (string); + } + else if (g_strcmp0 (method_name, "GetTextAfterOffset") == 0) + { + PangoLayout *layout = gtk_text_get_layout (text_widget); + int offset; + AtspiTextBoundaryType boundary_type; + char *string; + int start, end; + + g_variant_get (parameters, "(iu)", &offset, &boundary_type); + + string = gtk_pango_get_text_after (layout, offset, boundary_type, &start, &end); + + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(sii)", string, start, end)); + g_free (string); + } + else if (g_strcmp0 (method_name, "GetCharacterAtOffset") == 0) + { + int offset; + const char *text; + gunichar ch = 0; + + g_variant_get (parameters, "(i)", &offset); + + text = gtk_editable_get_text (GTK_EDITABLE (widget)); + if (0 <= offset && offset < g_utf8_strlen (text, -1)) + ch = g_utf8_get_char (g_utf8_offset_to_pointer (text, offset)); + + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(i)", ch)); + } + else if (g_strcmp0 (method_name, "GetStringAtOffset") == 0) + { + PangoLayout *layout = gtk_text_get_layout (text_widget); + int offset; + AtspiTextGranularity granularity; + char *string; + int start, end; + + g_variant_get (parameters, "(iu)", &offset, &granularity); + + string = gtk_pango_get_string_at (layout, offset, granularity, &start, &end); + + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(sii)", string, start, end)); + g_free (string); + } + else if (g_strcmp0 (method_name, "GetAttributes") == 0) + { + PangoLayout *layout = gtk_text_get_layout (text_widget); + GVariantBuilder builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("a{ss}")); + int offset; + int start, end; + + g_variant_get (parameters, "(i)", &offset); + + gtk_pango_get_run_attributes (layout, &builder, offset, &start, &end); + + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(a{ss}ii)", &builder, start, end)); + } + else if (g_strcmp0 (method_name, "GetAttributeValue") == 0) + { + PangoLayout *layout = gtk_text_get_layout (text_widget); + GVariantBuilder builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("a{ss}")); + int offset; + const char *name; + int start, end; + GVariant *attrs; + const char *val; + + g_variant_get (parameters, "(i&s)", &offset, &name); + + gtk_pango_get_run_attributes (layout, &builder, offset, &start, &end); + attrs = g_variant_builder_end (&builder); + if (!g_variant_lookup (attrs, name, "&s", &val)) + val = ""; + + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", val)); + g_variant_unref (attrs); + } + else if (g_strcmp0 (method_name, "GetAttributeRun") == 0) + { + PangoLayout *layout = gtk_text_get_layout (text_widget); + GVariantBuilder builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("a{ss}")); + int offset; + gboolean include_defaults; + int start, end; + + g_variant_get (parameters, "(ib)", &offset, &include_defaults); + + if (include_defaults) + gtk_pango_get_default_attributes (layout, &builder); + + gtk_pango_get_run_attributes (layout, &builder, offset, &start, &end); + + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(a{ss}ii)", &builder, start, end)); + } + else if (g_strcmp0 (method_name, "GetDefaultAttributes") == 0 || + g_strcmp0 (method_name, "GetDefaultAttributeSet") == 0) + { + PangoLayout *layout = gtk_text_get_layout (text_widget); + GVariantBuilder builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("a{ss}")); + + gtk_pango_get_default_attributes (layout, &builder); + + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(a{ss})", &builder)); + } + else if (g_strcmp0 (method_name, "GetNSelections") == 0) + { + int n = 0; + + if (gtk_editable_get_selection_bounds (GTK_EDITABLE (widget), NULL, NULL)) + n = 1; + + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(i)", n)); + } + else if (g_strcmp0 (method_name, "GetSelection") == 0) + { + int num; + int start, end; + gboolean ret = TRUE; + + g_variant_get (parameters, "(i)", &num); + + if (num != 0) + ret = FALSE; + else + { + if (!gtk_editable_get_selection_bounds (GTK_EDITABLE (widget), &start, &end)) + ret = FALSE; + } + + if (!ret) + g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "Not a valid selection: %d", num); + else + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(ii)", start, end)); + } + else if (g_strcmp0 (method_name, "AddSelection") == 0) + { + int start, end; + gboolean ret; + + g_variant_get (parameters, "(ii)", &start, &end); + + if (gtk_editable_get_selection_bounds (GTK_EDITABLE (widget), NULL, NULL)) + { + ret = FALSE; + } + else + { + gtk_editable_select_region (GTK_EDITABLE (widget), start, end); + ret = TRUE; + } + + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret)); + } + else if (g_strcmp0 (method_name, "RemoveSelection") == 0) + { + int num; + int start, end; + gboolean ret; + + g_variant_get (parameters, "(i)", &num); + + if (num != 0) + ret = FALSE; + else + { + if (!gtk_editable_get_selection_bounds (GTK_EDITABLE (widget), &start, &end)) + { + ret = FALSE; + } + else + { + gtk_editable_select_region (GTK_EDITABLE (widget), end, end); + ret = TRUE; + } + } + + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret)); + } + else if (g_strcmp0 (method_name, "SetSelection") == 0) + { + int num; + int start, end; + gboolean ret; + + g_variant_get (parameters, "(iii)", &num, &start, &end); + + if (num != 0) + ret = FALSE; + else + { + if (!gtk_editable_get_selection_bounds (GTK_EDITABLE (widget), NULL, NULL)) + { + ret = FALSE; + } + else + { + gtk_editable_select_region (GTK_EDITABLE (widget), start, end); + ret = TRUE; + } + } + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret)); + } + else if (g_strcmp0 (method_name, "GetCharacterExtents") == 0) + { + g_dbus_method_invocation_return_error_literal (invocation, G_DBUS_ERROR, G_DBUS_ERROR_NOT_SUPPORTED, ""); + } + else if (g_strcmp0 (method_name, "GetRangeExtents") == 0) + { + g_dbus_method_invocation_return_error_literal (invocation, G_DBUS_ERROR, G_DBUS_ERROR_NOT_SUPPORTED, ""); + } + else if (g_strcmp0 (method_name, "GetBoundedRanges") == 0) + { + g_dbus_method_invocation_return_error_literal (invocation, G_DBUS_ERROR, G_DBUS_ERROR_NOT_SUPPORTED, ""); + } + else if (g_strcmp0 (method_name, "ScrollSubstringTo") == 0) + { + g_dbus_method_invocation_return_error_literal (invocation, G_DBUS_ERROR, G_DBUS_ERROR_NOT_SUPPORTED, ""); + } + else if (g_strcmp0 (method_name, "ScrollSubstringToPoint") == 0) + { + g_dbus_method_invocation_return_error_literal (invocation, G_DBUS_ERROR, G_DBUS_ERROR_NOT_SUPPORTED, ""); + } +} + +static GVariant * +entry_get_property (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *property_name, + GError **error, + gpointer user_data) +{ + GtkATContext *self = user_data; + GtkAccessible *accessible = gtk_at_context_get_accessible (self); + GtkWidget *widget = GTK_WIDGET (accessible); + + if (g_strcmp0 (property_name, "CharacterCount") == 0) + { + const char *text; + int len; + + text = gtk_editable_get_text (GTK_EDITABLE (widget)); + len = g_utf8_strlen (text, -1); + + return g_variant_new_int32 (len); + } + else if (g_strcmp0 (property_name, "CaretOffset") == 0) + { + int offset; + + offset = gtk_editable_get_position (GTK_EDITABLE (widget)); + + return g_variant_new_int32 (offset); + } + + return NULL; +} + +static const GDBusInterfaceVTable entry_vtable = { + entry_handle_method, + entry_get_property, + NULL, +}; + + +static void +text_view_handle_method (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *method_name, + GVariant *parameters, + GDBusMethodInvocation *invocation, + gpointer user_data) +{ + GtkATContext *self = user_data; + GtkAccessible *accessible = gtk_at_context_get_accessible (self); + GtkWidget *widget = GTK_WIDGET (accessible); + + if (g_strcmp0 (method_name, "GetCaretOffset") == 0) + { + GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget)); + GtkTextMark *insert; + GtkTextIter iter; + int offset; + + insert = gtk_text_buffer_get_insert (buffer); + gtk_text_buffer_get_iter_at_mark (buffer, &iter, insert); + offset = gtk_text_iter_get_offset (&iter); + + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(i)", offset)); + } + else if (g_strcmp0 (method_name, "SetCaretOffset") == 0) + { + GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget)); + GtkTextIter iter; + int offset; + + g_variant_get (parameters, "(i)", &offset); + + gtk_text_buffer_get_iter_at_offset (buffer, &iter, offset); + gtk_text_buffer_place_cursor (buffer, &iter); + gtk_text_view_scroll_to_iter (GTK_TEXT_VIEW (widget), &iter, 0, FALSE, 0, 0); + + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", TRUE)); + } + else if (g_strcmp0 (method_name, "GetText") == 0) + { + GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget)); + GtkTextIter start_iter, end_iter; + int start, end; + char *string; + + g_variant_get (parameters, "(ii)", &start, &end); + + gtk_text_buffer_get_iter_at_offset (buffer, &start_iter, start); + gtk_text_buffer_get_iter_at_offset (buffer, &end_iter, end); + + string = gtk_text_buffer_get_text (buffer, &start_iter, &end_iter, FALSE); + + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", string)); + g_free (string); + } + else if (g_strcmp0 (method_name, "GetTextBeforeOffset") == 0) + { + int offset; + AtspiTextBoundaryType boundary_type; + char *string; + int start, end; + + g_variant_get (parameters, "(iu)", &offset, &boundary_type); + + string = gtk_text_view_get_text_before (GTK_TEXT_VIEW (widget), offset, boundary_type, &start, &end); + + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(sii)", string, start, end)); + g_free (string); + } + else if (g_strcmp0 (method_name, "GetTextAtOffset") == 0) + { + int offset; + AtspiTextBoundaryType boundary_type; + char *string; + int start, end; + + g_variant_get (parameters, "(iu)", &offset, &boundary_type); + + string = gtk_text_view_get_text_at (GTK_TEXT_VIEW (widget), offset, boundary_type, &start, &end); + + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(sii)", string, start, end)); + g_free (string); + } + else if (g_strcmp0 (method_name, "GetTextAfterOffset") == 0) + { + int offset; + AtspiTextBoundaryType boundary_type; + char *string; + int start, end; + + g_variant_get (parameters, "(iu)", &offset, &boundary_type); + + string = gtk_text_view_get_text_after (GTK_TEXT_VIEW (widget), offset, boundary_type, &start, &end); + + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(sii)", string, start, end)); + g_free (string); + } + else if (g_strcmp0 (method_name, "GetCharacterAtOffset") == 0) + { + GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget)); + int offset; + gunichar ch = 0; + + g_variant_get (parameters, "(i)", &offset); + + if (offset >= 0 && offset < gtk_text_buffer_get_char_count (buffer)) + { + GtkTextIter start, end; + char *string; + gtk_text_buffer_get_iter_at_offset (buffer, &start, offset); + end = start; + gtk_text_iter_forward_char (&end); + string = gtk_text_buffer_get_slice (buffer, &start, &end, FALSE); + ch = g_utf8_get_char (string); + g_free (string); + } + + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(i)", ch)); + } + else if (g_strcmp0 (method_name, "GetStringAtOffset") == 0) + { + int offset; + AtspiTextGranularity granularity; + char *string; + int start, end; + + g_variant_get (parameters, "(iu)", &offset, &granularity); + + string = gtk_text_view_get_string_at (GTK_TEXT_VIEW (widget), offset, granularity, &start, &end); + + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(sii)", string, start, end)); + g_free (string); + } + else if (g_strcmp0 (method_name, "GetAttributes") == 0) + { + GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget)); + GVariantBuilder builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("a{ss}")); + int offset; + int start, end; + + g_variant_get (parameters, "(i)", &offset); + + gtk_text_buffer_get_run_attributes (buffer, &builder, offset, &start, &end); + + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(a{ss}ii)", &builder, start, end)); + } + else if (g_strcmp0 (method_name, "GetAttributeValue") == 0) + { + GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget)); + GVariantBuilder builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("a{ss}")); + int offset; + const char *name; + int start, end; + GVariant *attrs; + const char *val; + + g_variant_get (parameters, "(i&s)", &offset, &name); + + gtk_text_buffer_get_run_attributes (buffer, &builder, offset, &start, &end); + + attrs = g_variant_builder_end (&builder); + if (!g_variant_lookup (attrs, name, "&s", &val)) + val = ""; + + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", val)); + g_variant_unref (attrs); + } + else if (g_strcmp0 (method_name, "GetAttributeRun") == 0) + { + GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget)); + GVariantBuilder builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("a{ss}")); + int offset; + gboolean include_defaults; + int start, end; + + g_variant_get (parameters, "(ib)", &offset, &include_defaults); + + if (include_defaults) + gtk_text_view_add_default_attributes (GTK_TEXT_VIEW (widget), &builder); + + gtk_text_buffer_get_run_attributes (buffer, &builder, offset, &start, &end); + + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(a{ss}ii)", &builder, start, end)); + } + else if (g_strcmp0 (method_name, "GetDefaultAttributes") == 0 || + g_strcmp0 (method_name, "GetDefaultAttributeSet") == 0) + { + GVariantBuilder builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("a{ss}")); + + gtk_text_view_add_default_attributes (GTK_TEXT_VIEW (widget), &builder); + + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(a{ss})", &builder)); + } + else if (g_strcmp0 (method_name, "GetNSelections") == 0) + { + int n = 0; + + if (gtk_text_buffer_get_selection_bounds (gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget)), NULL, NULL)) + n = 1; + + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(i)", n)); + } + else if (g_strcmp0 (method_name, "GetSelection") == 0) + { + GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget)); + GtkTextIter start_iter, end_iter; + int num; + int start, end; + gboolean ret = TRUE; + + g_variant_get (parameters, "(i)", &num); + + if (num != 0) + ret = FALSE; + else + { + if (!gtk_text_buffer_get_selection_bounds (buffer, &start_iter, &end_iter)) + ret = FALSE; + else + { + start = gtk_text_iter_get_offset (&start_iter); + end = gtk_text_iter_get_offset (&end_iter); + } + } + + if (!ret) + g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "Not a valid selection: %d", num); + else + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(ii)", start, end)); + } + else if (g_strcmp0 (method_name, "AddSelection") == 0) + { + GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget)); + GtkTextIter start_iter, end_iter; + int start, end; + gboolean ret; + + g_variant_get (parameters, "(ii)", &start, &end); + + if (gtk_text_buffer_get_selection_bounds (buffer, NULL, NULL)) + { + ret = FALSE; + } + else + { + gtk_text_buffer_get_iter_at_offset (buffer, &start_iter, start); + gtk_text_buffer_get_iter_at_offset (buffer, &end_iter, end); + gtk_text_buffer_select_range (buffer, &start_iter, &end_iter); + ret = TRUE; + } + + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(i)", ret)); + } + else if (g_strcmp0 (method_name, "RemoveSelection") == 0) + { + GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget)); + GtkTextIter start_iter, end_iter; + int num; + gboolean ret; + + g_variant_get (parameters, "(i)", &num); + + if (num != 0) + ret = FALSE; + else + { + if (!gtk_text_buffer_get_selection_bounds (buffer, &start_iter, &end_iter)) + { + ret = FALSE; + } + else + { + gtk_text_buffer_select_range (buffer, &end_iter, &end_iter); + ret = TRUE; + } + } + + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret)); + } + else if (g_strcmp0 (method_name, "SetSelection") == 0) + { + GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget)); + GtkTextIter start_iter, end_iter; + int num; + int start, end; + gboolean ret; + + g_variant_get (parameters, "(iii)", &num, &start, &end); + + if (num != 0) + ret = FALSE; + else + { + if (!gtk_text_buffer_get_selection_bounds (buffer, NULL, NULL)) + { + ret = FALSE; + } + else + { + gtk_text_buffer_get_iter_at_offset (buffer, &start_iter, start); + gtk_text_buffer_get_iter_at_offset (buffer, &end_iter, end); + gtk_text_buffer_select_range (buffer, &end_iter, &end_iter); + ret = TRUE; + } + } + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret)); + } + else if (g_strcmp0 (method_name, "GetCharacterExtents") == 0) + { + g_dbus_method_invocation_return_error_literal (invocation, G_DBUS_ERROR, G_DBUS_ERROR_NOT_SUPPORTED, ""); + } + else if (g_strcmp0 (method_name, "GetRangeExtents") == 0) + { + g_dbus_method_invocation_return_error_literal (invocation, G_DBUS_ERROR, G_DBUS_ERROR_NOT_SUPPORTED, ""); + } + else if (g_strcmp0 (method_name, "GetBoundedRanges") == 0) + { + g_dbus_method_invocation_return_error_literal (invocation, G_DBUS_ERROR, G_DBUS_ERROR_NOT_SUPPORTED, ""); + } + else if (g_strcmp0 (method_name, "ScrollSubstringTo") == 0) + { + g_dbus_method_invocation_return_error_literal (invocation, G_DBUS_ERROR, G_DBUS_ERROR_NOT_SUPPORTED, ""); + } + else if (g_strcmp0 (method_name, "ScrollSubstringToPoint") == 0) + { + g_dbus_method_invocation_return_error_literal (invocation, G_DBUS_ERROR, G_DBUS_ERROR_NOT_SUPPORTED, ""); + } +} + +static GVariant * +text_view_get_property (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *property_name, + GError **error, + gpointer user_data) +{ + GtkATContext *self = user_data; + GtkAccessible *accessible = gtk_at_context_get_accessible (self); + GtkWidget *widget = GTK_WIDGET (accessible); + + if (g_strcmp0 (property_name, "CharacterCount") == 0) + { + GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget)); + int len; + + len = gtk_text_buffer_get_char_count (buffer); + + return g_variant_new_int32 (len); + } + else if (g_strcmp0 (property_name, "CaretOffset") == 0) + { + GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget)); + GtkTextMark *insert; + GtkTextIter iter; + int offset; + + insert = gtk_text_buffer_get_insert (buffer); + gtk_text_buffer_get_iter_at_mark (buffer, &iter, insert); + offset = gtk_text_iter_get_offset (&iter); + + return g_variant_new_int32 (offset); + } + + return NULL; +} + +static const GDBusInterfaceVTable text_view_vtable = { + text_view_handle_method, + text_view_get_property, + NULL, +}; + +const GDBusInterfaceVTable * +gtk_atspi_get_text_vtable (GtkAccessible *accessible) +{ + if (GTK_IS_LABEL (accessible)) + return &label_vtable; + else if (GTK_IS_ENTRY (accessible) || + GTK_IS_SEARCH_ENTRY (accessible) || + GTK_IS_PASSWORD_ENTRY (accessible) || + GTK_IS_SPIN_BUTTON (accessible)) + return &entry_vtable; + else if (GTK_IS_TEXT_VIEW (accessible)) + return &text_view_vtable; + + return NULL; +} + +typedef struct { + void (* text_changed) (gpointer data, + const char *kind, + int start, + int end, + const char *text); + void (* selection_changed) (gpointer data, + const char *kind, + int cursor_position); + + gpointer data; + GtkTextBuffer *buffer; + int cursor_position; + int selection_bound; +} TextChanged; + +static void +insert_text_cb (GtkEditable *editable, + char *new_text, + int new_text_length, + int *position, + TextChanged *changed) +{ + int length; + + if (new_text_length == 0) + return; + + length = g_utf8_strlen (new_text, new_text_length); + changed->text_changed (changed->data, "insert", *position - length, length, new_text); +} + +static void +delete_text_cb (GtkEditable *editable, + int start, + int end, + TextChanged *changed) +{ + char *text; + + if (start == end) + return; + + text = gtk_editable_get_chars (editable, start, end); + changed->text_changed (changed->data, "delete", start, end - start, text); + g_free (text); +} + +static void +update_selection (TextChanged *changed, + int cursor_position, + int selection_bound) +{ + gboolean caret_moved, bound_moved; + + caret_moved = cursor_position != changed->cursor_position; + bound_moved = selection_bound != changed->selection_bound; + + if (!caret_moved && !bound_moved) + return; + + changed->cursor_position = cursor_position; + changed->selection_bound = selection_bound; + + if (caret_moved) + changed->selection_changed (changed->data, "text-caret-moved", changed->cursor_position); + + if (caret_moved || bound_moved) + changed->selection_changed (changed->data, "text-selection-changed", 0); +} + +static void +notify_cb (GObject *object, + GParamSpec *pspec, + TextChanged *changed) +{ + if (g_strcmp0 (pspec->name, "cursor-position") == 0 || + g_strcmp0 (pspec->name, "selection-bound") == 0) + { + int cursor_position, selection_bound; + + gtk_editable_get_selection_bounds (GTK_EDITABLE (object), &cursor_position, &selection_bound); + update_selection (changed, cursor_position, selection_bound); + } +} + +static void +update_cursor (GtkTextBuffer *buffer, + TextChanged *changed) +{ + GtkTextIter iter; + int cursor_position, selection_bound; + + gtk_text_buffer_get_iter_at_mark (buffer, &iter, gtk_text_buffer_get_insert (buffer)); + cursor_position = gtk_text_iter_get_offset (&iter); + + gtk_text_buffer_get_iter_at_mark (buffer, &iter, gtk_text_buffer_get_selection_bound (buffer)); + + selection_bound = gtk_text_iter_get_offset (&iter); + + update_selection (changed, cursor_position, selection_bound); +} + +static void +insert_range_cb (GtkTextBuffer *buffer, + GtkTextIter *iter, + char *text, + int len, + TextChanged *changed) +{ + int position; + int length; + + position = gtk_text_iter_get_offset (iter); + length = g_utf8_strlen (text, len); + + changed->text_changed (changed->data, "insert", position - length, length, text); + + update_cursor (buffer, changed); +} + +static void +delete_range_cb (GtkTextBuffer *buffer, + GtkTextIter *start, + GtkTextIter *end, + TextChanged *changed) +{ + int offset, length; + char *text; + + text = gtk_text_buffer_get_slice (buffer, start, end, FALSE); + + offset = gtk_text_iter_get_offset (start); + length = gtk_text_iter_get_offset (end) - offset; + + changed->text_changed (changed->data, "delete", offset, length, text); + + g_free (text); +} + +static void +delete_range_after_cb (GtkTextBuffer *buffer, + GtkTextIter *start, + GtkTextIter *end, + TextChanged *changed) +{ + update_cursor (buffer, changed); +} + +static void +mark_set_cb (GtkTextBuffer *buffer, + GtkTextIter *location, + GtkTextMark *mark, + TextChanged *changed) +{ + if (mark == gtk_text_buffer_get_insert (buffer) || + mark == gtk_text_buffer_get_selection_bound (buffer)) + update_cursor (buffer, changed); +} + +static void +buffer_changed (GtkWidget *widget, + GParamSpec *pspec, + TextChanged *changed) +{ + GtkTextBuffer *buffer; + GtkTextIter start, end; + char *text; + + buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget)); + + if (changed->buffer) + { + g_signal_handlers_disconnect_by_func (changed->buffer, insert_range_cb, changed); + g_signal_handlers_disconnect_by_func (changed->buffer, delete_range_cb, changed); + g_signal_handlers_disconnect_by_func (changed->buffer, delete_range_after_cb, changed); + g_signal_handlers_disconnect_by_func (changed->buffer, mark_set_cb, changed); + + gtk_text_buffer_get_bounds (changed->buffer, &start, &end); + text = gtk_text_buffer_get_slice (changed->buffer, &start, &end, FALSE); + changed->text_changed (changed->data, "delete", 0, gtk_text_buffer_get_char_count (changed->buffer), text); + g_free (text); + + update_selection (changed, 0, 0); + + g_clear_object (&changed->buffer); + } + + changed->buffer = buffer; + + if (changed->buffer) + { + g_object_ref (changed->buffer); + g_signal_connect (changed->buffer, "insert-text", G_CALLBACK (insert_range_cb), changed); + g_signal_connect (changed->buffer, "delete-range", G_CALLBACK (delete_range_cb), changed); + g_signal_connect_after (changed->buffer, "delete-range", G_CALLBACK (delete_range_after_cb), changed); + g_signal_connect_after (changed->buffer, "mark-set", G_CALLBACK (mark_set_cb), changed); + + gtk_text_buffer_get_bounds (changed->buffer, &start, &end); + text = gtk_text_buffer_get_slice (changed->buffer, &start, &end, FALSE); + changed->text_changed (changed->data, "insert", 0, gtk_text_buffer_get_char_count (changed->buffer), text); + g_free (text); + + update_cursor (changed->buffer, changed); + } +} + +void +gtk_atspi_connect_text_signals (GtkAccessible *accessible, + GtkAtspiTextChangedCallback text_changed, + GtkAtspiTextSelectionCallback selection_changed, + gpointer data) +{ + TextChanged *changed; + + if (!GTK_IS_EDITABLE (accessible) && + !GTK_IS_TEXT_VIEW (accessible)) + return; + + changed = g_new0 (TextChanged, 1); + changed->text_changed = text_changed; + changed->selection_changed = selection_changed; + changed->data = data; + + g_object_set_data_full (G_OBJECT (accessible), "accessible-text-data", changed, g_free); + + if (GTK_IS_EDITABLE (accessible)) + { + GtkText *text = gtk_editable_get_text_widget (GTK_WIDGET (accessible)); + + if (text) + { + g_signal_connect_after (text, "insert-text", G_CALLBACK (insert_text_cb), changed); + g_signal_connect (text, "delete-text", G_CALLBACK (delete_text_cb), changed); + g_signal_connect (text, "notify", G_CALLBACK (notify_cb), changed); + + gtk_editable_get_selection_bounds (GTK_EDITABLE (text), &changed->cursor_position, &changed->selection_bound); + } + } + else if (GTK_IS_TEXT_VIEW (accessible)) + { + g_signal_connect (accessible, "notify::buffer", G_CALLBACK (buffer_changed), changed); + buffer_changed (GTK_WIDGET (accessible), NULL, changed); + } +} + +void +gtk_atspi_disconnect_text_signals (GtkAccessible *accessible) +{ + TextChanged *changed; + + changed = g_object_get_data (G_OBJECT (accessible), "accessible-text-data"); + + if (GTK_IS_EDITABLE (accessible)) + { + GtkText *text = gtk_editable_get_text_widget (GTK_WIDGET (accessible)); + + if (text) + { + g_signal_handlers_disconnect_by_func (text, insert_text_cb, changed); + g_signal_handlers_disconnect_by_func (text, delete_text_cb, changed); + g_signal_handlers_disconnect_by_func (text, notify_cb, changed); + } + } + else if (GTK_IS_TEXT_VIEW (accessible)) + { + g_signal_handlers_disconnect_by_func (accessible, buffer_changed, changed); + if (changed->buffer) + { + g_signal_handlers_disconnect_by_func (changed->buffer, insert_range_cb, changed); + g_signal_handlers_disconnect_by_func (changed->buffer, delete_range_cb, changed); + g_signal_handlers_disconnect_by_func (changed->buffer, delete_range_after_cb, changed); + g_signal_handlers_disconnect_by_func (changed->buffer, mark_set_cb, changed); + } + g_clear_object (&changed->buffer); + } + + g_object_set_data (G_OBJECT (accessible), "accessible-text-data", NULL); +} diff --git a/gtk/a11y/gtkatspitextbuffer.c b/gtk/a11y/gtkatspitextbuffer.c new file mode 100644 index 0000000000..73380997bc --- /dev/null +++ b/gtk/a11y/gtkatspitextbuffer.c @@ -0,0 +1,984 @@ +/* gtkatspitextbuffer.c - GtkTextBuffer-related utilities for AT-SPI + * + * Copyright (c) 2020 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>.Free + */ + +#include "config.h" +#include "gtkatspitextbufferprivate.h" +#include "gtkatspipangoprivate.h" +#include "gtktextviewprivate.h" + +static const char * +gtk_justification_to_string (GtkJustification just) +{ + switch (just) + { + case GTK_JUSTIFY_LEFT: + return "left"; + case GTK_JUSTIFY_RIGHT: + return "right"; + case GTK_JUSTIFY_CENTER: + return "center"; + case GTK_JUSTIFY_FILL: + return "fill"; + default: + g_assert_not_reached (); + } +} + +static const char * +gtk_text_direction_to_string (GtkTextDirection direction) +{ + switch (direction) + { + case GTK_TEXT_DIR_NONE: + return "none"; + case GTK_TEXT_DIR_LTR: + return "ltr"; + case GTK_TEXT_DIR_RTL: + return "rtl"; + default: + g_assert_not_reached (); + } +} + +void +gtk_text_view_add_default_attributes (GtkTextView *view, + GVariantBuilder *builder) +{ + GtkTextAttributes *text_attrs; + PangoFontDescription *font; + char *value; + + text_attrs = gtk_text_view_get_default_attributes (view); + + font = text_attrs->font; + + if (font) + gtk_pango_get_font_attributes (font, builder); + + g_variant_builder_add (builder, "{ss}", "justification", + gtk_justification_to_string (text_attrs->justification)); + g_variant_builder_add (builder, "{ss}", "direction", + gtk_text_direction_to_string (text_attrs->direction)); + g_variant_builder_add (builder, "{ss}", "wrap-mode", + pango_wrap_mode_to_string (text_attrs->wrap_mode)); + g_variant_builder_add (builder, "{ss}", "editable", + text_attrs->editable ? "true" : "false"); + g_variant_builder_add (builder, "{ss}", "invisible", + text_attrs->invisible ? "true" : "false"); + g_variant_builder_add (builder, "{ss}", "bg-full-height", + text_attrs->bg_full_height ? "true" : "false"); + g_variant_builder_add (builder, "{ss}", "strikethrough", + text_attrs->appearance.strikethrough ? "true" : "false"); + g_variant_builder_add (builder, "{ss}", "underline", + pango_underline_to_string (text_attrs->appearance.underline)); + + value = g_strdup_printf ("%u,%u,%u", + (guint)(text_attrs->appearance.bg_rgba->red * 65535), + (guint)(text_attrs->appearance.bg_rgba->green * 65535), + (guint)(text_attrs->appearance.bg_rgba->blue * 65535)); + g_variant_builder_add (builder, "{ss}", "bg-color", value); + g_free (value); + + value = g_strdup_printf ("%u,%u,%u", + (guint)(text_attrs->appearance.fg_rgba->red * 65535), + (guint)(text_attrs->appearance.fg_rgba->green * 65535), + (guint)(text_attrs->appearance.fg_rgba->blue * 65535)); + g_variant_builder_add (builder, "{ss}", "bg-color", value); + g_free (value); + + value = g_strdup_printf ("%g", text_attrs->font_scale); + g_variant_builder_add (builder, "{ss}", "scale", value); + g_free (value); + + value = g_strdup ((gchar *)(text_attrs->language)); + g_variant_builder_add (builder, "{ss}", "language", value); + g_free (value); + + value = g_strdup_printf ("%i", text_attrs->appearance.rise); + g_variant_builder_add (builder, "{ss}", "rise", value); + g_free (value); + + value = g_strdup_printf ("%i", text_attrs->pixels_inside_wrap); + g_variant_builder_add (builder, "{ss}", "pixels-inside-wrap", value); + g_free (value); + + value = g_strdup_printf ("%i", text_attrs->pixels_below_lines); + g_variant_builder_add (builder, "{ss}", "pixels-below-lines", value); + g_free (value); + + value = g_strdup_printf ("%i", text_attrs->pixels_above_lines); + g_variant_builder_add (builder, "{ss}", "pixels-above-lines", value); + g_free (value); + + value = g_strdup_printf ("%i", text_attrs->indent); + g_variant_builder_add (builder, "{ss}", "indent", value); + g_free (value); + + value = g_strdup_printf ("%i", text_attrs->left_margin); + g_variant_builder_add (builder, "{ss}", "left-margin", value); + g_free (value); + + value = g_strdup_printf ("%i", text_attrs->right_margin); + g_variant_builder_add (builder, "{ss}", "right-margin", value); + g_free (value); + + gtk_text_attributes_unref (text_attrs); +} + +void +gtk_text_buffer_get_run_attributes (GtkTextBuffer *buffer, + GVariantBuilder *builder, + int offset, + int *start_offset, + int *end_offset) +{ + GtkTextIter iter; + GSList *tags, *temp_tags; + gdouble scale = 1; + gboolean val_set = FALSE; + + gtk_text_buffer_get_iter_at_offset (buffer, &iter, offset); + + gtk_text_iter_forward_to_tag_toggle (&iter, NULL); + *end_offset = gtk_text_iter_get_offset (&iter); + + gtk_text_iter_backward_to_tag_toggle (&iter, NULL); + *start_offset = gtk_text_iter_get_offset (&iter); + + gtk_text_buffer_get_iter_at_offset (buffer, &iter, offset); + + tags = gtk_text_iter_get_tags (&iter); + tags = g_slist_reverse (tags); + + temp_tags = tags; + while (temp_tags && !val_set) + { + GtkTextTag *tag = GTK_TEXT_TAG (temp_tags->data); + PangoStyle style; + + g_object_get (tag, + "style-set", &val_set, + "style", &style, + NULL); + if (val_set) + g_variant_builder_add (builder, "{ss}", "style", pango_style_to_string (style)); + temp_tags = temp_tags->next; + } + val_set = FALSE; + + temp_tags = tags; + while (temp_tags && !val_set) + { + GtkTextTag *tag = GTK_TEXT_TAG (temp_tags->data); + PangoVariant variant; + + g_object_get (tag, + "variant-set", &val_set, + "variant", &variant, + NULL); + if (val_set) + g_variant_builder_add (builder, "{ss}", "variant", pango_variant_to_string (variant)); + temp_tags = temp_tags->next; + } + val_set = FALSE; + + temp_tags = tags; + while (temp_tags && !val_set) + { + GtkTextTag *tag = GTK_TEXT_TAG (temp_tags->data); + PangoStretch stretch; + + g_object_get (tag, + "stretch-set", &val_set, + "stretch", &stretch, + NULL); + if (val_set) + g_variant_builder_add (builder, "{ss}", "stretch", pango_stretch_to_string (stretch)); + temp_tags = temp_tags->next; + } + val_set = FALSE; + + temp_tags = tags; + while (temp_tags && !val_set) + { + GtkTextTag *tag = GTK_TEXT_TAG (temp_tags->data); + GtkJustification justification; + + g_object_get (tag, + "justification-set", &val_set, + "justification", &justification, + NULL); + if (val_set) + g_variant_builder_add (builder, "{ss}", "justification", gtk_justification_to_string (justification)); + temp_tags = temp_tags->next; + } + val_set = FALSE; + + temp_tags = tags; + while (temp_tags && !val_set) + { + GtkTextTag *tag = GTK_TEXT_TAG (temp_tags->data); + GtkTextDirection direction; + + g_object_get (tag, "direction", &direction, NULL); + if (direction != GTK_TEXT_DIR_NONE) + { + val_set = TRUE; + g_variant_builder_add (builder, "{ss}", "direction", gtk_text_direction_to_string (direction)); + } + temp_tags = temp_tags->next; + } + val_set = FALSE; + + temp_tags = tags; + while (temp_tags && !val_set) + { + GtkTextTag *tag = GTK_TEXT_TAG (temp_tags->data); + GtkWrapMode wrap_mode; + + g_object_get (tag, + "wrap-mode-set", &val_set, + "wrap-mode", &wrap_mode, + NULL); + if (val_set) + g_variant_builder_add (builder, "{ss}", "wrap-mode", pango_wrap_mode_to_string (wrap_mode)); + temp_tags = temp_tags->next; + } + val_set = FALSE; + + temp_tags = tags; + while (temp_tags && !val_set) + { + GtkTextTag *tag = GTK_TEXT_TAG (temp_tags->data); + + g_object_get (tag, "foreground-set", &val_set, NULL); + if (val_set) + { + GdkRGBA *rgba; + char *value; + + g_object_get (tag, "foreground", &rgba, NULL); + value = g_strdup_printf ("%u,%u,%u", + (guint) rgba->red * 65535, + (guint) rgba->green * 65535, + (guint) rgba->blue * 65535); + gdk_rgba_free (rgba); + g_variant_builder_add (builder, "{ss}", "fg-color", value); + g_free (value); + } + temp_tags = temp_tags->next; + } + val_set = FALSE; + + temp_tags = tags; + while (temp_tags && !val_set) + { + GtkTextTag *tag = GTK_TEXT_TAG (temp_tags->data); + + g_object_get (tag, "background-set", &val_set, NULL); + if (val_set) + { + GdkRGBA *rgba; + char *value; + + g_object_get (tag, "background-rgba", &rgba, NULL); + value = g_strdup_printf ("%u,%u,%u", + (guint) rgba->red * 65535, + (guint) rgba->green * 65535, + (guint) rgba->blue * 65535); + gdk_rgba_free (rgba); + g_variant_builder_add (builder, "{ss}", "bg-color", value); + g_free (value); + } + temp_tags = temp_tags->next; + } + val_set = FALSE; + + temp_tags = tags; + while (temp_tags && !val_set) + { + GtkTextTag *tag = GTK_TEXT_TAG (temp_tags->data); + + g_object_get (tag, "family-set", &val_set, NULL); + + if (val_set) + { + char *value; + g_object_get (tag, "family", &value, NULL); + g_variant_builder_add (builder, "{ss}", "family-name", value); + g_free (value); + } + temp_tags = temp_tags->next; + } + val_set = FALSE; + + temp_tags = tags; + while (temp_tags && !val_set) + { + GtkTextTag *tag = GTK_TEXT_TAG (temp_tags->data); + + g_object_get (tag, "language-set", &val_set, NULL); + + if (val_set) + { + char *value; + g_object_get (tag, "language", &value, NULL); + g_variant_builder_add (builder, "{ss}", "language", value); + g_free (value); + } + temp_tags = temp_tags->next; + } + val_set = FALSE; + + temp_tags = tags; + while (temp_tags && !val_set) + { + GtkTextTag *tag = GTK_TEXT_TAG (temp_tags->data); + int weight; + + g_object_get (tag, + "weight-set", &val_set, + "weight", &weight, + NULL); + + if (val_set) + { + char *value; + value = g_strdup_printf ("%d", weight); + g_variant_builder_add (builder, "{ss}", "weight", value); + g_free (value); + } + temp_tags = temp_tags->next; + } + val_set = FALSE; + + /* scale is special as the effective value is the product + * of all specified values + */ + temp_tags = tags; + while (temp_tags) + { + GtkTextTag *tag = GTK_TEXT_TAG (temp_tags->data); + gboolean scale_set; + + g_object_get (tag, "scale-set", &scale_set, NULL); + if (scale_set) + { + double font_scale; + g_object_get (tag, "scale", &font_scale, NULL); + val_set = TRUE; + scale *= font_scale; + } + temp_tags = temp_tags->next; + } + if (val_set) + { + char *value = g_strdup_printf ("%g", scale); + g_variant_builder_add (builder, "{ss}", "scale", value); + g_free (value); + } + val_set = FALSE; + + temp_tags = tags; + while (temp_tags && !val_set) + { + GtkTextTag *tag = GTK_TEXT_TAG (temp_tags->data); + int size; + + g_object_get (tag, + "size-set", &val_set, + "size", &size, + NULL); + if (val_set) + { + char *value = g_strdup_printf ("%i", size); + g_variant_builder_add (builder, "{ss}", "size", value); + g_free (value); + } + temp_tags = temp_tags->next; + } + val_set = FALSE; + + temp_tags = tags; + while (temp_tags && !val_set) + { + GtkTextTag *tag = GTK_TEXT_TAG (temp_tags->data); + gboolean strikethrough; + + g_object_get (tag, + "strikethrough-set", &val_set, + "strikethrough", &strikethrough, + NULL); + if (val_set) + g_variant_builder_add (builder, "{ss}", "strikethrough", strikethrough ? "true" : "false"); + temp_tags = temp_tags->next; + } + val_set = FALSE; + + temp_tags = tags; + while (temp_tags && !val_set) + { + GtkTextTag *tag = GTK_TEXT_TAG (temp_tags->data); + PangoUnderline underline; + + g_object_get (tag, + "underline-set", &val_set, + "underline", &underline, + NULL); + if (val_set) + g_variant_builder_add (builder, "{ss}", "underline", + pango_underline_to_string (underline)); + temp_tags = temp_tags->next; + } + val_set = FALSE; + + temp_tags = tags; + while (temp_tags && !val_set) + { + GtkTextTag *tag = GTK_TEXT_TAG (temp_tags->data); + int rise; + + g_object_get (tag, + "rise-set", &val_set, + "rise", &rise, + NULL); + if (val_set) + { + char *value = g_strdup_printf ("%i", rise); + g_variant_builder_add (builder, "{ss}", "rise", value); + g_free (value); + } + temp_tags = temp_tags->next; + } + val_set = FALSE; + + temp_tags = tags; + while (temp_tags && !val_set) + { + GtkTextTag *tag = GTK_TEXT_TAG (temp_tags->data); + gboolean bg_full_height; + + g_object_get (tag, + "background-full-height-set", &val_set, + "background-full-height", &bg_full_height, + NULL); + if (val_set) + g_variant_builder_add (builder, "{ss}", "bg-full-height", bg_full_height ? "true" : "false"); + temp_tags = temp_tags->next; + } + val_set = FALSE; + + temp_tags = tags; + while (temp_tags && !val_set) + { + GtkTextTag *tag = GTK_TEXT_TAG (temp_tags->data); + int pixels; + + g_object_get (tag, + "pixels-inside-wrap-set", &val_set, + "pixels-inside-wrap", &pixels, + NULL); + if (val_set) + { + char *value = g_strdup_printf ("%i", pixels); + g_variant_builder_add (builder, "{ss}", "pixels-inside-wrap", value); + g_free (value); + } + temp_tags = temp_tags->next; + } + val_set = FALSE; + + temp_tags = tags; + while (temp_tags && !val_set) + { + GtkTextTag *tag = GTK_TEXT_TAG (temp_tags->data); + int pixels; + + g_object_get (tag, + "pixels-below-lines-set", &val_set, + "pixels-below-lines", &pixels, + NULL); + if (val_set) + { + char *value = g_strdup_printf ("%i", pixels); + g_variant_builder_add (builder, "{ss}", "pixels-below-lines", value); + g_free (value); + } + temp_tags = temp_tags->next; + } + val_set = FALSE; + + temp_tags = tags; + while (temp_tags && !val_set) + { + GtkTextTag *tag = GTK_TEXT_TAG (temp_tags->data); + int pixels; + + g_object_get (tag, + "pixels-above-lines-set", &val_set, + "pixels-above-lines", &pixels, + NULL); + if (val_set) + { + char *value = g_strdup_printf ("%i", pixels); + g_variant_builder_add (builder, "{ss}", "pixels-above-lines", value); + g_free (value); + } + temp_tags = temp_tags->next; + } + val_set = FALSE; + + temp_tags = tags; + while (temp_tags && !val_set) + { + GtkTextTag *tag = GTK_TEXT_TAG (temp_tags->data); + gboolean editable; + + g_object_get (tag, + "editable-set", &val_set, + "editable", &editable, + NULL); + if (val_set) + g_variant_builder_add (builder, "{ss}", "editable", editable ? "true" : "false"); + temp_tags = temp_tags->next; + } + val_set = FALSE; + + temp_tags = tags; + while (temp_tags && !val_set) + { + GtkTextTag *tag = GTK_TEXT_TAG (temp_tags->data); + gboolean invisible; + + g_object_get (tag, + "invisible-set", &val_set, + "invisible", &invisible, + NULL); + if (val_set) + g_variant_builder_add (builder, "{ss}", "invisible", invisible ? "true" : "false"); + temp_tags = temp_tags->next; + } + val_set = FALSE; + + temp_tags = tags; + while (temp_tags && !val_set) + { + GtkTextTag *tag = GTK_TEXT_TAG (temp_tags->data); + int indent; + + g_object_get (tag, + "indent-set", &val_set, + "indent", &indent, + NULL); + if (val_set) + { + char *value = g_strdup_printf ("%i", indent); + g_variant_builder_add (builder, "{ss}", "indent", value); + g_free (value); + } + temp_tags = temp_tags->next; + } + val_set = FALSE; + + temp_tags = tags; + while (temp_tags && !val_set) + { + GtkTextTag *tag = GTK_TEXT_TAG (temp_tags->data); + int margin; + + g_object_get (tag, + "right-margin-set", &val_set, + "right-margin", &margin, + NULL); + if (val_set) + { + char *value = g_strdup_printf ("%i", margin); + g_variant_builder_add (builder, "{ss}", "right-margin", value); + g_free (value); + } + temp_tags = temp_tags->next; + } + val_set = FALSE; + + temp_tags = tags; + while (temp_tags && !val_set) + { + GtkTextTag *tag = GTK_TEXT_TAG (temp_tags->data); + int margin; + + g_object_get (tag, + "left-margin-set", &val_set, + "left-margin", &margin, + NULL); + if (val_set) + { + char *value = g_strdup_printf ("%i", margin); + g_variant_builder_add (builder, "{ss}", "left-margin", value); + g_free (value); + } + temp_tags = temp_tags->next; + } + val_set = FALSE; + + g_slist_free (tags); +} + +char * +gtk_text_view_get_text_before (GtkTextView *view, + int offset, + AtspiTextBoundaryType boundary_type, + int *start_offset, + int *end_offset) +{ + GtkTextBuffer *buffer; + GtkTextIter pos, start, end; + + buffer = gtk_text_view_get_buffer (view); + gtk_text_buffer_get_iter_at_offset (buffer, &pos, offset); + start = end = pos; + + switch (boundary_type) + { + case ATSPI_TEXT_BOUNDARY_CHAR: + gtk_text_iter_backward_char (&start); + break; + + case ATSPI_TEXT_BOUNDARY_WORD_START: + if (!gtk_text_iter_starts_word (&start)) + gtk_text_iter_backward_word_start (&start); + end = start; + gtk_text_iter_backward_word_start (&start); + break; + + case ATSPI_TEXT_BOUNDARY_WORD_END: + if (gtk_text_iter_inside_word (&start) && + !gtk_text_iter_starts_word (&start)) + gtk_text_iter_backward_word_start (&start); + while (!gtk_text_iter_ends_word (&start)) + { + if (!gtk_text_iter_backward_char (&start)) + break; + } + end = start; + gtk_text_iter_backward_word_start (&start); + while (!gtk_text_iter_ends_word (&start)) + { + if (!gtk_text_iter_backward_char (&start)) + break; + } + break; + + case ATSPI_TEXT_BOUNDARY_SENTENCE_START: + if (!gtk_text_iter_starts_sentence (&start)) + gtk_text_iter_backward_sentence_start (&start); + end = start; + gtk_text_iter_backward_sentence_start (&start); + break; + + case ATSPI_TEXT_BOUNDARY_SENTENCE_END: + if (gtk_text_iter_inside_sentence (&start) && + !gtk_text_iter_starts_sentence (&start)) + gtk_text_iter_backward_sentence_start (&start); + while (!gtk_text_iter_ends_sentence (&start)) + { + if (!gtk_text_iter_backward_char (&start)) + break; + } + end = start; + gtk_text_iter_backward_sentence_start (&start); + while (!gtk_text_iter_ends_sentence (&start)) + { + if (!gtk_text_iter_backward_char (&start)) + break; + } + break; + + case ATSPI_TEXT_BOUNDARY_LINE_START: + gtk_text_view_backward_display_line_start (view, &start); + end = start; + gtk_text_view_backward_display_line (view, &start); + gtk_text_view_backward_display_line_start (view, &start); + break; + + case ATSPI_TEXT_BOUNDARY_LINE_END: + gtk_text_view_backward_display_line_start (view, &start); + if (!gtk_text_iter_is_start (&start)) + { + gtk_text_view_backward_display_line (view, &start); + end = start; + gtk_text_view_forward_display_line_end (view, &end); + if (!gtk_text_iter_is_start (&start)) + { + if (gtk_text_view_backward_display_line (view, &start)) + gtk_text_view_forward_display_line_end (view, &start); + else + gtk_text_iter_set_offset (&start, 0); + } + } + else + end = start; + break; + + default: + g_assert_not_reached (); + } + + *start_offset = gtk_text_iter_get_offset (&start); + *end_offset = gtk_text_iter_get_offset (&end); + + return gtk_text_buffer_get_slice (buffer, &start, &end, FALSE); +} + +char * +gtk_text_view_get_text_at (GtkTextView *view, + int offset, + AtspiTextBoundaryType boundary_type, + int *start_offset, + int *end_offset) +{ + GtkTextBuffer *buffer; + GtkTextIter pos, start, end; + + buffer = gtk_text_view_get_buffer (view); + gtk_text_buffer_get_iter_at_offset (buffer, &pos, offset); + start = end = pos; + + switch (boundary_type) + { + case ATSPI_TEXT_BOUNDARY_CHAR: + gtk_text_iter_forward_char (&end); + break; + + case ATSPI_TEXT_BOUNDARY_WORD_START: + if (!gtk_text_iter_starts_word (&start)) + gtk_text_iter_backward_word_start (&start); + if (gtk_text_iter_inside_word (&end)) + gtk_text_iter_forward_word_end (&end); + while (!gtk_text_iter_starts_word (&end)) + { + if (!gtk_text_iter_forward_char (&end)) + break; + } + break; + + case ATSPI_TEXT_BOUNDARY_WORD_END: + if (gtk_text_iter_inside_word (&start) && + !gtk_text_iter_starts_word (&start)) + gtk_text_iter_backward_word_start (&start); + while (!gtk_text_iter_ends_word (&start)) + { + if (!gtk_text_iter_backward_char (&start)) + break; + } + gtk_text_iter_forward_word_end (&end); + break; + + case ATSPI_TEXT_BOUNDARY_SENTENCE_START: + if (!gtk_text_iter_starts_sentence (&start)) + gtk_text_iter_backward_sentence_start (&start); + if (gtk_text_iter_inside_sentence (&end)) + gtk_text_iter_forward_sentence_end (&end); + while (!gtk_text_iter_starts_sentence (&end)) + { + if (!gtk_text_iter_forward_char (&end)) + break; + } + break; + + case ATSPI_TEXT_BOUNDARY_SENTENCE_END: + if (gtk_text_iter_inside_sentence (&start) && + !gtk_text_iter_starts_sentence (&start)) + gtk_text_iter_backward_sentence_start (&start); + while (!gtk_text_iter_ends_sentence (&start)) + { + if (!gtk_text_iter_backward_char (&start)) + break; + } + gtk_text_iter_forward_sentence_end (&end); + break; + + case ATSPI_TEXT_BOUNDARY_LINE_START: + gtk_text_view_backward_display_line_start (view, &start); + gtk_text_view_forward_display_line (view, &end); + break; + + case ATSPI_TEXT_BOUNDARY_LINE_END: + gtk_text_view_backward_display_line_start (view, &start); + if (!gtk_text_iter_is_start (&start)) + { + gtk_text_view_backward_display_line (view, &start); + gtk_text_view_forward_display_line_end (view, &start); + } + gtk_text_view_forward_display_line_end (view, &end); + break; + + default: + g_assert_not_reached (); + } + + *start_offset = gtk_text_iter_get_offset (&start); + *end_offset = gtk_text_iter_get_offset (&end); + + return gtk_text_buffer_get_slice (buffer, &start, &end, FALSE); +} + +char * +gtk_text_view_get_text_after (GtkTextView *view, + int offset, + AtspiTextBoundaryType boundary_type, + int *start_offset, + int *end_offset) +{ + GtkTextBuffer *buffer; + GtkTextIter pos, start, end; + + buffer = gtk_text_view_get_buffer (view); + gtk_text_buffer_get_iter_at_offset (buffer, &pos, offset); + start = end = pos; + + switch (boundary_type) + { + case ATSPI_TEXT_BOUNDARY_CHAR: + gtk_text_iter_forward_char (&start); + gtk_text_iter_forward_chars (&end, 2); + break; + + case ATSPI_TEXT_BOUNDARY_WORD_START: + if (gtk_text_iter_inside_word (&end)) + gtk_text_iter_forward_word_end (&end); + while (!gtk_text_iter_starts_word (&end)) + { + if (!gtk_text_iter_forward_char (&end)) + break; + } + start = end; + if (!gtk_text_iter_is_end (&end)) + { + gtk_text_iter_forward_word_end (&end); + while (!gtk_text_iter_starts_word (&end)) + { + if (!gtk_text_iter_forward_char (&end)) + break; + } + } + break; + + case ATSPI_TEXT_BOUNDARY_WORD_END: + gtk_text_iter_forward_word_end (&end); + start = end; + if (!gtk_text_iter_is_end (&end)) + gtk_text_iter_forward_word_end (&end); + break; + + case ATSPI_TEXT_BOUNDARY_SENTENCE_START: + if (gtk_text_iter_inside_sentence (&end)) + gtk_text_iter_forward_sentence_end (&end); + while (!gtk_text_iter_starts_sentence (&end)) + { + if (!gtk_text_iter_forward_char (&end)) + break; + } + start = end; + if (!gtk_text_iter_is_end (&end)) + { + gtk_text_iter_forward_sentence_end (&end); + while (!gtk_text_iter_starts_sentence (&end)) + { + if (!gtk_text_iter_forward_char (&end)) + break; + } + } + break; + + case ATSPI_TEXT_BOUNDARY_SENTENCE_END: + gtk_text_iter_forward_sentence_end (&end); + start = end; + if (!gtk_text_iter_is_end (&end)) + gtk_text_iter_forward_sentence_end (&end); + break; + + case ATSPI_TEXT_BOUNDARY_LINE_START: + gtk_text_view_forward_display_line (view, &end); + start = end; + gtk_text_view_forward_display_line (view, &end); + break; + + case ATSPI_TEXT_BOUNDARY_LINE_END: + gtk_text_view_forward_display_line_end (view, &end); + start = end; + gtk_text_view_forward_display_line (view, &end); + gtk_text_view_forward_display_line_end (view, &end); + break; + + default: + g_assert_not_reached (); + } + + *start_offset = gtk_text_iter_get_offset (&start); + *end_offset = gtk_text_iter_get_offset (&end); + + return gtk_text_buffer_get_slice (buffer, &start, &end, FALSE); +} + +char * +gtk_text_view_get_string_at (GtkTextView *view, + int offset, + AtspiTextGranularity granularity, + int *start_offset, + int *end_offset) +{ + GtkTextBuffer *buffer; + GtkTextIter pos, start, end; + + buffer = gtk_text_view_get_buffer (view); + gtk_text_buffer_get_iter_at_offset (buffer, &pos, offset); + start = end = pos; + + if (granularity == ATSPI_TEXT_GRANULARITY_CHAR) + { + gtk_text_iter_forward_char (&end); + } + else if (granularity == ATSPI_TEXT_GRANULARITY_WORD) + { + if (!gtk_text_iter_starts_word (&start)) + gtk_text_iter_backward_word_start (&start); + gtk_text_iter_forward_word_end (&end); + } + else if (granularity == ATSPI_TEXT_GRANULARITY_SENTENCE) + { + if (!gtk_text_iter_starts_sentence (&start)) + gtk_text_iter_backward_sentence_start (&start); + gtk_text_iter_forward_sentence_end (&end); + } + else if (granularity == ATSPI_TEXT_GRANULARITY_LINE) + { + if (!gtk_text_view_starts_display_line (view, &start)) + gtk_text_view_backward_display_line (view, &start); + gtk_text_view_forward_display_line_end (view, &end); + } + else if (granularity == ATSPI_TEXT_GRANULARITY_PARAGRAPH) + { + gtk_text_iter_set_line_offset (&start, 0); + gtk_text_iter_forward_to_line_end (&end); + } + + *start_offset = gtk_text_iter_get_offset (&start); + *end_offset = gtk_text_iter_get_offset (&end); + + return gtk_text_buffer_get_slice (buffer, &start, &end, FALSE); +} diff --git a/gtk/a11y/gtkatspitextbufferprivate.h b/gtk/a11y/gtkatspitextbufferprivate.h new file mode 100644 index 0000000000..fb97a94641 --- /dev/null +++ b/gtk/a11y/gtkatspitextbufferprivate.h @@ -0,0 +1,55 @@ +/* gtkatspitextbufferprivate.h: Utilities for GtkTextBuffer and AT-SPI + * + * Copyright 2020 Red Hat, Inc + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "gtkatspiprivate.h" +#include "gtktextview.h" + +G_BEGIN_DECLS + +void gtk_text_view_add_default_attributes (GtkTextView *view, + GVariantBuilder *builder); +void gtk_text_buffer_get_run_attributes (GtkTextBuffer *buffer, + GVariantBuilder *builder, + int offset, + int *start_offset, + int *end_offset); + +char *gtk_text_view_get_text_before (GtkTextView *view, + int offset, + AtspiTextBoundaryType boundary_type, + int *start_offset, + int *end_offset); +char *gtk_text_view_get_text_at (GtkTextView *view, + int offset, + AtspiTextBoundaryType boundary_type, + int *start_offset, + int *end_offset); +char *gtk_text_view_get_text_after (GtkTextView *view, + int offset, + AtspiTextBoundaryType boundary_type, + int *start_offset, + int *end_offset); +char *gtk_text_view_get_string_at (GtkTextView *view, + int offset, + AtspiTextGranularity granularity, + int *start_offset, + int *end_offset); + +G_END_DECLS diff --git a/gtk/a11y/gtkatspitextprivate.h b/gtk/a11y/gtkatspitextprivate.h new file mode 100644 index 0000000000..34ba37b065 --- /dev/null +++ b/gtk/a11y/gtkatspitextprivate.h @@ -0,0 +1,45 @@ +/* gtkatspitextprivate.h: AT-SPI Text implementation + * + * Copyright 2020 Red Hat, Inc. + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <gio/gio.h> +#include "gtkaccessible.h" + +G_BEGIN_DECLS + +const GDBusInterfaceVTable *gtk_atspi_get_text_vtable (GtkAccessible *accessible); + +typedef void (GtkAtspiTextChangedCallback) (gpointer data, + const char *kind, + int start, + int end, + const char *text); +typedef void (GtkAtspiTextSelectionCallback) (gpointer data, + const char *kind, + int position); + +void gtk_atspi_connect_text_signals (GtkAccessible *accessible, + GtkAtspiTextChangedCallback text_changed, + GtkAtspiTextSelectionCallback selection_changed, + gpointer data); +void gtk_atspi_disconnect_text_signals (GtkAccessible *accessible); + +G_END_DECLS diff --git a/gtk/a11y/gtkatspiutils.c b/gtk/a11y/gtkatspiutils.c new file mode 100644 index 0000000000..1fef628144 --- /dev/null +++ b/gtk/a11y/gtkatspiutils.c @@ -0,0 +1,307 @@ +/* gtkatspiutils.c: Shared utilities for ATSPI + * + * Copyright 2020 GNOME Foundation + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include "gtkatspiutilsprivate.h" +#include "gtkenums.h" +#include "gtkpasswordentry.h" + +/*< private > + * gtk_accessible_role_to_atspi_role: + * @role: a #GtkAccessibleRole + * + * Converts a #GtkAccessibleRole value to the equivalent ATSPI role. + * + * Returns: an #AtspiRole + */ +static AtspiRole +gtk_accessible_role_to_atspi_role (GtkAccessibleRole role) +{ + switch (role) + { + case GTK_ACCESSIBLE_ROLE_ALERT: + return ATSPI_ROLE_ALERT; + + case GTK_ACCESSIBLE_ROLE_ALERT_DIALOG: + return ATSPI_ROLE_DIALOG; + + case GTK_ACCESSIBLE_ROLE_BANNER: + break; + + case GTK_ACCESSIBLE_ROLE_BUTTON: + return ATSPI_ROLE_PUSH_BUTTON; + + case GTK_ACCESSIBLE_ROLE_CAPTION: + return ATSPI_ROLE_CAPTION; + + case GTK_ACCESSIBLE_ROLE_CELL: + return ATSPI_ROLE_TABLE_CELL; + + case GTK_ACCESSIBLE_ROLE_CHECKBOX: + return ATSPI_ROLE_CHECK_BOX; + + case GTK_ACCESSIBLE_ROLE_COLUMN_HEADER: + break; + + case GTK_ACCESSIBLE_ROLE_COMBO_BOX: + return ATSPI_ROLE_COMBO_BOX; + + case GTK_ACCESSIBLE_ROLE_COMMAND: + break; + + case GTK_ACCESSIBLE_ROLE_COMPOSITE: + break; + + case GTK_ACCESSIBLE_ROLE_DIALOG: + return ATSPI_ROLE_DIALOG; + + case GTK_ACCESSIBLE_ROLE_DOCUMENT: + return ATSPI_ROLE_DOCUMENT_TEXT; + + case GTK_ACCESSIBLE_ROLE_FEED: + break; + + case GTK_ACCESSIBLE_ROLE_FORM: + break; + + case GTK_ACCESSIBLE_ROLE_GENERIC: + break; + + case GTK_ACCESSIBLE_ROLE_GRID: + return ATSPI_ROLE_TABLE; + + case GTK_ACCESSIBLE_ROLE_GRID_CELL: + return ATSPI_ROLE_TABLE_CELL; + + case GTK_ACCESSIBLE_ROLE_GROUP: + break; + + case GTK_ACCESSIBLE_ROLE_HEADING: + break; + + case GTK_ACCESSIBLE_ROLE_IMG: + return ATSPI_ROLE_IMAGE; + + case GTK_ACCESSIBLE_ROLE_INPUT: + return ATSPI_ROLE_ENTRY; + + case GTK_ACCESSIBLE_ROLE_LABEL: + return ATSPI_ROLE_LABEL; + + case GTK_ACCESSIBLE_ROLE_LANDMARK: + break; + + case GTK_ACCESSIBLE_ROLE_LEGEND: + break; + + case GTK_ACCESSIBLE_ROLE_LINK: + return ATSPI_ROLE_LINK; + + case GTK_ACCESSIBLE_ROLE_LIST: + return ATSPI_ROLE_LIST; + + case GTK_ACCESSIBLE_ROLE_LIST_BOX: + return ATSPI_ROLE_LIST_BOX; + + case GTK_ACCESSIBLE_ROLE_LIST_ITEM: + return ATSPI_ROLE_LIST_ITEM; + + case GTK_ACCESSIBLE_ROLE_LOG: + return ATSPI_ROLE_LOG; + + case GTK_ACCESSIBLE_ROLE_MAIN: + break; + + case GTK_ACCESSIBLE_ROLE_MARQUEE: + return ATSPI_ROLE_MARQUEE; + + case GTK_ACCESSIBLE_ROLE_MATH: + return ATSPI_ROLE_MATH;; + + case GTK_ACCESSIBLE_ROLE_METER: + return ATSPI_ROLE_LEVEL_BAR; + + case GTK_ACCESSIBLE_ROLE_MENU: + return ATSPI_ROLE_MENU; + + case GTK_ACCESSIBLE_ROLE_MENU_BAR: + return ATSPI_ROLE_MENU_BAR; + + case GTK_ACCESSIBLE_ROLE_MENU_ITEM: + return ATSPI_ROLE_MENU_ITEM; + + case GTK_ACCESSIBLE_ROLE_MENU_ITEM_CHECKBOX: + return ATSPI_ROLE_CHECK_MENU_ITEM; + + case GTK_ACCESSIBLE_ROLE_MENU_ITEM_RADIO: + return ATSPI_ROLE_RADIO_MENU_ITEM; + + case GTK_ACCESSIBLE_ROLE_NAVIGATION: + return ATSPI_ROLE_FILLER; + + case GTK_ACCESSIBLE_ROLE_NONE: + return ATSPI_ROLE_INVALID; + + case GTK_ACCESSIBLE_ROLE_NOTE: + return ATSPI_ROLE_FOOTNOTE; + + case GTK_ACCESSIBLE_ROLE_OPTION: + return ATSPI_ROLE_OPTION_PANE; + + case GTK_ACCESSIBLE_ROLE_PRESENTATION: + return ATSPI_ROLE_DOCUMENT_PRESENTATION; + + case GTK_ACCESSIBLE_ROLE_PROGRESS_BAR: + return ATSPI_ROLE_PROGRESS_BAR; + + case GTK_ACCESSIBLE_ROLE_RADIO: + return ATSPI_ROLE_RADIO_BUTTON; + + case GTK_ACCESSIBLE_ROLE_RADIO_GROUP: + return ATSPI_ROLE_GROUPING; + + case GTK_ACCESSIBLE_ROLE_RANGE: + break; + + case GTK_ACCESSIBLE_ROLE_REGION: + return ATSPI_ROLE_FILLER; + + case GTK_ACCESSIBLE_ROLE_ROW: + return ATSPI_ROLE_TABLE_ROW; + + case GTK_ACCESSIBLE_ROLE_ROW_GROUP: + return ATSPI_ROLE_GROUPING; + + case GTK_ACCESSIBLE_ROLE_ROW_HEADER: + return ATSPI_ROLE_ROW_HEADER; + + case GTK_ACCESSIBLE_ROLE_SCROLLBAR: + return ATSPI_ROLE_SCROLL_BAR; + + case GTK_ACCESSIBLE_ROLE_SEARCH: + return ATSPI_ROLE_FORM; + + case GTK_ACCESSIBLE_ROLE_SEARCH_BOX: + return ATSPI_ROLE_ENTRY; + + case GTK_ACCESSIBLE_ROLE_SECTION: + return ATSPI_ROLE_FILLER; + + case GTK_ACCESSIBLE_ROLE_SECTION_HEAD: + return ATSPI_ROLE_FILLER; + + case GTK_ACCESSIBLE_ROLE_SELECT: + return ATSPI_ROLE_FILLER; + + case GTK_ACCESSIBLE_ROLE_SEPARATOR: + return ATSPI_ROLE_SEPARATOR; + + case GTK_ACCESSIBLE_ROLE_SLIDER: + return ATSPI_ROLE_SLIDER; + + case GTK_ACCESSIBLE_ROLE_SPIN_BUTTON: + return ATSPI_ROLE_SPIN_BUTTON; + + case GTK_ACCESSIBLE_ROLE_STATUS: + return ATSPI_ROLE_STATUS_BAR; + + case GTK_ACCESSIBLE_ROLE_STRUCTURE: + return ATSPI_ROLE_FILLER; + + case GTK_ACCESSIBLE_ROLE_SWITCH: + return ATSPI_ROLE_CHECK_BOX; + + case GTK_ACCESSIBLE_ROLE_TAB: + return ATSPI_ROLE_PAGE_TAB; + + case GTK_ACCESSIBLE_ROLE_TABLE: + return ATSPI_ROLE_TABLE; + + case GTK_ACCESSIBLE_ROLE_TAB_LIST: + return ATSPI_ROLE_PAGE_TAB_LIST; + + case GTK_ACCESSIBLE_ROLE_TAB_PANEL: + return ATSPI_ROLE_PANEL; + + case GTK_ACCESSIBLE_ROLE_TEXT_BOX: + return ATSPI_ROLE_TEXT; + + case GTK_ACCESSIBLE_ROLE_TIME: + return ATSPI_ROLE_TEXT; + + case GTK_ACCESSIBLE_ROLE_TIMER: + return ATSPI_ROLE_TIMER; + + case GTK_ACCESSIBLE_ROLE_TOOLBAR: + return ATSPI_ROLE_TOOL_BAR; + + case GTK_ACCESSIBLE_ROLE_TOOLTIP: + return ATSPI_ROLE_TOOL_TIP; + + case GTK_ACCESSIBLE_ROLE_TREE: + return ATSPI_ROLE_TREE; + + case GTK_ACCESSIBLE_ROLE_TREE_GRID: + return ATSPI_ROLE_TREE_TABLE; + + case GTK_ACCESSIBLE_ROLE_TREE_ITEM: + return ATSPI_ROLE_TREE_ITEM; + + case GTK_ACCESSIBLE_ROLE_WIDGET: + return ATSPI_ROLE_FILLER; + + case GTK_ACCESSIBLE_ROLE_WINDOW: + return ATSPI_ROLE_WINDOW; + + default: + break; + } + + return ATSPI_ROLE_FILLER; +} + +/*<private> + * gtk_atspi_role_for_context: + * @context: a #GtkATContext + * + * Returns a suitable ATSPI role for a context, taking into account + * both the #GtkAccessibleRole set on the context and the type + * of accessible. + * + * Returns: an #AtspiRole + */ +AtspiRole +gtk_atspi_role_for_context (GtkATContext *context) +{ + GtkAccessible *accessible = gtk_at_context_get_accessible (context); + GtkAccessibleRole role = gtk_at_context_get_accessible_role (context); + + if (GTK_IS_PASSWORD_ENTRY (accessible)) + return ATSPI_ROLE_PASSWORD_TEXT; + + return gtk_accessible_role_to_atspi_role (role); +} + +GVariant * +gtk_at_spi_null_ref (void) +{ + return g_variant_new ("(so)", "", "/org/a11y/atspi/null"); +} diff --git a/gtk/a11y/gtkatspiutilsprivate.h b/gtk/a11y/gtkatspiutilsprivate.h new file mode 100644 index 0000000000..20b0b09305 --- /dev/null +++ b/gtk/a11y/gtkatspiutilsprivate.h @@ -0,0 +1,34 @@ +/* gtkatspiutilsprivate.h: Shared utilities for AT-SPI + * + * Copyright 2020 GNOME Foundation + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "gtkatspiprivate.h" +#include "gtkatcontextprivate.h" + +G_BEGIN_DECLS + +AtspiRole +gtk_atspi_role_for_context (GtkATContext *context); + +GVariant * +gtk_at_spi_null_ref (void); + +G_END_DECLS diff --git a/gtk/a11y/gtkatspivalue.c b/gtk/a11y/gtkatspivalue.c new file mode 100644 index 0000000000..269d27e61a --- /dev/null +++ b/gtk/a11y/gtkatspivalue.c @@ -0,0 +1,129 @@ +/* gtkatspivalue.c: AT-SPI Value implementation + * + * Copyright 2020 Red Hat, Inc. + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include "gtkatspivalueprivate.h" + +#include "a11y/atspi/atspi-value.h" + +#include "gtkatcontextprivate.h" +#include "gtkdebug.h" +#include "gtklevelbar.h" +#include "gtkpaned.h" +#include "gtkprogressbar.h" +#include "gtkrange.h" +#include "gtkscalebutton.h" +#include "gtkspinbutton.h" + +#include <gio/gio.h> + +static GVariant * +handle_value_get_property (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *property_name, + GError **error, + gpointer user_data) +{ + GtkATContext *ctx = GTK_AT_CONTEXT (user_data); + struct { + const char *name; + GtkAccessibleProperty property; + } properties[] = { + { "MinimumValue", GTK_ACCESSIBLE_PROPERTY_VALUE_MIN }, + { "MaximumValue", GTK_ACCESSIBLE_PROPERTY_VALUE_MAX }, + { "CurrentValue", GTK_ACCESSIBLE_PROPERTY_VALUE_NOW }, + }; + int i; + + for (i = 0; i < G_N_ELEMENTS (properties); i++) + { + if (g_strcmp0 (property_name, properties[i].name) == 0) + { + if (gtk_at_context_has_accessible_property (ctx, properties[i].property)) + { + GtkAccessibleValue *value; + + value = gtk_at_context_get_accessible_property (ctx, properties[i].property); + return g_variant_new_double (gtk_number_accessible_value_get (value)); + } + } + } + + /* fall back for a) MinimumIncrement b) widgets that should have the + * properties but don't + */ + return g_variant_new_double (0.0); +} + +static gboolean +handle_value_set_property (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *property_name, + GVariant *value, + GError **error, + gpointer user_data) +{ + GtkATContext *self = user_data; + GtkWidget *widget = GTK_WIDGET (gtk_at_context_get_accessible (self)); + + if (g_strcmp0 (property_name, "CurrentValue") == 0) + { + /* we only allow setting values if that is part of the user-exposed + * functionality of the widget. + */ + if (GTK_IS_RANGE (widget)) + gtk_range_set_value (GTK_RANGE (widget), g_variant_get_double (value)); + else if (GTK_IS_PANED (widget)) + gtk_paned_set_position (GTK_PANED (widget), (int)(g_variant_get_double (value) + 0.5)); + else if (GTK_IS_SPIN_BUTTON (widget)) + gtk_spin_button_set_value (GTK_SPIN_BUTTON (widget), g_variant_get_double (value)); + else if (GTK_IS_SCALE_BUTTON (widget)) + gtk_scale_button_set_value (GTK_SCALE_BUTTON (widget), g_variant_get_double (value)); + return TRUE; + } + + return FALSE; +} + +static const GDBusInterfaceVTable value_vtable = { + NULL, + handle_value_get_property, + handle_value_set_property, +}; + +const GDBusInterfaceVTable * +gtk_atspi_get_value_vtable (GtkAccessible *accessible) +{ + if (GTK_IS_LEVEL_BAR (accessible) || + GTK_IS_PANED (accessible) || + GTK_IS_PROGRESS_BAR (accessible) || + GTK_IS_RANGE (accessible) || + GTK_IS_SCALE_BUTTON (accessible) || + GTK_IS_SPIN_BUTTON (accessible)) + return &value_vtable; + + return NULL; +} + diff --git a/gtk/a11y/gtkatspivalueprivate.h b/gtk/a11y/gtkatspivalueprivate.h new file mode 100644 index 0000000000..de882f118e --- /dev/null +++ b/gtk/a11y/gtkatspivalueprivate.h @@ -0,0 +1,30 @@ +/* gtkatspivalueprivate.h: AT-SPI Value implementation + * + * Copyright 2020 Red Hat, Inc. + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <gio/gio.h> +#include "gtkaccessible.h" + +G_BEGIN_DECLS + +const GDBusInterfaceVTable *gtk_atspi_get_value_vtable (GtkAccessible *accessible); + +G_END_DECLS diff --git a/gtk/a11y/meson.build b/gtk/a11y/meson.build new file mode 100644 index 0000000000..49359159ae --- /dev/null +++ b/gtk/a11y/meson.build @@ -0,0 +1,23 @@ +gtk_a11y_src = [] +gtk_a11y_backends = [] + +if os_unix + gtk_a11y_backends += 'atspi' +endif + +if gtk_a11y_backends.contains('atspi') + subdir('atspi') + + gtk_a11y_src += files([ + 'gtkatspicache.c', + 'gtkatspicontext.c', + 'gtkatspiroot.c', + 'gtkatspiutils.c', + 'gtkatspipango.c', + 'gtkatspitextbuffer.c', + 'gtkatspitext.c', + 'gtkatspivalue.c', + 'gtkatspieditabletext.c', + 'gtkatspiselection.c', + ]) +endif |