diff options
112 files changed, 10821 insertions, 161 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 166d77e47b..5607e2e1aa 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -99,6 +99,7 @@ installed-tests: needs: [] variables: EXTRA_MESON_FLAGS: "--prefix=/usr --libdir=/usr/lib64 -Dinstall-tests=true" + G_TEST_ACCESSIBLE: 1 script: - meson subprojects update - meson ${COMMON_MESON_FLAGS} ${EXTRA_MESON_FLAGS} ${BACKEND_FLAGS} ${FEATURE_FLAGS} diff --git a/docs/reference/gtk/section-accessibility.md b/docs/reference/gtk/section-accessibility.md index ade43a1bfa..ecd3dacd26 100644 --- a/docs/reference/gtk/section-accessibility.md +++ b/docs/reference/gtk/section-accessibility.md @@ -48,18 +48,28 @@ Each role name is part of the #GtkAccessibleRole enumeration. | `BUTTON` | A control that performs an action when pressed | #GtkButton, #GtkLinkButton, #GtkExpander | | `CHECKBOX` | A control that has three possible value: `true`, `false`, or `undefined` | #GtkCheckButton | | `COMBOBOX` | A control that can be expanded to show a list of possible values to select | #GtkComboBox | +| `COLUMN_HEADER` | A header in a columned list | #GtkColumnView | | `DIALOG` | A dialog that prompts the user to enter information or require a response | #GtkDialog and subclasses | +| `GRID` | A grid of items | #GtkFlowBox, #GtkGridView | +| `GRID_CELL` | An item in a grid | #GtkFlowBoxChild, #GtkGridView, #GtkColumnView | | `IMG` | An image | #GtkImage, #GtkPicture | | `LABEL` | A visible name or caption for a user interface component | #GtkLabel | +| `LIST` | A list of items | #GtkListBox | +| `LIST_ITEM` | An item in a list | #GtkListBoxRow | | `METER` | Represents a value within a known range | #GtkLevelBar | | `PROGRESS_BAR` | An element that display progress | #GtkProgressBar | | `RADIO` | A checkable input in a group of radio roles | #GtkCheckButton | +| `ROW` | A row in a columned list | #GtkColumnView | | `SCROLLBAR` | A graphical object controlling the scrolling of content | #GtkScrollbar | | `SEARCH_BOX` | A text box for entering search criteria | #GtkSearchEntry | | `SEPARATOR` | A divider that separates sections of content or groups of items | #GtkSeparator | | `SPIN_BUTTON` | A range control that allows seelcting among discrete choices | #GtkSpinButton | | `SWITCH` | A control that represents on/off values | #GtkSwitch | +| `TAB` | A tab in a list of tabs for switching pages | #GtkStackSwitcher, #GtkNotebook | +| `TAB_LIST` | A list of tabs for switching pages | #GtkStackSwitcher, #GtkNotebook | +| `TAB_PANEL` | A page in a notebook or stack | #GtkStack | | `TEXT_BOX` | A type of input that allows free-form text as its value. | #GtkEntry, #GtkPasswordEntry, #GtkTextView | +| `TREE_GRID` | A treeview-like columned list | #GtkColumnView | | `WINDOW` | An application window | #GtkWindow | | `...` | … | 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 diff --git a/gtk/gtk-autocleanups.h b/gtk/gtk-autocleanups.h index 77c6cd5a56..127a63c4a8 100644 --- a/gtk/gtk-autocleanups.h +++ b/gtk/gtk-autocleanups.h @@ -102,7 +102,6 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC(GtkInfoBar, g_object_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC(GtkLevelBar, g_object_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC(GtkLinkButton, g_object_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC(GtkListStore, g_object_unref) -G_DEFINE_AUTOPTR_CLEANUP_FUNC(GtkListView, g_object_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC(GtkLockButton, g_object_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC(GtkMenuButton, g_object_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC(GtkMessageDialog, g_object_unref) diff --git a/gtk/gtkaccessible.c b/gtk/gtkaccessible.c index 0c9a399ef0..582cdf08fd 100644 --- a/gtk/gtkaccessible.c +++ b/gtk/gtkaccessible.c @@ -47,6 +47,9 @@ #include "gtkatcontextprivate.h" #include "gtkenums.h" #include "gtktypebuiltins.h" +#include "gtkwidget.h" + +#include <glib/gi18n-lib.h> #include <stdarg.h> @@ -532,3 +535,192 @@ gtk_accessible_reset_relation (GtkAccessible *self, gtk_at_context_set_accessible_relation (context, relation, NULL); gtk_at_context_update (context); } + +static const char *role_names[] = { + [GTK_ACCESSIBLE_ROLE_ALERT] = NC_("accessibility", "alert"), + [GTK_ACCESSIBLE_ROLE_ALERT_DIALOG] = NC_("accessibility", "alert dialog"), + [GTK_ACCESSIBLE_ROLE_BANNER] = NC_("accessibility", "banner"), + [GTK_ACCESSIBLE_ROLE_BUTTON] = NC_("accessibility", "button"), + [GTK_ACCESSIBLE_ROLE_CAPTION] = NC_("accessibility", "caption"), + [GTK_ACCESSIBLE_ROLE_CELL] = NC_("accessibility", "cell"), + [GTK_ACCESSIBLE_ROLE_CHECKBOX] = NC_("accessibility", "checkbox"), + [GTK_ACCESSIBLE_ROLE_COLUMN_HEADER] = NC_("accessibility", "column header"), + [GTK_ACCESSIBLE_ROLE_COMBO_BOX] = NC_("accessibility", "combo box"), + [GTK_ACCESSIBLE_ROLE_COMMAND] = NC_("accessibility", "command"), + [GTK_ACCESSIBLE_ROLE_COMPOSITE] = NC_("accessibility", "composite"), + [GTK_ACCESSIBLE_ROLE_DIALOG] = NC_("accessibility", "dialog"), + [GTK_ACCESSIBLE_ROLE_DOCUMENT] = NC_("accessibility", "document"), + [GTK_ACCESSIBLE_ROLE_FEED] = NC_("accessibility", "feed"), + [GTK_ACCESSIBLE_ROLE_FORM] = NC_("accessibility", "form"), + [GTK_ACCESSIBLE_ROLE_GENERIC] = NC_("accessibility", "generic"), + [GTK_ACCESSIBLE_ROLE_GRID] = NC_("accessibility", "grid"), + [GTK_ACCESSIBLE_ROLE_GRID_CELL] = NC_("accessibility", "grid cell"), + [GTK_ACCESSIBLE_ROLE_GROUP] = NC_("accessibility", "group"), + [GTK_ACCESSIBLE_ROLE_HEADING] = NC_("accessibility", "heading"), + [GTK_ACCESSIBLE_ROLE_IMG] = NC_("accessibility", "image"), + [GTK_ACCESSIBLE_ROLE_INPUT] = NC_("accessibility", "input"), + [GTK_ACCESSIBLE_ROLE_LABEL] = NC_("accessibility", "label"), + [GTK_ACCESSIBLE_ROLE_LANDMARK] = NC_("accessibility", "landmark"), + [GTK_ACCESSIBLE_ROLE_LEGEND] = NC_("accessibility", "legend"), + [GTK_ACCESSIBLE_ROLE_LINK] = NC_("accessibility", "link"), + [GTK_ACCESSIBLE_ROLE_LIST] = NC_("accessibility", "list"), + [GTK_ACCESSIBLE_ROLE_LIST_BOX] = NC_("accessibility", "list box"), + [GTK_ACCESSIBLE_ROLE_LIST_ITEM] = NC_("accessibility", "list item"), + [GTK_ACCESSIBLE_ROLE_LOG] = NC_("accessibility", "log"), + [GTK_ACCESSIBLE_ROLE_MAIN] = NC_("accessibility", "main"), + [GTK_ACCESSIBLE_ROLE_MARQUEE] = NC_("accessibility", "marquee"), + [GTK_ACCESSIBLE_ROLE_MATH] = NC_("accessibility", "math"), + [GTK_ACCESSIBLE_ROLE_METER] = NC_("accessibility", "meter"), + [GTK_ACCESSIBLE_ROLE_MENU] = NC_("accessibility", "menu"), + [GTK_ACCESSIBLE_ROLE_MENU_BAR] = NC_("accessibility", "menu bar"), + [GTK_ACCESSIBLE_ROLE_MENU_ITEM] = NC_("accessibility", "menu item"), + [GTK_ACCESSIBLE_ROLE_MENU_ITEM_CHECKBOX] = NC_("accessibility", "menu item checkbox"), + [GTK_ACCESSIBLE_ROLE_MENU_ITEM_RADIO] = NC_("accessibility", "menu item radio"), + [GTK_ACCESSIBLE_ROLE_NAVIGATION] = NC_("accessibility", "navigation"), + [GTK_ACCESSIBLE_ROLE_NONE] = NC_("accessibility", "none"), + [GTK_ACCESSIBLE_ROLE_NOTE] = NC_("accessibility", "note"), + [GTK_ACCESSIBLE_ROLE_OPTION] = NC_("accessibility", "option"), + [GTK_ACCESSIBLE_ROLE_PRESENTATION] = NC_("accessibility", "presentation"), + [GTK_ACCESSIBLE_ROLE_PROGRESS_BAR] = NC_("accessibility", "progress bar"), + [GTK_ACCESSIBLE_ROLE_RADIO] = NC_("accessibility", "radio"), + [GTK_ACCESSIBLE_ROLE_RADIO_GROUP] = NC_("accessibility", "radio group"), + [GTK_ACCESSIBLE_ROLE_RANGE] = NC_("accessibility", "range"), + [GTK_ACCESSIBLE_ROLE_REGION] = NC_("accessibility", "region"), + [GTK_ACCESSIBLE_ROLE_ROW] = NC_("accessibility", "row"), + [GTK_ACCESSIBLE_ROLE_ROW_GROUP] = NC_("accessibility", "row group"), + [GTK_ACCESSIBLE_ROLE_ROW_HEADER] = NC_("accessibility", "row header"), + [GTK_ACCESSIBLE_ROLE_SCROLLBAR] = NC_("accessibility", "scroll bar"), + [GTK_ACCESSIBLE_ROLE_SEARCH] = NC_("accessibility", "search"), + [GTK_ACCESSIBLE_ROLE_SEARCH_BOX] = NC_("accessibility", "search box"), + [GTK_ACCESSIBLE_ROLE_SECTION] = NC_("accessibility", "section"), + [GTK_ACCESSIBLE_ROLE_SECTION_HEAD] = NC_("accessibility", "section head"), + [GTK_ACCESSIBLE_ROLE_SELECT] = NC_("accessibility", "select"), + [GTK_ACCESSIBLE_ROLE_SEPARATOR] = NC_("accessibility", "separator"), + [GTK_ACCESSIBLE_ROLE_SLIDER] = NC_("accessibility", "slider"), + [GTK_ACCESSIBLE_ROLE_SPIN_BUTTON] = NC_("accessibility", "spin button"), + [GTK_ACCESSIBLE_ROLE_STATUS] = NC_("accessibility", "status"), + [GTK_ACCESSIBLE_ROLE_STRUCTURE] = NC_("accessibility", "structure"), + [GTK_ACCESSIBLE_ROLE_SWITCH] = NC_("accessibility", "switch"), + [GTK_ACCESSIBLE_ROLE_TAB] = NC_("accessibility", "tab"), + [GTK_ACCESSIBLE_ROLE_TABLE] = NC_("accessibility", "table"), + [GTK_ACCESSIBLE_ROLE_TAB_LIST] = NC_("accessibility", "tab list"), + [GTK_ACCESSIBLE_ROLE_TAB_PANEL] = NC_("accessibility", "tab panel"), + [GTK_ACCESSIBLE_ROLE_TEXT_BOX] = NC_("accessibility", "text box"), + [GTK_ACCESSIBLE_ROLE_TIME] = NC_("accessibility", "time"), + [GTK_ACCESSIBLE_ROLE_TIMER] = NC_("accessibility", "timer"), + [GTK_ACCESSIBLE_ROLE_TOOLBAR] = NC_("accessibility", "tool bar"), + [GTK_ACCESSIBLE_ROLE_TOOLTIP] = NC_("accessibility", "tool tip"), + [GTK_ACCESSIBLE_ROLE_TREE] = NC_("accessibility", "tree"), + [GTK_ACCESSIBLE_ROLE_TREE_GRID] = NC_("accessibility", "tree grid"), + [GTK_ACCESSIBLE_ROLE_TREE_ITEM] = NC_("accessibility", "tree item"), + [GTK_ACCESSIBLE_ROLE_WIDGET] = NC_("accessibility", "widget"), + [GTK_ACCESSIBLE_ROLE_WINDOW] = NC_("accessibility", "window"), +}; + +/*< private > + * gtk_accessible_role_to_name: + * @role: a #GtkAccessibleRole + * @domain: (nullable): the translation domain + * + * Converts a #GtkAccessibleRole value to the equivalent role name. + * + * If @domain is not %NULL, the returned string will be localized. + * + * Returns: (transfer none): the name of the role + */ +const char * +gtk_accessible_role_to_name (GtkAccessibleRole role, + const char *domain) +{ + if (domain != NULL) + return g_dpgettext2 (domain, "accessibility", role_names[role]); + + return role_names[role]; +} + +/*<private> + * gtk_accessible_platform_changed: + * @self: a #GtkAccessible + * @change: the platform state change to report + * + * ARIA discriminates between author-controlled states + * and 'platform' states, which are not. This function + * can be used by widgets to inform ATs that a platform + * state, such as focus, has changed. + * + * Note that the state itself is not included in this API. + * AT backends should use gtk_accessible_get_platform_state() + * to obtain the actual state. + */ +void +gtk_accessible_platform_changed (GtkAccessible *self, + GtkAccessiblePlatformChange change) +{ + GtkATContext *context; + + if (GTK_IS_WIDGET (self) && + gtk_widget_get_root (GTK_WIDGET (self)) == NULL) + return; + + context = gtk_accessible_get_at_context (self); + + /* propagate changes up from ignored widgets */ + if (gtk_accessible_get_accessible_role (self) == GTK_ACCESSIBLE_ROLE_NONE) + { + context = gtk_accessible_get_at_context (GTK_ACCESSIBLE (gtk_widget_get_parent (GTK_WIDGET (self)))); + } + + if (context == NULL) + return; + + gtk_at_context_platform_changed (context, change); + gtk_at_context_update (context); +} + +/*<private> + * gtk_accessible_get_platform_state: + * @self: a #GtkAccessible + * @state: platform state to query + * + * Query a platform state, such as focus. + * + * See gtk_accessible_platform_changed(). + * + * This functionality can be overridden by #GtkAccessible + * implementations, e.g. to get platform state from an ignored + * child widget, as is the case for #GtkText wrappers. + * + * Returns: the value of @state for the accessible + */ +gboolean +gtk_accessible_get_platform_state (GtkAccessible *self, + GtkAccessiblePlatformState state) +{ + return GTK_ACCESSIBLE_GET_IFACE (self)->get_platform_state (self, state); +} + +/*<private> + * gtk_accessible_should_present: + * @self: a #GtkAccessible + * + * Returns whether this accessible should be represented to ATs. + * + * By default, hidden widgets are are among these, but there can + * be other reasons to return %FALSE, e.g. for widgets that are + * purely presentations, or for widgets whose functionality is + * represented elsewhere, as is the case for #GtkText widgets. + * + * Returns: %TRUE if the widget should be represented + */ +gboolean +gtk_accessible_should_present (GtkAccessible *self) +{ + if (GTK_IS_WIDGET (self) && + !gtk_widget_get_visible (GTK_WIDGET (self))) + return FALSE; + + if (gtk_accessible_get_accessible_role (self) == GTK_ACCESSIBLE_ROLE_NONE) + return FALSE; + + return TRUE; +} diff --git a/gtk/gtkaccessibleattributesetprivate.h b/gtk/gtkaccessibleattributesetprivate.h index 83694c7ede..bce94d6876 100644 --- a/gtk/gtkaccessibleattributesetprivate.h +++ b/gtk/gtkaccessibleattributesetprivate.h @@ -20,7 +20,7 @@ #pragma once -#include "gtkaccessibleprivate.h" +#include "gtkaccessible.h" #include "gtkaccessiblevalueprivate.h" G_BEGIN_DECLS diff --git a/gtk/gtkaccessibleprivate.h b/gtk/gtkaccessibleprivate.h index aa5701cf50..4d1f458fb8 100644 --- a/gtk/gtkaccessibleprivate.h +++ b/gtk/gtkaccessibleprivate.h @@ -21,7 +21,7 @@ #pragma once #include "gtkaccessible.h" -#include "gtkatcontext.h" +#include "gtkatcontextprivate.h" G_BEGIN_DECLS @@ -30,8 +30,21 @@ struct _GtkAccessibleInterface GTypeInterface g_iface; GtkATContext * (* get_at_context) (GtkAccessible *self); + + gboolean (* get_platform_state) (GtkAccessible *self, + GtkAccessiblePlatformState state); }; GtkATContext * gtk_accessible_get_at_context (GtkAccessible *self); +const char * gtk_accessible_role_to_name (GtkAccessibleRole role, + const char *domain); + +void gtk_accessible_platform_changed (GtkAccessible *self, + GtkAccessiblePlatformChange change); +gboolean gtk_accessible_get_platform_state (GtkAccessible *self, + GtkAccessiblePlatformState state); + +gboolean gtk_accessible_should_present (GtkAccessible *self); + G_END_DECLS diff --git a/gtk/gtkatcontext.c b/gtk/gtkatcontext.c index 6686e3f9a9..de9dc79275 100644 --- a/gtk/gtkatcontext.c +++ b/gtk/gtkatcontext.c @@ -36,15 +36,22 @@ #include "gtkatcontextprivate.h" #include "gtkaccessiblevalueprivate.h" +#include "gtkaccessibleprivate.h" +#include "gtkdebug.h" #include "gtktestatcontextprivate.h" #include "gtktypebuiltins.h" +#if defined(GDK_WINDOWING_X11) || defined(GDK_WINDOWING_WAYLAND) +#include "a11y/gtkatspicontextprivate.h" +#endif + G_DEFINE_ABSTRACT_TYPE (GtkATContext, gtk_at_context, G_TYPE_OBJECT) enum { PROP_ACCESSIBLE_ROLE = 1, PROP_ACCESSIBLE, + PROP_DISPLAY, N_PROPS }; @@ -90,6 +97,10 @@ gtk_at_context_set_property (GObject *gobject, self->accessible = g_value_get_object (value); break; + case PROP_DISPLAY: + self->display = g_value_get_object (value); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); } @@ -113,6 +124,10 @@ gtk_at_context_get_property (GObject *gobject, g_value_set_object (value, self->accessible); break; + case PROP_DISPLAY: + g_value_set_object (value, self->display); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); } @@ -123,6 +138,7 @@ gtk_at_context_real_state_change (GtkATContext *self, GtkAccessibleStateChange changed_states, GtkAccessiblePropertyChange changed_properties, GtkAccessibleRelationChange changed_relations, + GtkAccessiblePlatformChange changed_platform, GtkAccessibleAttributeSet *states, GtkAccessibleAttributeSet *properties, GtkAccessibleAttributeSet *relations) @@ -173,6 +189,20 @@ gtk_at_context_class_init (GtkATContextClass *klass) G_PARAM_STATIC_STRINGS); /** + * GtkATContext:display: + * + * The #GdkDisplay for the #GtkATContext. + */ + obj_props[PROP_DISPLAY] = + g_param_spec_object ("display", + "Display", + "The display connection", + GDK_TYPE_DISPLAY, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + + /** * GtkATContext::state-change: * @self: the #GtkATContext * @@ -354,13 +384,45 @@ gtk_at_context_get_accessible_role (GtkATContext *self) return self->accessible_role; } +/*< private > + * gtk_at_context_get_display: + * @self: a #GtkATContext + * + * Retrieves the #GdkDisplay used to create the context. + * + * Returns: (transfer none): a #GdkDisplay + */ +GdkDisplay * +gtk_at_context_get_display (GtkATContext *self) +{ + g_return_val_if_fail (GTK_IS_AT_CONTEXT (self), NULL); + + return self->display; +} + +static const struct { + const char *name; + GtkATContext * (* create_context) (GtkAccessibleRole accessible_role, + GtkAccessible *accessible, + GdkDisplay *display); +} a11y_backends[] = { +#if defined(GDK_WINDOWING_WAYLAND) + { "AT-SPI (Wayland)", gtk_at_spi_create_context }, +#endif +#if defined(GDK_WINDOWING_X11) + { "AT-SPI (X11)", gtk_at_spi_create_context }, +#endif + { NULL, NULL }, +}; + /** * gtk_at_context_create: (constructor) * @accessible_role: the accessible role used by the #GtkATContext * @accessible: the #GtkAccessible implementation using the #GtkATContext + * @display: the #GdkDisplay used by the #GtkATContext * - * Creates a new #GtkATContext instance for the given accessible role and - * accessible instance. + * Creates a new #GtkATContext instance for the given accessible role, + * accessible instance, and display connection. * * The #GtkATContext implementation being instantiated will depend on the * platform. @@ -369,7 +431,8 @@ gtk_at_context_get_accessible_role (GtkATContext *self) */ GtkATContext * gtk_at_context_create (GtkAccessibleRole accessible_role, - GtkAccessible *accessible) + GtkAccessible *accessible, + GdkDisplay *display) { static const char *gtk_test_accessible; static const char *gtk_no_a11y; @@ -401,8 +464,31 @@ gtk_at_context_create (GtkAccessibleRole accessible_role, if (gtk_no_a11y[0] == '1') return NULL; + GtkATContext *res = NULL; + + for (guint i = 0; i < G_N_ELEMENTS (a11y_backends); i++) + { + if (a11y_backends[i].name == NULL) + break; + + if (a11y_backends[i].create_context != NULL) + { + res = a11y_backends[i].create_context (accessible_role, accessible, display); + if (res != NULL) + break; + } + } + + /* Fall back to the test context, so we can get debugging data */ + if (res == NULL) + res = g_object_new (GTK_TYPE_TEST_AT_CONTEXT, + "accessible_role", accessible_role, + "accessible", accessible, + "display", display, + NULL); + /* FIXME: Add GIOExtension for AT contexts */ - return gtk_test_at_context_new (accessible_role, accessible); + return res; } /*< private > @@ -420,7 +506,8 @@ gtk_at_context_update (GtkATContext *self) /* There's no point in notifying of state changes if there weren't any */ if (self->updated_properties == 0 && self->updated_relations == 0 && - self->updated_states == 0) + self->updated_states == 0 && + self->updated_platform == 0) return; GtkAccessibleStateChange changed_states = @@ -432,12 +519,14 @@ gtk_at_context_update (GtkATContext *self) GTK_AT_CONTEXT_GET_CLASS (self)->state_change (self, changed_states, changed_properties, changed_relations, + self->updated_platform, self->states, self->properties, self->relations); g_signal_emit (self, obj_signals[STATE_CHANGE], 0); self->updated_properties = 0; self->updated_relations = 0; self->updated_states = 0; + self->updated_platform = 0; } /*< private > @@ -640,3 +729,91 @@ gtk_at_context_get_accessible_relation (GtkATContext *self, return gtk_accessible_attribute_set_get_value (self->relations, relation); } + +/*< private > + * gtk_at_context_get_label: + * @self: a #GtkATContext + * + * Retrieves the accessible label of the #GtkATContext. + * + * This is a convenience function meant to be used by #GtkATContext implementations. + * + * Returns: (transfer full): the label of the #GtkATContext + */ +char * +gtk_at_context_get_label (GtkATContext *self) +{ + g_return_val_if_fail (GTK_IS_AT_CONTEXT (self), NULL); + + GtkAccessibleValue *value = NULL; + + if (gtk_accessible_attribute_set_contains (self->states, GTK_ACCESSIBLE_STATE_HIDDEN)) + { + value = gtk_accessible_attribute_set_get_value (self->states, GTK_ACCESSIBLE_STATE_HIDDEN); + + if (gtk_boolean_accessible_value_get (value)) + return g_strdup (""); + } + + if (gtk_accessible_attribute_set_contains (self->properties, GTK_ACCESSIBLE_PROPERTY_LABEL)) + { + value = gtk_accessible_attribute_set_get_value (self->properties, GTK_ACCESSIBLE_PROPERTY_LABEL); + + return g_strdup (gtk_string_accessible_value_get (value)); + } + + if (gtk_accessible_attribute_set_contains (self->relations, GTK_ACCESSIBLE_RELATION_LABELLED_BY)) + { + value = gtk_accessible_attribute_set_get_value (self->relations, GTK_ACCESSIBLE_RELATION_LABELLED_BY); + + GList *list = gtk_reference_list_accessible_value_get (value); + GtkAccessible *rel = GTK_ACCESSIBLE (list->data); + GtkATContext *rel_context = gtk_accessible_get_at_context (rel); + + return gtk_at_context_get_label (rel_context); + } + + GtkAccessibleRole role = gtk_at_context_get_accessible_role (self); + + switch ((int) role) + { + case GTK_ACCESSIBLE_ROLE_RANGE: + { + int range_attrs[] = { + GTK_ACCESSIBLE_PROPERTY_VALUE_TEXT, + GTK_ACCESSIBLE_PROPERTY_VALUE_NOW, + }; + + for (int i = 0; i < G_N_ELEMENTS (range_attrs); i++) + { + if (gtk_accessible_attribute_set_contains (self->properties, range_attrs[i])) + { + value = gtk_accessible_attribute_set_get_value (self->properties, range_attrs[i]); + break; + } + } + + if (value != NULL) + return g_strdup (gtk_string_accessible_value_get (value)); + } + break; + + default: + break; + } + + GEnumClass *enum_class = g_type_class_peek (GTK_TYPE_ACCESSIBLE_ROLE); + GEnumValue *enum_value = g_enum_get_value (enum_class, role); + + if (enum_value != NULL) + return g_strdup (enum_value->value_nick); + + return g_strdup ("widget"); +} + +void +gtk_at_context_platform_changed (GtkATContext *self, + GtkAccessiblePlatformChange change) +{ + self->updated_platform |= change; +} diff --git a/gtk/gtkatcontext.h b/gtk/gtkatcontext.h index e6b742ffb0..2372f04b94 100644 --- a/gtk/gtkatcontext.h +++ b/gtk/gtkatcontext.h @@ -42,6 +42,7 @@ GtkAccessibleRole gtk_at_context_get_accessible_role (GtkATContext GDK_AVAILABLE_IN_ALL GtkATContext * gtk_at_context_create (GtkAccessibleRole accessible_role, - GtkAccessible *accessible); + GtkAccessible *accessible, + GdkDisplay *display); G_END_DECLS diff --git a/gtk/gtkatcontextprivate.h b/gtk/gtkatcontextprivate.h index 2b916187fd..4fec1444c8 100644 --- a/gtk/gtkatcontextprivate.h +++ b/gtk/gtkatcontextprivate.h @@ -80,12 +80,23 @@ typedef enum { GTK_ACCESSIBLE_STATE_CHANGE_SELECTED = 1 << GTK_ACCESSIBLE_STATE_SELECTED } GtkAccessibleStateChange; +typedef enum { + GTK_ACCESSIBLE_PLATFORM_STATE_FOCUSABLE, + GTK_ACCESSIBLE_PLATFORM_STATE_FOCUSED, +} GtkAccessiblePlatformState; + +typedef enum { + GTK_ACCESSIBLE_PLATFORM_CHANGE_FOCUSABLE = 1 << GTK_ACCESSIBLE_PLATFORM_STATE_FOCUSABLE, + GTK_ACCESSIBLE_PLATFORM_CHANGE_FOCUSED = 1 << GTK_ACCESSIBLE_PLATFORM_STATE_FOCUSED, +} GtkAccessiblePlatformChange; + struct _GtkATContext { GObject parent_instance; GtkAccessibleRole accessible_role; GtkAccessible *accessible; + GdkDisplay *display; GtkAccessibleAttributeSet *states; GtkAccessibleAttributeSet *properties; @@ -94,6 +105,7 @@ struct _GtkATContext GtkAccessibleStateChange updated_states; GtkAccessiblePropertyChange updated_properties; GtkAccessibleRelationChange updated_relations; + GtkAccessiblePlatformChange updated_platform; }; struct _GtkATContextClass @@ -104,11 +116,14 @@ struct _GtkATContextClass GtkAccessibleStateChange changed_states, GtkAccessiblePropertyChange changed_properties, GtkAccessibleRelationChange changed_relations, + GtkAccessiblePlatformChange changed_platform, GtkAccessibleAttributeSet *states, GtkAccessibleAttributeSet *properties, GtkAccessibleAttributeSet *relations); }; +GdkDisplay * gtk_at_context_get_display (GtkATContext *self); + void gtk_at_context_update (GtkATContext *self); void gtk_at_context_set_accessible_state (GtkATContext *self, @@ -133,6 +148,11 @@ gboolean gtk_at_context_has_accessible_relation (GtkATContext GtkAccessibleValue * gtk_at_context_get_accessible_relation (GtkATContext *self, GtkAccessibleRelation relation); +char * gtk_at_context_get_label (GtkATContext *self); + +void gtk_at_context_platform_changed (GtkATContext *self, + GtkAccessiblePlatformChange change); + const char * gtk_accessible_property_get_attribute_name (GtkAccessibleProperty property); const char * gtk_accessible_relation_get_attribute_name (GtkAccessibleRelation relation); const char * gtk_accessible_state_get_attribute_name (GtkAccessibleState state); diff --git a/gtk/gtkcolumnview.c b/gtk/gtkcolumnview.c index 0f1ca8fd42..d2b73fe105 100644 --- a/gtk/gtkcolumnview.c +++ b/gtk/gtkcolumnview.c @@ -30,7 +30,7 @@ #include "gtkcssnodeprivate.h" #include "gtkdropcontrollermotion.h" #include "gtkintl.h" -#include "gtklistview.h" +#include "gtklistviewprivate.h" #include "gtkmain.h" #include "gtkprivate.h" #include "gtkscrollable.h" @@ -106,6 +106,44 @@ * .rich-list, .navigation-sidebar or .data-table. */ +/* We create a subclass of GtkListView for the sole purpose of overriding + * some parameters for item creation. + */ + +#define GTK_TYPE_COLUMN_LIST_VIEW (gtk_column_list_view_get_type ()) +G_DECLARE_FINAL_TYPE (GtkColumnListView, gtk_column_list_view, GTK, COLUMN_LIST_VIEW, GtkListView) + +struct _GtkColumnListView +{ + GtkListView parent_instance; +}; + +struct _GtkColumnListViewClass +{ + GtkListViewClass parent_class; +}; + +G_DEFINE_TYPE (GtkColumnListView, gtk_column_list_view, GTK_TYPE_LIST_VIEW) + +static void +gtk_column_list_view_init (GtkColumnListView *view) +{ +} + +static void +gtk_column_list_view_class_init (GtkColumnListViewClass *klass) +{ + GtkListBaseClass *list_base_class = GTK_LIST_BASE_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + list_base_class->list_item_name = "row"; + list_base_class->list_item_role = GTK_ACCESSIBLE_ROLE_ROW; + + gtk_widget_class_set_css_name (widget_class, I_("listview")); + gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_LIST); +} + + struct _GtkColumnView { GtkWidget parent_instance; @@ -733,6 +771,7 @@ gtk_column_view_class_init (GtkColumnViewClass *klass) g_cclosure_marshal_VOID__UINTv); gtk_widget_class_set_css_name (widget_class, I_("columnview")); + gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_TREE_GRID); } static void update_column_resize (GtkColumnView *self, @@ -1136,7 +1175,7 @@ gtk_column_view_init (GtkColumnView *self) self->columns = g_list_store_new (GTK_TYPE_COLUMN_VIEW_COLUMN); - self->header = gtk_list_item_widget_new (NULL, "header"); + self->header = gtk_list_item_widget_new (NULL, "header", GTK_ACCESSIBLE_ROLE_ROW); gtk_widget_set_can_focus (self->header, FALSE); gtk_widget_set_layout_manager (self->header, gtk_column_view_layout_new (self)); gtk_widget_set_parent (self->header, GTK_WIDGET (self)); @@ -1164,8 +1203,8 @@ gtk_column_view_init (GtkColumnView *self) self->sorter = GTK_SORTER (gtk_column_view_sorter_new ()); self->factory = gtk_column_list_item_factory_new (self); - self->listview = GTK_LIST_VIEW (gtk_list_view_new (NULL, - GTK_LIST_ITEM_FACTORY (g_object_ref (self->factory)))); + self->listview = GTK_LIST_VIEW (g_object_new (GTK_TYPE_COLUMN_LIST_VIEW, NULL)); + gtk_list_view_set_factory (self->listview, GTK_LIST_ITEM_FACTORY (self->factory)); gtk_widget_set_hexpand (GTK_WIDGET (self->listview), TRUE); gtk_widget_set_vexpand (GTK_WIDGET (self->listview), TRUE); g_signal_connect (self->listview, "activate", G_CALLBACK (gtk_column_view_activate_cb), self); diff --git a/gtk/gtkcolumnviewcell.c b/gtk/gtkcolumnviewcell.c index 0b94fe7bcd..f7db70e04f 100644 --- a/gtk/gtkcolumnviewcell.c +++ b/gtk/gtkcolumnviewcell.c @@ -204,6 +204,7 @@ gtk_column_view_cell_class_init (GtkColumnViewCellClass *klass) gobject_class->dispose = gtk_column_view_cell_dispose; gtk_widget_class_set_css_name (widget_class, I_("cell")); + gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_GRID_CELL); } static void diff --git a/gtk/gtkcolumnviewtitle.c b/gtk/gtkcolumnviewtitle.c index 9291bd01b6..f2cb4fca83 100644 --- a/gtk/gtkcolumnviewtitle.c +++ b/gtk/gtkcolumnviewtitle.c @@ -171,6 +171,7 @@ gtk_column_view_title_class_init (GtkColumnViewTitleClass *klass) gobject_class->dispose = gtk_column_view_title_dispose; gtk_widget_class_set_css_name (widget_class, I_("button")); + gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_COLUMN_HEADER); } static void diff --git a/gtk/gtkcombobox.c b/gtk/gtkcombobox.c index 8b6cf921bc..a8c839b170 100644 --- a/gtk/gtkcombobox.c +++ b/gtk/gtkcombobox.c @@ -103,6 +103,10 @@ * contains a box with the .linked class. That box contains an entry and a * button, both with the .combo class added. * The button also contains another node with name arrow. + * + * # Accessibility + * + * GtkComboBox uses the #GTK_ACCESSIBLE_ROLE_COMBO_BOX role. */ @@ -797,6 +801,7 @@ gtk_combo_box_class_init (GtkComboBoxClass *klass) gtk_widget_class_bind_template_callback (widget_class, gtk_combo_box_menu_hide); gtk_widget_class_set_css_name (widget_class, I_("combobox")); + gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_COMBO_BOX); } static void diff --git a/gtk/gtkdropdown.c b/gtk/gtkdropdown.c index 2dd07ade52..80dc7a1ae6 100644 --- a/gtk/gtkdropdown.c +++ b/gtk/gtkdropdown.c @@ -71,6 +71,10 @@ * * GtkDropDown has a single CSS node with name dropdown, * with the button and popover nodes as children. + * + * # Accessibility + * + * GtkDropDown uses the #GTK_ACCESSIBLE_ROLE_COMBO_BOX role. */ struct _GtkDropDown @@ -525,6 +529,7 @@ gtk_drop_down_class_init (GtkDropDownClass *klass) gtk_widget_class_bind_template_callback (widget_class, search_stop); gtk_widget_class_set_css_name (widget_class, I_("dropdown")); + gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_COMBO_BOX); } static void diff --git a/gtk/gtkentry.c b/gtk/gtkentry.c index 3d058bad27..532d3896fe 100644 --- a/gtk/gtkentry.c +++ b/gtk/gtkentry.c @@ -30,6 +30,7 @@ #include "gtkentryprivate.h" +#include "gtkaccessibleprivate.h" #include "gtkadjustment.h" #include "gtkbox.h" #include "gtkbutton.h" @@ -320,9 +321,12 @@ static void gtk_entry_measure (GtkWidget *widget, static GtkBuildableIface *buildable_parent_iface = NULL; static void gtk_entry_buildable_interface_init (GtkBuildableIface *iface); +static void gtk_entry_accessible_interface_init (GtkAccessibleInterface *iface); G_DEFINE_TYPE_WITH_CODE (GtkEntry, gtk_entry, GTK_TYPE_WIDGET, G_ADD_PRIVATE (GtkEntry) + G_IMPLEMENT_INTERFACE (GTK_TYPE_ACCESSIBLE, + gtk_entry_accessible_interface_init) G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, gtk_entry_buildable_interface_init) G_IMPLEMENT_INTERFACE (GTK_TYPE_EDITABLE, @@ -330,6 +334,37 @@ G_DEFINE_TYPE_WITH_CODE (GtkEntry, gtk_entry, GTK_TYPE_WIDGET, G_IMPLEMENT_INTERFACE (GTK_TYPE_CELL_EDITABLE, gtk_entry_cell_editable_init)) +/* Implement the GtkAccessible interface, in order to obtain focus + * state from the #GtkText widget that we are wrapping. The GtkText + * widget is ignored for accessibility purposes (it has role NONE), + * and any a11y text functionality is implemented for GtkEntry and + * similar wrappers (GtkPasswordEntry, GtkSpinButton, etc). + */ +static gboolean +gtk_entry_accessible_get_platform_state (GtkAccessible *self, + GtkAccessiblePlatformState state) +{ + GtkEntry *entry = GTK_ENTRY (self); + GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); + + switch (state) + { + case GTK_ACCESSIBLE_PLATFORM_STATE_FOCUSABLE: + return gtk_widget_get_focusable (GTK_WIDGET (priv->text)); + case GTK_ACCESSIBLE_PLATFORM_STATE_FOCUSED: + return gtk_widget_has_focus (GTK_WIDGET (priv->text)); + default: + g_assert_not_reached (); + } +} + +static void +gtk_entry_accessible_interface_init (GtkAccessibleInterface *iface) +{ + GtkAccessibleInterface *parent_iface = g_type_interface_peek_parent (iface); + iface->get_at_context = parent_iface->get_at_context; + iface->get_platform_state = gtk_entry_accessible_get_platform_state; +} static const GtkBuildableParser pango_parser = { diff --git a/gtk/gtkenums.h b/gtk/gtkenums.h index d1ca41cb1a..561a273b59 100644 --- a/gtk/gtkenums.h +++ b/gtk/gtkenums.h @@ -1161,7 +1161,7 @@ typedef enum { * @GTK_ACCESSIBLE_ROLE_CELL: Unused * @GTK_ACCESSIBLE_ROLE_CHECKBOX: A checkable input element that has * three possible values: `true`, `false`, or `mixed` - * @GTK_ACCESSIBLE_ROLE_COLUMN_HEADER: Unused + * @GTK_ACCESSIBLE_ROLE_COLUMN_HEADER: A header in a columned list. * @GTK_ACCESSIBLE_ROLE_COMBO_BOX: An input that controls another element, * such as a list or a grid, that can dynamically pop up to help the user * set the value of the input @@ -1175,8 +1175,8 @@ typedef enum { * @GTK_ACCESSIBLE_ROLE_FEED: Unused * @GTK_ACCESSIBLE_ROLE_FORM: Unused * @GTK_ACCESSIBLE_ROLE_GENERIC: Unused - * @GTK_ACCESSIBLE_ROLE_GRID: Unused - * @GTK_ACCESSIBLE_ROLE_GRID_CELL: Unused + * @GTK_ACCESSIBLE_ROLE_GRID: A grid of items. + * @GTK_ACCESSIBLE_ROLE_GRID_CELL: An item in a grid or tree grid. * @GTK_ACCESSIBLE_ROLE_GROUP: Unused * @GTK_ACCESSIBLE_ROLE_HEADING: Unused * @GTK_ACCESSIBLE_ROLE_IMG: An image. @@ -1185,9 +1185,9 @@ typedef enum { * @GTK_ACCESSIBLE_ROLE_LANDMARK: Unused * @GTK_ACCESSIBLE_ROLE_LEGEND: Unused * @GTK_ACCESSIBLE_ROLE_LINK: Unused - * @GTK_ACCESSIBLE_ROLE_LIST: Unused - * @GTK_ACCESSIBLE_ROLE_LIST_BOX: Unused - * @GTK_ACCESSIBLE_ROLE_LIST_ITEM: Unused + * @GTK_ACCESSIBLE_ROLE_LIST: A list of items. + * @GTK_ACCESSIBLE_ROLE_LIST_BOX: Unused. + * @GTK_ACCESSIBLE_ROLE_LIST_ITEM: An item in a list. * @GTK_ACCESSIBLE_ROLE_LOG: Unused * @GTK_ACCESSIBLE_ROLE_MAIN: Unused * @GTK_ACCESSIBLE_ROLE_MARQUEE: Unused @@ -1210,7 +1210,7 @@ typedef enum { * @GTK_ACCESSIBLE_ROLE_RADIO_GROUP: Unused * @GTK_ACCESSIBLE_ROLE_RANGE: Unused * @GTK_ACCESSIBLE_ROLE_REGION: Unused - * @GTK_ACCESSIBLE_ROLE_ROW: Unused + * @GTK_ACCESSIBLE_ROLE_ROW: A row in a columned list. * @GTK_ACCESSIBLE_ROLE_ROW_GROUP: Unused * @GTK_ACCESSIBLE_ROLE_ROW_HEADER: Unused * @GTK_ACCESSIBLE_ROLE_SCROLLBAR: A graphical object that controls the scrolling @@ -1232,10 +1232,10 @@ typedef enum { * @GTK_ACCESSIBLE_ROLE_STRUCTURE: Unused * @GTK_ACCESSIBLE_ROLE_SWITCH: A type of checkbox that represents on/off values, * as opposed to checked/unchecked values. - * @GTK_ACCESSIBLE_ROLE_TAB: Unused + * @GTK_ACCESSIBLE_ROLE_TAB: An item in a list of tab used for switching pages. * @GTK_ACCESSIBLE_ROLE_TABLE: Unused - * @GTK_ACCESSIBLE_ROLE_TAB_LIST: Unused - * @GTK_ACCESSIBLE_ROLE_TAB_PANEL: Unused + * @GTK_ACCESSIBLE_ROLE_TAB_LIST: A list of tabs for switching pages. + * @GTK_ACCESSIBLE_ROLE_TAB_PANEL: A page in a notebook or stack. * @GTK_ACCESSIBLE_ROLE_TEXT_BOX: A type of input that allows free-form text * as its value. * @GTK_ACCESSIBLE_ROLE_TIME: Unused @@ -1243,7 +1243,7 @@ typedef enum { * @GTK_ACCESSIBLE_ROLE_TOOLBAR: Unused * @GTK_ACCESSIBLE_ROLE_TOOLTIP: Unused * @GTK_ACCESSIBLE_ROLE_TREE: Unused - * @GTK_ACCESSIBLE_ROLE_TREE_GRID: Unused + * @GTK_ACCESSIBLE_ROLE_TREE_GRID: A treeview-like, columned list. * @GTK_ACCESSIBLE_ROLE_TREE_ITEM: Unused * @GTK_ACCESSIBLE_ROLE_WIDGET: An interactive component of a graphical user * interface. This is the role that GTK uses by default for widgets. diff --git a/gtk/gtkflowbox.c b/gtk/gtkflowbox.c index 7a306a8dc3..8d01400835 100644 --- a/gtk/gtkflowbox.c +++ b/gtk/gtkflowbox.c @@ -75,6 +75,7 @@ #include "gtkflowboxprivate.h" +#include "gtkaccessible.h" #include "gtkadjustment.h" #include "gtkbinlayout.h" #include "gtkbuildable.h" @@ -483,6 +484,18 @@ gtk_flow_box_child_compute_expand (GtkWidget *widget, } } +static void +gtk_flow_box_child_root (GtkWidget *widget) +{ + GtkFlowBoxChild *child = GTK_FLOW_BOX_CHILD (widget); + + GTK_WIDGET_CLASS (gtk_flow_box_child_parent_class)->root (widget); + + gtk_accessible_update_state (GTK_ACCESSIBLE (child), + GTK_ACCESSIBLE_STATE_SELECTED, CHILD_PRIV (child)->selected, + -1); +} + /* GObject implementation {{{2 */ static void @@ -495,6 +508,7 @@ gtk_flow_box_child_class_init (GtkFlowBoxChildClass *class) object_class->get_property = gtk_flow_box_child_get_property; object_class->set_property = gtk_flow_box_child_set_property; + widget_class->root = gtk_flow_box_child_root; widget_class->get_request_mode = gtk_flow_box_child_get_request_mode; widget_class->compute_expand = gtk_flow_box_child_compute_expand; widget_class->focus = gtk_flow_box_child_focus; @@ -533,6 +547,7 @@ gtk_flow_box_child_class_init (GtkFlowBoxChildClass *class) gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT); gtk_widget_class_set_css_name (widget_class, I_("flowboxchild")); + gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_GRID_CELL); } static void @@ -905,6 +920,9 @@ gtk_flow_box_child_set_selected (GtkFlowBoxChild *child, gtk_widget_unset_state_flags (GTK_WIDGET (child), GTK_STATE_FLAG_SELECTED); + gtk_accessible_update_state (GTK_ACCESSIBLE (child), + GTK_ACCESSIBLE_STATE_SELECTED, selected, + -1); return TRUE; } @@ -3870,6 +3888,7 @@ gtk_flow_box_class_init (GtkFlowBoxClass *class) NULL); gtk_widget_class_set_css_name (widget_class, I_("flowbox")); + gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_GRID); } static void @@ -4724,6 +4743,10 @@ gtk_flow_box_set_selection_mode (GtkFlowBox *box, BOX_PRIV (box)->selection_mode = mode; + gtk_accessible_update_property (GTK_ACCESSIBLE (box), + GTK_ACCESSIBLE_PROPERTY_MULTI_SELECTABLE, mode == GTK_SELECTION_MULTIPLE, + -1); + g_object_notify_by_pspec (G_OBJECT (box), props[PROP_SELECTION_MODE]); if (dirty) diff --git a/gtk/gtkgizmo.c b/gtk/gtkgizmo.c index a929248a5c..00162645b3 100644 --- a/gtk/gtkgizmo.c +++ b/gtk/gtkgizmo.c @@ -131,8 +131,29 @@ gtk_gizmo_new (const char *css_name, GtkGizmoFocusFunc focus_func, GtkGizmoGrabFocusFunc grab_focus_func) { + return gtk_gizmo_new_with_role (css_name, + GTK_ACCESSIBLE_ROLE_WIDGET, + measure_func, + allocate_func, + snapshot_func, + contains_func, + focus_func, + grab_focus_func); +} + +GtkWidget * +gtk_gizmo_new_with_role (const char *css_name, + GtkAccessibleRole role, + GtkGizmoMeasureFunc measure_func, + GtkGizmoAllocateFunc allocate_func, + GtkGizmoSnapshotFunc snapshot_func, + GtkGizmoContainsFunc contains_func, + GtkGizmoFocusFunc focus_func, + GtkGizmoGrabFocusFunc grab_focus_func) +{ GtkGizmo *gizmo = GTK_GIZMO (g_object_new (GTK_TYPE_GIZMO, "css-name", css_name, + "accessible-role", role, NULL)); gizmo->measure_func = measure_func; diff --git a/gtk/gtkgizmoprivate.h b/gtk/gtkgizmoprivate.h index 478af19971..9257201a59 100644 --- a/gtk/gtkgizmoprivate.h +++ b/gtk/gtkgizmoprivate.h @@ -3,6 +3,7 @@ #define __GTK_GIZMO_H__ #include "gtkwidget.h" +#include "gtkenums.h" #define GTK_TYPE_GIZMO (gtk_gizmo_get_type ()) #define GTK_GIZMO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_GIZMO, GtkGizmo)) @@ -61,5 +62,14 @@ GtkWidget *gtk_gizmo_new (const char *css_name, GtkGizmoFocusFunc focus_func, GtkGizmoGrabFocusFunc grab_focus_func); +GtkWidget *gtk_gizmo_new_with_role (const char *css_name, + GtkAccessibleRole role, + GtkGizmoMeasureFunc measure_func, + GtkGizmoAllocateFunc allocate_func, + GtkGizmoSnapshotFunc snapshot_func, + GtkGizmoContainsFunc contains_func, + GtkGizmoFocusFunc focus_func, + GtkGizmoGrabFocusFunc grab_focus_func); + #endif diff --git a/gtk/gtkgridview.c b/gtk/gtkgridview.c index ba8ce422af..8c24afc9e1 100644 --- a/gtk/gtkgridview.c +++ b/gtk/gtkgridview.c @@ -30,6 +30,7 @@ #include "gtkprivate.h" #include "gtksingleselection.h" #include "gtkwidgetprivate.h" +#include "gtkmultiselection.h" /* Maximum number of list items created by the gridview. * For debugging, you can set this to G_MAXUINT to ensure @@ -1025,6 +1026,7 @@ gtk_grid_view_class_init (GtkGridViewClass *klass) GObjectClass *gobject_class = G_OBJECT_CLASS (klass); list_base_class->list_item_name = "child"; + list_base_class->list_item_role = GTK_ACCESSIBLE_ROLE_GRID_CELL; list_base_class->list_item_size = sizeof (Cell); list_base_class->list_item_augment_size = sizeof (CellAugment); list_base_class->list_item_augment_func = cell_augment; @@ -1157,6 +1159,7 @@ gtk_grid_view_class_init (GtkGridViewClass *klass) gtk_grid_view_activate_item); gtk_widget_class_set_css_name (widget_class, I_("gridview")); + gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_GRID); } static void @@ -1246,6 +1249,10 @@ gtk_grid_view_set_model (GtkGridView *self, if (!gtk_list_base_set_model (GTK_LIST_BASE (self), model)) return; + gtk_accessible_update_property (GTK_ACCESSIBLE (self), + GTK_ACCESSIBLE_PROPERTY_MULTI_SELECTABLE, GTK_IS_MULTI_SELECTION (model), + -1); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MODEL]); } diff --git a/gtk/gtklistbase.c b/gtk/gtklistbase.c index 201bcb0844..ed4f84ad2a 100644 --- a/gtk/gtklistbase.c +++ b/gtk/gtklistbase.c @@ -1794,6 +1794,7 @@ gtk_list_base_init_real (GtkListBase *self, priv->item_manager = gtk_list_item_manager_new_for_size (GTK_WIDGET (self), g_class->list_item_name, + g_class->list_item_role, g_class->list_item_size, g_class->list_item_augment_size, g_class->list_item_augment_func); diff --git a/gtk/gtklistbaseprivate.h b/gtk/gtklistbaseprivate.h index fe640978b2..75d7466784 100644 --- a/gtk/gtklistbaseprivate.h +++ b/gtk/gtklistbaseprivate.h @@ -35,6 +35,7 @@ struct _GtkListBaseClass GtkWidgetClass parent_class; const char * list_item_name; + GtkAccessibleRole list_item_role; gsize list_item_size; gsize list_item_augment_size; GtkRbTreeAugmentFunc list_item_augment_func; diff --git a/gtk/gtklistbox.c b/gtk/gtklistbox.c index c0028d83fd..189a68ea79 100644 --- a/gtk/gtklistbox.c +++ b/gtk/gtklistbox.c @@ -19,6 +19,7 @@ #include "gtklistbox.h" +#include "gtkaccessible.h" #include "gtkactionhelperprivate.h" #include "gtkadjustmentprivate.h" #include "gtkbinlayout.h" @@ -678,6 +679,7 @@ gtk_list_box_class_init (GtkListBoxClass *klass) NULL); gtk_widget_class_set_css_name (widget_class, I_("list")); + gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_LIST); } static void @@ -1119,6 +1121,10 @@ gtk_list_box_set_selection_mode (GtkListBox *box, gtk_list_box_update_row_styles (box); + gtk_accessible_update_property (GTK_ACCESSIBLE (box), + GTK_ACCESSIBLE_PROPERTY_MULTI_SELECTABLE, mode == GTK_SELECTION_MULTIPLE, + -1); + g_object_notify_by_pspec (G_OBJECT (box), properties[PROP_SELECTION_MODE]); if (dirty) @@ -1558,6 +1564,10 @@ gtk_list_box_row_set_selected (GtkListBoxRow *row, gtk_widget_unset_state_flags (GTK_WIDGET (row), GTK_STATE_FLAG_SELECTED); + gtk_accessible_update_state (GTK_ACCESSIBLE (row), + GTK_ACCESSIBLE_STATE_SELECTED, selected, + -1); + return TRUE; } @@ -3019,6 +3029,19 @@ gtk_list_box_row_hide (GtkWidget *widget) gtk_list_box_row_visibility_changed (box, row); } +static void +gtk_list_box_row_root (GtkWidget *widget) +{ + GtkListBoxRow *row = GTK_LIST_BOX_ROW (widget); + + GTK_WIDGET_CLASS (gtk_list_box_row_parent_class)->root (widget); + + if (ROW_PRIV (row)->selectable) + gtk_accessible_update_state (GTK_ACCESSIBLE (row), + GTK_ACCESSIBLE_STATE_SELECTED, ROW_PRIV (row)->selected, + -1); +} + /** * gtk_list_box_row_changed: * @row: a #GtkListBoxRow @@ -3228,10 +3251,19 @@ gtk_list_box_row_set_selectable (GtkListBoxRow *row, { if (!selectable) gtk_list_box_row_set_selected (row, FALSE); - + ROW_PRIV (row)->selectable = selectable; + if (selectable) + gtk_accessible_update_state (GTK_ACCESSIBLE (row), + GTK_ACCESSIBLE_STATE_SELECTED, FALSE, + -1); + else + gtk_accessible_reset_state (GTK_ACCESSIBLE (row), + GTK_ACCESSIBLE_STATE_SELECTED); + gtk_list_box_update_row_style (gtk_list_box_row_get_box (row), row); + g_object_notify_by_pspec (G_OBJECT (row), row_properties[ROW_PROP_SELECTABLE]); } } @@ -3413,6 +3445,7 @@ gtk_list_box_row_class_init (GtkListBoxRowClass *klass) object_class->finalize = gtk_list_box_row_finalize; object_class->dispose = gtk_list_box_row_dispose; + widget_class->root = gtk_list_box_row_root; widget_class->show = gtk_list_box_row_show; widget_class->hide = gtk_list_box_row_hide; widget_class->focus = gtk_list_box_row_focus; @@ -3478,6 +3511,7 @@ gtk_list_box_row_class_init (GtkListBoxRowClass *klass) gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT); gtk_widget_class_set_css_name (widget_class, I_("row")); + gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_LIST_ITEM); } static void diff --git a/gtk/gtklistitemmanager.c b/gtk/gtklistitemmanager.c index 7cad397ffa..c795653004 100644 --- a/gtk/gtklistitemmanager.c +++ b/gtk/gtklistitemmanager.c @@ -35,6 +35,7 @@ struct _GtkListItemManager GtkListItemFactory *factory; gboolean single_click_activate; const char *item_css_name; + GtkAccessibleRole item_role; GtkRbTree *items; GSList *trackers; @@ -111,6 +112,7 @@ gtk_list_item_manager_clear_node (gpointer _item) GtkListItemManager * gtk_list_item_manager_new_for_size (GtkWidget *widget, const char *item_css_name, + GtkAccessibleRole item_role, gsize element_size, gsize augment_size, GtkRbTreeAugmentFunc augment_func) @@ -126,6 +128,7 @@ gtk_list_item_manager_new_for_size (GtkWidget *widget, /* not taking a ref because the widget refs us */ self->widget = widget; self->item_css_name = g_intern_string (item_css_name); + self->item_role = item_role; self->items = gtk_rb_tree_new_for_size (element_size, augment_size, @@ -923,7 +926,8 @@ gtk_list_item_manager_acquire_list_item (GtkListItemManager *self, g_return_val_if_fail (prev_sibling == NULL || GTK_IS_WIDGET (prev_sibling), NULL); result = gtk_list_item_widget_new (self->factory, - self->item_css_name); + self->item_css_name, + self->item_role); gtk_list_item_widget_set_single_click_activate (GTK_LIST_ITEM_WIDGET (result), self->single_click_activate); diff --git a/gtk/gtklistitemmanagerprivate.h b/gtk/gtklistitemmanagerprivate.h index baf4c51442..d2b4bbc433 100644 --- a/gtk/gtklistitemmanagerprivate.h +++ b/gtk/gtklistitemmanagerprivate.h @@ -22,6 +22,7 @@ #define __GTK_LIST_ITEM_MANAGER_H__ #include "gtk/gtktypes.h" +#include "gtk/gtkenums.h" #include "gtk/gtklistitemfactory.h" #include "gtk/gtkrbtreeprivate.h" @@ -58,6 +59,7 @@ GType gtk_list_item_manager_get_type (void) G_GNUC_CO GtkListItemManager * gtk_list_item_manager_new_for_size (GtkWidget *widget, const char *item_css_name, + GtkAccessibleRole item_role, gsize element_size, gsize augment_size, GtkRbTreeAugmentFunc augment_func); diff --git a/gtk/gtklistitemwidget.c b/gtk/gtklistitemwidget.c index ce4f505039..8f6a5c5633 100644 --- a/gtk/gtklistitemwidget.c +++ b/gtk/gtklistitemwidget.c @@ -453,12 +453,14 @@ gtk_list_item_widget_init (GtkListItemWidget *self) GtkWidget * gtk_list_item_widget_new (GtkListItemFactory *factory, - const char *css_name) + const char *css_name, + GtkAccessibleRole role) { g_return_val_if_fail (css_name != NULL, NULL); return g_object_new (GTK_TYPE_LIST_ITEM_WIDGET, "css-name", css_name, + "accessible-role", role, "factory", factory, NULL); } @@ -480,6 +482,10 @@ gtk_list_item_widget_update (GtkListItemWidget *self, gtk_widget_set_state_flags (GTK_WIDGET (self), GTK_STATE_FLAG_SELECTED, FALSE); else gtk_widget_unset_state_flags (GTK_WIDGET (self), GTK_STATE_FLAG_SELECTED); + + gtk_accessible_update_state (GTK_ACCESSIBLE (self), + GTK_ACCESSIBLE_STATE_SELECTED, selected, + -1); } void diff --git a/gtk/gtklistitemwidgetprivate.h b/gtk/gtklistitemwidgetprivate.h index 3042d08789..8f78b1a7a2 100644 --- a/gtk/gtklistitemwidgetprivate.h +++ b/gtk/gtklistitemwidgetprivate.h @@ -50,7 +50,8 @@ struct _GtkListItemWidgetClass GType gtk_list_item_widget_get_type (void) G_GNUC_CONST; GtkWidget * gtk_list_item_widget_new (GtkListItemFactory *factory, - const char *css_name); + const char *css_name, + GtkAccessibleRole role); void gtk_list_item_widget_update (GtkListItemWidget *self, guint position, diff --git a/gtk/gtklistview.c b/gtk/gtklistview.c index 5382c7b1ad..ad8f8ea16c 100644 --- a/gtk/gtklistview.c +++ b/gtk/gtklistview.c @@ -19,7 +19,7 @@ #include "config.h" -#include "gtklistview.h" +#include "gtklistviewprivate.h" #include "gtkbitset.h" #include "gtkintl.h" @@ -29,6 +29,7 @@ #include "gtkprivate.h" #include "gtkrbtreeprivate.h" #include "gtkwidgetprivate.h" +#include "gtkmultiselection.h" /* Maximum number of list items created by the listview. * For debugging, you can set this to G_MAXUINT to ensure @@ -139,21 +140,6 @@ typedef struct _ListRow ListRow; typedef struct _ListRowAugment ListRowAugment; -struct _GtkListView -{ - GtkListBase parent_instance; - - GtkListItemManager *item_manager; - gboolean show_separators; - - int list_width; -}; - -struct _GtkListViewClass -{ - GtkListBaseClass parent_class; -}; - struct _ListRow { GtkListItemManagerItem parent; @@ -799,6 +785,7 @@ gtk_list_view_class_init (GtkListViewClass *klass) GObjectClass *gobject_class = G_OBJECT_CLASS (klass); list_base_class->list_item_name = "row"; + list_base_class->list_item_role = GTK_ACCESSIBLE_ROLE_LIST_ITEM; list_base_class->list_item_size = sizeof (ListRow); list_base_class->list_item_augment_size = sizeof (ListRowAugment); list_base_class->list_item_augment_func = list_row_augment; @@ -915,6 +902,7 @@ gtk_list_view_class_init (GtkListViewClass *klass) gtk_list_view_activate_item); gtk_widget_class_set_css_name (widget_class, I_("listview")); + gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_LIST); } static void @@ -1000,6 +988,10 @@ gtk_list_view_set_model (GtkListView *self, if (!gtk_list_base_set_model (GTK_LIST_BASE (self), model)) return; + gtk_accessible_update_property (GTK_ACCESSIBLE (self), + GTK_ACCESSIBLE_PROPERTY_MULTI_SELECTABLE, GTK_IS_MULTI_SELECTION (model), + -1); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MODEL]); } diff --git a/gtk/gtklistview.h b/gtk/gtklistview.h index 9a258de1da..ee2f70c0c4 100644 --- a/gtk/gtklistview.h +++ b/gtk/gtklistview.h @@ -81,6 +81,8 @@ void gtk_list_view_set_enable_rubberband (GtkListView GDK_AVAILABLE_IN_ALL gboolean gtk_list_view_get_enable_rubberband (GtkListView *self); +G_DEFINE_AUTOPTR_CLEANUP_FUNC(GtkListView, g_object_unref) + G_END_DECLS #endif /* __GTK_LIST_VIEW_H__ */ diff --git a/gtk/gtklistviewprivate.h b/gtk/gtklistviewprivate.h new file mode 100644 index 0000000000..007e75661a --- /dev/null +++ b/gtk/gtklistviewprivate.h @@ -0,0 +1,45 @@ +/* + * 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.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/>. + * + * Authors: Benjamin Otte <otte@gnome.org> + */ + +#ifndef __GTK_LIST_VIEW_PRIVATE_H__ +#define __GTK_LIST_VIEW_PRIVATE_H__ + +#include <gtk/gtklistview.h> +#include <gtk/gtklistbaseprivate.h> + +G_BEGIN_DECLS + +struct _GtkListView +{ + GtkListBase parent_instance; + + GtkListItemManager *item_manager; + gboolean show_separators; + + int list_width; +}; + +struct _GtkListViewClass +{ + GtkListBaseClass parent_class; +}; + +G_END_DECLS + +#endif /* __GTK_LIST_VIEW_H__ */ diff --git a/gtk/gtknotebook.c b/gtk/gtknotebook.c index cdf873861f..81346060c1 100644 --- a/gtk/gtknotebook.c +++ b/gtk/gtknotebook.c @@ -144,6 +144,12 @@ * A tab node gets the .dnd style class while it is moved with drag-and-drop. * * The nodes are always arranged from left-to-right, regardless of text direction. + * + * # Accessibility + * + * GtkNotebook uses the #GTK_ACCESSIBLE_ROLE_TAB_LIST and + * #GTK_ACCESSIBLE_ROLE_TAB roles for its list of tabs and the + * #GTK_ACCESSIBLE_ROLE_TAB_PANEL for the pages. */ @@ -1404,13 +1410,14 @@ gtk_notebook_init (GtkNotebook *notebook) gtk_widget_hide (notebook->header_widget); gtk_widget_set_parent (notebook->header_widget, GTK_WIDGET (notebook)); - notebook->tabs_widget = gtk_gizmo_new ("tabs", - gtk_notebook_measure_tabs, - gtk_notebook_allocate_tabs, - gtk_notebook_snapshot_tabs, - NULL, - (GtkGizmoFocusFunc)gtk_widget_focus_self, - (GtkGizmoGrabFocusFunc)gtk_widget_grab_focus_self); + notebook->tabs_widget = gtk_gizmo_new_with_role ("tabs", + GTK_ACCESSIBLE_ROLE_TAB_LIST, + gtk_notebook_measure_tabs, + gtk_notebook_allocate_tabs, + gtk_notebook_snapshot_tabs, + NULL, + (GtkGizmoFocusFunc)gtk_widget_focus_self, + (GtkGizmoGrabFocusFunc)gtk_widget_grab_focus_self); gtk_widget_set_hexpand (notebook->tabs_widget, TRUE); gtk_box_append (GTK_BOX (notebook->header_widget), notebook->tabs_widget); @@ -3915,6 +3922,7 @@ gtk_notebook_insert_notebook_page (GtkNotebook *notebook, GList *list; GtkWidget *sibling; GtkEventController *controller; + GtkStackPage *stack_page; nchildren = g_list_length (notebook->children); if ((position < 0) || (position > nchildren)) @@ -3929,7 +3937,14 @@ gtk_notebook_insert_notebook_page (GtkNotebook *notebook, else sibling = notebook->arrow_widget[ARROW_RIGHT_AFTER]; - page->tab_widget = gtk_gizmo_new ("tab", measure_tab, allocate_tab, NULL, NULL, NULL, NULL); + page->tab_widget = gtk_gizmo_new_with_role ("tab", + GTK_ACCESSIBLE_ROLE_TAB, + measure_tab, + allocate_tab, + NULL, + NULL, + NULL, + NULL); g_object_set_data (G_OBJECT (page->tab_widget), "notebook", notebook); gtk_widget_insert_before (page->tab_widget, notebook->tabs_widget, sibling); controller = gtk_drop_controller_motion_new (); @@ -3951,6 +3966,15 @@ gtk_notebook_insert_notebook_page (GtkNotebook *notebook, g_object_set_data (G_OBJECT (page->tab_label), "notebook", notebook); } + stack_page = gtk_stack_get_page (GTK_STACK (notebook->stack_widget), page->child); + gtk_accessible_update_relation (GTK_ACCESSIBLE (page->tab_widget), + GTK_ACCESSIBLE_RELATION_CONTROLS, g_list_append (NULL, stack_page), + -1); + + gtk_accessible_update_state (GTK_ACCESSIBLE (page->tab_widget), + GTK_ACCESSIBLE_STATE_SELECTED, FALSE, + -1); + gtk_notebook_update_labels (notebook); if (!notebook->first_tab) @@ -5303,12 +5327,20 @@ gtk_notebook_real_switch_page (GtkNotebook *notebook, if (focus) child_has_focus = gtk_widget_is_ancestor (focus, notebook->cur_page->child); gtk_widget_unset_state_flags (notebook->cur_page->tab_widget, GTK_STATE_FLAG_CHECKED); + + gtk_accessible_update_state (GTK_ACCESSIBLE (notebook->cur_page->tab_widget), + GTK_ACCESSIBLE_STATE_SELECTED, FALSE, + -1); } notebook->cur_page = page; gtk_widget_set_state_flags (page->tab_widget, GTK_STATE_FLAG_CHECKED, FALSE); gtk_widget_set_visible (notebook->header_widget, notebook->show_tabs); + gtk_accessible_update_state (GTK_ACCESSIBLE (notebook->cur_page->tab_widget), + GTK_ACCESSIBLE_STATE_SELECTED, TRUE, + -1); + if (!notebook->focus_tab || notebook->focus_tab->data != (gpointer) notebook->cur_page) notebook->focus_tab = diff --git a/gtk/gtkpaned.c b/gtk/gtkpaned.c index 8ac244d483..929779a39a 100644 --- a/gtk/gtkpaned.c +++ b/gtk/gtkpaned.c @@ -1385,6 +1385,14 @@ gtk_paned_size_allocate (GtkWidget *widget, gtk_widget_set_child_visible (paned->handle_widget, FALSE); } + + gtk_accessible_update_property (GTK_ACCESSIBLE (paned), + GTK_ACCESSIBLE_PROPERTY_VALUE_MIN, 0.0, + GTK_ACCESSIBLE_PROPERTY_VALUE_MAX, + (double) (paned->orientation == GTK_ORIENTATION_HORIZONTAL ? width : height), + GTK_ACCESSIBLE_PROPERTY_VALUE_NOW, + (double) paned->start_child_size, + -1); } diff --git a/gtk/gtkpasswordentry.c b/gtk/gtkpasswordentry.c index d8da5b4e56..8d5de422b2 100644 --- a/gtk/gtkpasswordentry.c +++ b/gtk/gtkpasswordentry.c @@ -22,6 +22,7 @@ #include "gtkpasswordentryprivate.h" +#include "gtkaccessibleprivate.h" #include "gtktextprivate.h" #include "gtkeditable.h" #include "gtkeventcontrollerkey.h" @@ -104,8 +105,10 @@ enum { static GParamSpec *props[NUM_PROPERTIES] = { NULL, }; static void gtk_password_entry_editable_init (GtkEditableInterface *iface); +static void gtk_password_entry_accessible_init (GtkAccessibleInterface *iface); G_DEFINE_TYPE_WITH_CODE (GtkPasswordEntry, gtk_password_entry, GTK_TYPE_WIDGET, + G_IMPLEMENT_INTERFACE (GTK_TYPE_ACCESSIBLE, gtk_password_entry_accessible_init) G_IMPLEMENT_INTERFACE (GTK_TYPE_EDITABLE, gtk_password_entry_editable_init)) static void @@ -486,6 +489,31 @@ gtk_password_entry_editable_init (GtkEditableInterface *iface) iface->get_delegate = gtk_password_entry_get_delegate; } +static gboolean +gtk_password_entry_accessible_get_platform_state (GtkAccessible *self, + GtkAccessiblePlatformState state) +{ + GtkPasswordEntry *entry = GTK_PASSWORD_ENTRY (self); + + switch (state) + { + case GTK_ACCESSIBLE_PLATFORM_STATE_FOCUSABLE: + return gtk_widget_get_focusable (GTK_WIDGET (entry->entry)); + case GTK_ACCESSIBLE_PLATFORM_STATE_FOCUSED: + return gtk_widget_has_focus (GTK_WIDGET (entry->entry)); + default: + g_assert_not_reached (); + } +} + +static void +gtk_password_entry_accessible_init (GtkAccessibleInterface *iface) +{ + GtkAccessibleInterface *parent_iface = g_type_interface_peek_parent (iface); + iface->get_at_context = parent_iface->get_at_context; + iface->get_platform_state = gtk_password_entry_accessible_get_platform_state; +} + GtkText * gtk_password_entry_get_text_widget (GtkPasswordEntry *entry) { diff --git a/gtk/gtkscalebutton.c b/gtk/gtkscalebutton.c index 60e28c52b1..a6d78cbdb5 100644 --- a/gtk/gtkscalebutton.c +++ b/gtk/gtkscalebutton.c @@ -414,6 +414,12 @@ gtk_scale_button_init (GtkScaleButton *button) g_object_ref_sink (priv->adjustment); gtk_range_set_adjustment (GTK_RANGE (priv->scale), priv->adjustment); + gtk_accessible_update_property (GTK_ACCESSIBLE (button), + GTK_ACCESSIBLE_PROPERTY_VALUE_MAX, gtk_adjustment_get_upper (priv->adjustment), + GTK_ACCESSIBLE_PROPERTY_VALUE_MIN, gtk_adjustment_get_lower (priv->adjustment), + GTK_ACCESSIBLE_PROPERTY_VALUE_NOW, gtk_adjustment_get_value (priv->adjustment), + -1); + gtk_widget_add_css_class (GTK_WIDGET (button), "scale"); controller = gtk_event_controller_scroll_new (GTK_EVENT_CONTROLLER_SCROLL_VERTICAL); @@ -677,6 +683,13 @@ gtk_scale_button_set_adjustment (GtkScaleButton *button, gtk_range_set_adjustment (GTK_RANGE (priv->scale), adjustment); g_object_notify (G_OBJECT (button), "adjustment"); + + gtk_accessible_update_property (GTK_ACCESSIBLE (button), + GTK_ACCESSIBLE_PROPERTY_VALUE_MAX, gtk_adjustment_get_upper (adjustment), + GTK_ACCESSIBLE_PROPERTY_VALUE_MIN, gtk_adjustment_get_lower (adjustment), + GTK_ACCESSIBLE_PROPERTY_VALUE_NOW, gtk_adjustment_get_value (adjustment), + -1); + } } @@ -939,6 +952,10 @@ cb_scale_value_changed (GtkRange *range, g_signal_emit (button, signals[VALUE_CHANGED], 0, value); g_object_notify (G_OBJECT (button), "value"); + + gtk_accessible_update_property (GTK_ACCESSIBLE (button), + GTK_ACCESSIBLE_PROPERTY_VALUE_NOW, value, + -1); } static void diff --git a/gtk/gtksearchentry.c b/gtk/gtksearchentry.c index 4960c98941..78268113c8 100644 --- a/gtk/gtksearchentry.c +++ b/gtk/gtksearchentry.c @@ -29,6 +29,7 @@ #include "gtksearchentryprivate.h" +#include "gtkaccessibleprivate.h" #include "gtkeditable.h" #include "gtkboxlayout.h" #include "gtkgestureclick.h" @@ -135,8 +136,11 @@ struct _GtkSearchEntryClass }; static void gtk_search_entry_editable_init (GtkEditableInterface *iface); +static void gtk_search_entry_accessible_init (GtkAccessibleInterface *iface); G_DEFINE_TYPE_WITH_CODE (GtkSearchEntry, gtk_search_entry, GTK_TYPE_WIDGET, + G_IMPLEMENT_INTERFACE (GTK_TYPE_ACCESSIBLE, + gtk_search_entry_accessible_init) G_IMPLEMENT_INTERFACE (GTK_TYPE_EDITABLE, gtk_search_entry_editable_init)) @@ -438,6 +442,31 @@ gtk_search_entry_editable_init (GtkEditableInterface *iface) iface->get_delegate = gtk_search_entry_get_delegate; } +static gboolean +gtk_search_entry_accessible_get_platform_state (GtkAccessible *self, + GtkAccessiblePlatformState state) +{ + GtkSearchEntry *entry = GTK_SEARCH_ENTRY (self); + + switch (state) + { + case GTK_ACCESSIBLE_PLATFORM_STATE_FOCUSABLE: + return gtk_widget_get_focusable (GTK_WIDGET (entry->entry)); + case GTK_ACCESSIBLE_PLATFORM_STATE_FOCUSED: + return gtk_widget_has_focus (GTK_WIDGET (entry->entry)); + default: + g_assert_not_reached (); + } +} + +static void +gtk_search_entry_accessible_init (GtkAccessibleInterface *iface) +{ + GtkAccessibleInterface *parent_iface = g_type_interface_peek_parent (iface); + iface->get_at_context = parent_iface->get_at_context; + iface->get_platform_state = gtk_search_entry_accessible_get_platform_state; +} + static void gtk_search_entry_icon_release (GtkGestureClick *press, int n_press, diff --git a/gtk/gtkspinbutton.c b/gtk/gtkspinbutton.c index b0cbfcf852..b0ff1672f0 100644 --- a/gtk/gtkspinbutton.c +++ b/gtk/gtkspinbutton.c @@ -29,6 +29,9 @@ #include "gtkspinbutton.h" +#include "gtkspinbuttonprivate.h" + +#include "gtkaccessibleprivate.h" #include "gtkadjustment.h" #include "gtkbox.h" #include "gtkbutton.h" @@ -305,12 +308,15 @@ static void gtk_spin_button_default_output (GtkSpinButton *spin_button); static void gtk_spin_button_update_width_chars (GtkSpinButton *spin_button); +static void gtk_spin_button_accessible_init (GtkAccessibleInterface *iface); static guint spinbutton_signals[LAST_SIGNAL] = {0}; static GParamSpec *spinbutton_props[NUM_SPINBUTTON_PROPS] = {NULL, }; G_DEFINE_TYPE_WITH_CODE (GtkSpinButton, gtk_spin_button, GTK_TYPE_WIDGET, G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL) + G_IMPLEMENT_INTERFACE (GTK_TYPE_ACCESSIBLE, + gtk_spin_button_accessible_init) G_IMPLEMENT_INTERFACE (GTK_TYPE_EDITABLE, gtk_spin_button_editable_init) G_IMPLEMENT_INTERFACE (GTK_TYPE_CELL_EDITABLE, @@ -572,6 +578,31 @@ gtk_spin_button_editable_init (GtkEditableInterface *iface) iface->insert_text = gtk_spin_button_insert_text; } +static gboolean +gtk_spin_button_accessible_get_platform_state (GtkAccessible *self, + GtkAccessiblePlatformState state) +{ + GtkSpinButton *spin_button = GTK_SPIN_BUTTON (self); + + switch (state) + { + case GTK_ACCESSIBLE_PLATFORM_STATE_FOCUSABLE: + return gtk_widget_get_focusable (spin_button->entry); + case GTK_ACCESSIBLE_PLATFORM_STATE_FOCUSED: + return gtk_widget_has_focus (spin_button->entry); + default: + g_assert_not_reached (); + } +} + +static void +gtk_spin_button_accessible_init (GtkAccessibleInterface *iface) +{ + GtkAccessibleInterface *parent_iface = g_type_interface_peek_parent (iface); + iface->get_at_context = parent_iface->get_at_context; + iface->get_platform_state = gtk_spin_button_accessible_get_platform_state; +} + static void gtk_cell_editable_spin_button_activated (GtkText *text, GtkSpinButton *spin) { @@ -2310,3 +2341,10 @@ gtk_spin_button_update (GtkSpinButton *spin_button) else gtk_spin_button_set_value (spin_button, val); } + +GtkText * +gtk_spin_button_get_text_widget (GtkSpinButton *spin_button) +{ + return GTK_TEXT (spin_button->entry); +} + diff --git a/gtk/gtkspinbuttonprivate.h b/gtk/gtkspinbuttonprivate.h new file mode 100644 index 0000000000..2a83077faa --- /dev/null +++ b/gtk/gtkspinbuttonprivate.h @@ -0,0 +1,27 @@ +/* GTK - The GIMP Toolkit + * 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/>. + */ + +#pragma once + +#include <gtk/gtkspinbutton.h> +#include <gtk/gtktext.h> + +G_BEGIN_DECLS + +GtkText *gtk_spin_button_get_text_widget (GtkSpinButton *spin_button); + +G_END_DECLS diff --git a/gtk/gtkstack.c b/gtk/gtkstack.c index 8fd95a39f1..a9a0902c23 100644 --- a/gtk/gtkstack.c +++ b/gtk/gtkstack.c @@ -23,6 +23,9 @@ #include <gtk/gtk.h> #include "gtkstack.h" +#include "gtkenums.h" +#include "gtkaccessibleprivate.h" +#include "gtkatcontextprivate.h" #include "gtkprivate.h" #include "gtkintl.h" #include "gtkprogresstrackerprivate.h" @@ -77,6 +80,11 @@ * # CSS nodes * * GtkStack has a single CSS node named stack. + * + * # Accessibility + * + * GtkStack uses the #GTK_ACCESSIBLE_ROLE_TAB_PANEL for the stack + * pages. */ /** @@ -181,7 +189,9 @@ enum CHILD_PROP_NEEDS_ATTENTION, CHILD_PROP_VISIBLE, CHILD_PROP_USE_UNDERLINE, - LAST_CHILD_PROP + LAST_CHILD_PROP, + + PROP_ACCESSIBLE_ROLE }; struct _GtkStackPage @@ -193,6 +203,9 @@ struct _GtkStackPage char *title; char *icon_name; GtkWidget *last_focus; + + GtkATContext *at_context; + guint needs_attention : 1; guint visible : 1; guint use_underline : 1; @@ -207,7 +220,39 @@ struct _GtkStackPageClass static GParamSpec *stack_props[LAST_PROP] = { NULL, }; static GParamSpec *stack_page_props[LAST_CHILD_PROP] = { NULL, }; -G_DEFINE_TYPE (GtkStackPage, gtk_stack_page, G_TYPE_OBJECT) +static GtkATContext * +gtk_stack_page_accessible_get_at_context (GtkAccessible *accessible) +{ + GtkStackPage *page = GTK_STACK_PAGE (accessible); + + if (page->at_context == NULL) + { + GtkAccessibleRole role = GTK_ACCESSIBLE_ROLE_TAB_PANEL; + GdkDisplay *display = gtk_widget_get_display (page->widget); + + page->at_context = gtk_at_context_create (role, accessible, display); + } + + return page->at_context; +} + +static gboolean +gtk_stack_page_accessible_get_platform_state (GtkAccessible *self, + GtkAccessiblePlatformState state) +{ + return FALSE; +} + +static void +gtk_stack_page_accessible_init (GtkAccessibleInterface *iface) +{ + iface->get_at_context = gtk_stack_page_accessible_get_at_context; + iface->get_platform_state = gtk_stack_page_accessible_get_platform_state; +} + +G_DEFINE_TYPE_WITH_CODE (GtkStackPage, gtk_stack_page, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (GTK_TYPE_ACCESSIBLE, + gtk_stack_page_accessible_init)) static void gtk_stack_page_init (GtkStackPage *page) @@ -270,6 +315,10 @@ gtk_stack_page_get_property (GObject *object, g_value_set_boolean (value, gtk_stack_page_get_use_underline (info)); break; + case PROP_ACCESSIBLE_ROLE: + g_value_set_enum (value, GTK_ACCESSIBLE_ROLE_TAB_PANEL); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; @@ -314,6 +363,9 @@ gtk_stack_page_set_property (GObject *object, gtk_stack_page_set_use_underline (info, g_value_get_boolean (value)); break; + case PROP_ACCESSIBLE_ROLE: + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; @@ -386,6 +438,8 @@ gtk_stack_page_class_init (GtkStackPageClass *class) GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); g_object_class_install_properties (object_class, LAST_CHILD_PROP, stack_page_props); + + g_object_class_override_property (object_class, PROP_ACCESSIBLE_ROLE, "accessible-role"); } #define GTK_TYPE_STACK_PAGES (gtk_stack_pages_get_type ()) diff --git a/gtk/gtkstackswitcher.c b/gtk/gtkstackswitcher.c index 00b9ab3524..86fe74d418 100644 --- a/gtk/gtkstackswitcher.c +++ b/gtk/gtkstackswitcher.c @@ -58,6 +58,11 @@ * When circumstances require it, GtkStackSwitcher adds the * .needs-attention style class to the widgets representing the * stack pages. + * + * # Accessibility + * + * GtkStackSwitcher uses the #GTK_ACCESSIBLE_ROLE_TAB_LIST role + * and uses the #GTK_ACCESSIBLE_ROLE_TAB for its buttons. */ #define TIMEOUT_EXPAND 500 @@ -247,7 +252,9 @@ add_child (guint position, GtkStackPage *page; GtkEventController *controller; - button = gtk_toggle_button_new (); + button = g_object_new (GTK_TYPE_TOGGLE_BUTTON, + "accessible-role", GTK_ACCESSIBLE_ROLE_TAB, + NULL); gtk_widget_set_focus_on_click (button, FALSE); controller = gtk_drop_controller_motion_new (); @@ -264,6 +271,14 @@ add_child (guint position, selected = gtk_selection_model_is_selected (self->pages, position); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), selected); + gtk_accessible_update_state (GTK_ACCESSIBLE (button), + GTK_ACCESSIBLE_STATE_SELECTED, selected, + -1); + + gtk_accessible_update_relation (GTK_ACCESSIBLE (button), + GTK_ACCESSIBLE_RELATION_CONTROLS, g_list_append (NULL, page), + -1); + g_signal_connect (button, "notify::active", G_CALLBACK (on_button_toggled), self); g_signal_connect (page, "notify", G_CALLBACK (on_page_updated), self); @@ -328,6 +343,10 @@ selection_changed_cb (GtkSelectionModel *model, { selected = gtk_selection_model_is_selected (switcher->pages, i); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), selected); + + gtk_accessible_update_state (GTK_ACCESSIBLE (button), + GTK_ACCESSIBLE_STATE_SELECTED, selected, + -1); } g_object_unref (page); } @@ -497,6 +516,7 @@ gtk_stack_switcher_class_init (GtkStackSwitcherClass *class) gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BOX_LAYOUT); gtk_widget_class_set_css_name (widget_class, I_("stackswitcher")); + gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_TAB_LIST); } /** diff --git a/gtk/gtktestatcontext.c b/gtk/gtktestatcontext.c index e1bf0f5d1d..6358c196ce 100644 --- a/gtk/gtktestatcontext.c +++ b/gtk/gtktestatcontext.c @@ -23,6 +23,7 @@ #include "gtktestatcontextprivate.h" #include "gtkatcontextprivate.h" +#include "gtkaccessibleprivate.h" #include "gtkdebug.h" #include "gtkenums.h" #include "gtkprivate.h" @@ -45,6 +46,7 @@ gtk_test_at_context_state_change (GtkATContext *self, GtkAccessibleStateChange changed_states, GtkAccessiblePropertyChange changed_properties, GtkAccessibleRelationChange changed_relations, + GtkAccessiblePlatformChange changed_platform, GtkAccessibleAttributeSet *states, GtkAccessibleAttributeSet *properties, GtkAccessibleAttributeSet *relations) diff --git a/gtk/gtktext.c b/gtk/gtktext.c index 6910aeff8b..26bf27eeb5 100644 --- a/gtk/gtktext.c +++ b/gtk/gtktext.c @@ -1482,6 +1482,7 @@ gtk_text_class_init (GtkTextClass *class) "text.redo", NULL); gtk_widget_class_set_css_name (widget_class, I_("text")); + gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_NONE); } static void @@ -3376,7 +3377,7 @@ gtk_text_delete_selection (GtkText *self) int start_pos = MIN (priv->selection_bound, priv->current_pos); int end_pos = MAX (priv->selection_bound, priv->current_pos); - gtk_text_delete_text (self, start_pos, end_pos); + gtk_editable_delete_text (GTK_EDITABLE (self), start_pos, end_pos); } static void @@ -3871,7 +3872,7 @@ gtk_text_insert_at_cursor (GtkText *self, if (priv->editable) { gtk_text_reset_im_context (self); - gtk_text_insert_text (self, str, -1, &pos); + gtk_editable_insert_text (GTK_EDITABLE (self), str, -1, &pos); gtk_text_set_selection_bounds (self, pos, pos); } } @@ -3904,7 +3905,7 @@ gtk_text_delete_from_cursor (GtkText *self, { case GTK_DELETE_CHARS: end_pos = gtk_text_move_logically (self, priv->current_pos, count); - gtk_text_delete_text (self, MIN (start_pos, end_pos), MAX (start_pos, end_pos)); + gtk_editable_delete_text (GTK_EDITABLE (self), MIN (start_pos, end_pos), MAX (start_pos, end_pos)); break; case GTK_DELETE_WORDS: @@ -3934,21 +3935,21 @@ gtk_text_delete_from_cursor (GtkText *self, count--; } - gtk_text_delete_text (self, start_pos, end_pos); + gtk_editable_delete_text (GTK_EDITABLE (self), start_pos, end_pos); break; case GTK_DELETE_DISPLAY_LINE_ENDS: case GTK_DELETE_PARAGRAPH_ENDS: if (count < 0) - gtk_text_delete_text (self, 0, priv->current_pos); + gtk_editable_delete_text (GTK_EDITABLE (self), 0, priv->current_pos); else - gtk_text_delete_text (self, priv->current_pos, -1); + gtk_editable_delete_text (GTK_EDITABLE (self), priv->current_pos, -1); break; case GTK_DELETE_DISPLAY_LINES: case GTK_DELETE_PARAGRAPHS: - gtk_text_delete_text (self, 0, -1); + gtk_editable_delete_text (GTK_EDITABLE (self), 0, -1); break; case GTK_DELETE_WHITESPACE: @@ -4008,12 +4009,12 @@ gtk_text_backspace (GtkText *self) G_NORMALIZE_NFD); len = g_utf8_strlen (normalized_text, -1); - gtk_text_delete_text (self, prev_pos, priv->current_pos); + gtk_editable_delete_text (GTK_EDITABLE (self), prev_pos, priv->current_pos); if (len > 1) { int pos = priv->current_pos; - gtk_text_insert_text (self, normalized_text, + gtk_editable_insert_text (GTK_EDITABLE (self), normalized_text, g_utf8_offset_to_pointer (normalized_text, len - 1) - normalized_text, &pos); gtk_text_set_selection_bounds (self, pos, pos); @@ -4024,7 +4025,7 @@ gtk_text_backspace (GtkText *self) } else { - gtk_text_delete_text (self, prev_pos, priv->current_pos); + gtk_editable_delete_text (GTK_EDITABLE (self), prev_pos, priv->current_pos); } } else @@ -4238,9 +4239,9 @@ gtk_text_delete_surrounding_cb (GtkIMContext *context, GtkTextPrivate *priv = gtk_text_get_instance_private (self); if (priv->editable) - gtk_text_delete_text (self, - priv->current_pos + offset, - priv->current_pos + offset + n_chars); + gtk_editable_delete_text (GTK_EDITABLE (self), + priv->current_pos + offset, + priv->current_pos + offset + n_chars); return TRUE; } @@ -4274,7 +4275,7 @@ gtk_text_enter_text (GtkText *self, } tmp_pos = priv->current_pos; - gtk_text_insert_text (self, str, strlen (str), &tmp_pos); + gtk_editable_insert_text (GTK_EDITABLE (self), str, strlen (str), &tmp_pos); gtk_text_set_selection_bounds (self, tmp_pos, tmp_pos); priv->need_im_reset = old_need_im_reset; @@ -5165,7 +5166,7 @@ gtk_text_delete_whitespace (GtkText *self) end++; if (start != end) - gtk_text_delete_text (self, start, end); + gtk_editable_delete_text (GTK_EDITABLE (self), start, end); } @@ -5233,7 +5234,7 @@ paste_received (GObject *clipboard, gtk_text_delete_selection (self); pos = priv->current_pos; - gtk_text_insert_text (self, text, length, &pos); + gtk_editable_insert_text (GTK_EDITABLE (self), text, length, &pos); gtk_text_set_selection_bounds (self, pos, pos); end_change (self); @@ -5430,6 +5431,10 @@ gtk_text_set_editable (GtkText *self, gtk_text_update_clipboard_actions (self); gtk_text_update_emoji_action (self); + gtk_accessible_update_property (GTK_ACCESSIBLE (self), + GTK_ACCESSIBLE_PROPERTY_READ_ONLY, !priv->editable, + -1); + g_object_notify (G_OBJECT (self), "editable"); } } @@ -5454,9 +5459,9 @@ gtk_text_set_text (GtkText *self, begin_change (self); g_object_freeze_notify (G_OBJECT (self)); - gtk_text_delete_text (self, 0, -1); + gtk_editable_delete_text (GTK_EDITABLE (self), 0, -1); tmp_pos = 0; - gtk_text_insert_text (self, text, strlen (text), &tmp_pos); + gtk_editable_insert_text (GTK_EDITABLE (self), text, strlen (text), &tmp_pos); g_object_thaw_notify (G_OBJECT (self)); end_change (self); @@ -6240,7 +6245,7 @@ gtk_text_drag_drop (GtkDropTarget *dest, drop_position < priv->selection_bound || drop_position > priv->current_pos) { - gtk_text_insert_text (self, str, length, &drop_position); + gtk_editable_insert_text (GTK_EDITABLE (self), str, length, &drop_position); } else { @@ -6249,7 +6254,7 @@ gtk_text_drag_drop (GtkDropTarget *dest, begin_change (self); gtk_text_delete_selection (self); pos = MIN (priv->selection_bound, priv->current_pos); - gtk_text_insert_text (self, str, length, &pos); + gtk_editable_insert_text (GTK_EDITABLE (self), str, length, &pos); end_change (self); } @@ -6811,7 +6816,7 @@ emoji_picked (GtkEmojiChooser *chooser, gtk_text_delete_selection (self); pos = priv->current_pos; - gtk_text_insert_text (self, text, -1, &pos); + gtk_editable_insert_text (GTK_EDITABLE (self), text, -1, &pos); gtk_text_set_selection_bounds (self, pos, pos); end_change (self); } diff --git a/gtk/gtkwidget.c b/gtk/gtkwidget.c index c87f9304a7..a1c3a069b7 100644 --- a/gtk/gtkwidget.c +++ b/gtk/gtkwidget.c @@ -4948,6 +4948,9 @@ gtk_widget_set_focusable (GtkWidget *widget, priv->focusable = focusable; gtk_widget_queue_resize (widget); + + gtk_accessible_platform_changed (GTK_ACCESSIBLE (widget), GTK_ACCESSIBLE_PLATFORM_CHANGE_FOCUSABLE); + g_object_notify_by_pspec (G_OBJECT (widget), widget_props[PROP_FOCUSABLE]); } @@ -8092,10 +8095,14 @@ gtk_widget_accessible_get_at_context (GtkAccessible *accessible) GtkWidget *self = GTK_WIDGET (accessible); GtkWidgetPrivate *priv = gtk_widget_get_instance_private (self); + if (priv->in_destruction) + return NULL; + if (priv->at_context == NULL) { GtkWidgetClass *widget_class = GTK_WIDGET_GET_CLASS (self); GtkWidgetClassPrivate *class_priv = widget_class->priv; + GdkDisplay *display = _gtk_widget_get_display (self); GtkAccessibleRole role; /* Widgets have two options to set the accessible role: either they @@ -8112,16 +8119,32 @@ gtk_widget_accessible_get_at_context (GtkAccessible *accessible) role = class_priv->accessible_role; priv->accessible_role = role; - priv->at_context = gtk_at_context_create (role, accessible); + priv->at_context = gtk_at_context_create (role, accessible, display); } return priv->at_context; } +static gboolean +gtk_widget_accessible_get_platform_state (GtkAccessible *self, + GtkAccessiblePlatformState state) +{ + switch (state) + { + case GTK_ACCESSIBLE_PLATFORM_STATE_FOCUSABLE: + return gtk_widget_get_focusable (GTK_WIDGET (self)); + case GTK_ACCESSIBLE_PLATFORM_STATE_FOCUSED: + return gtk_widget_has_focus (GTK_WIDGET (self)); + default: + g_assert_not_reached (); + } +} + static void gtk_widget_accessible_interface_init (GtkAccessibleInterface *iface) { iface->get_at_context = gtk_widget_accessible_get_at_context; + iface->get_platform_state = gtk_widget_accessible_get_platform_state; } /* @@ -9751,6 +9774,8 @@ gtk_widget_set_has_focus (GtkWidget *widget, priv->has_focus = has_focus; + gtk_accessible_platform_changed (GTK_ACCESSIBLE (widget), GTK_ACCESSIBLE_PLATFORM_CHANGE_FOCUSED); + g_object_notify_by_pspec (G_OBJECT (widget), widget_props[PROP_HAS_FOCUS]); } @@ -12138,6 +12163,10 @@ gtk_widget_update_orientation (GtkWidget *widget, gtk_widget_add_css_class (widget, "vertical"); gtk_widget_remove_css_class (widget, "horizontal"); } + + gtk_accessible_update_property (GTK_ACCESSIBLE (widget), + GTK_ACCESSIBLE_PROPERTY_ORIENTATION, orientation, + -1); } /** diff --git a/gtk/meson.build b/gtk/meson.build index 006ac8f31e..614b4079f8 100644 --- a/gtk/meson.build +++ b/gtk/meson.build @@ -1,3 +1,4 @@ +subdir('a11y') subdir('deprecated') subdir('inspector') @@ -961,6 +962,7 @@ gtk_gen_headers = [ ] gtk_sources += [ + gtk_a11y_src, gtk_dbus_src, gtk_deprecated_sources, inspector_sources, diff --git a/testsuite/a11y/accessible.c b/testsuite/a11y/accessible.c index b8daf9e182..b353d9e94e 100644 --- a/testsuite/a11y/accessible.c +++ b/testsuite/a11y/accessible.c @@ -29,7 +29,9 @@ test_object_accessible_get_at_context (GtkAccessible *accessible) TestObject *self = (TestObject*)accessible; if (self->at_context == NULL) - self->at_context = gtk_at_context_create (self->role, accessible); + self->at_context = gtk_at_context_create (self->role, + accessible, + gdk_display_get_default ()); return self->at_context; } diff --git a/testsuite/css/api.test.in b/testsuite/css/api.test.in index ee5a0b38e4..d1d25a1bc1 100644 --- a/testsuite/css/api.test.in +++ b/testsuite/css/api.test.in @@ -1,4 +1,4 @@ [Test] -Exec=@libexecdir@/installed-tests/gtk-4.0/css/api --tap -k +Exec=/bin/sh -c "env G_ENABLE_DIAGNOSTIC=0 GTK_TEST_ACCESSIBLE=1 @libexecdir@/installed-tests/gtk-4.0/css/api --tap -k" Type=session Output=TAP diff --git a/testsuite/css/change/change.test.in b/testsuite/css/change/change.test.in index 0996111779..9906e9f95b 100644 --- a/testsuite/css/change/change.test.in +++ b/testsuite/css/change/change.test.in @@ -1,4 +1,4 @@ [Test] -Exec=@libexecdir@/installed-tests/gtk-4.0/css/change/test-css-change --tap -k +Exec=/bin/sh -c "env G_ENABLE_DIAGNOSTIC=0 GTK_TEST_ACCESSIBLE=1 @libexecdir@/installed-tests/gtk-4.0/css/change/test-css-change --tap -k" Type=session Output=TAP diff --git a/testsuite/css/change/meson.build b/testsuite/css/change/meson.build index 04204ed5ba..83e37ae2f6 100644 --- a/testsuite/css/change/meson.build +++ b/testsuite/css/change/meson.build @@ -9,14 +9,13 @@ test_change = executable( install: get_option('install-tests'), install_dir: testexecdir, ) + test('change', test_change, - args: [ '--tap', '-k' ], - protocol: 'tap', - env: [ - 'G_TEST_SRCDIR=@0@'.format(meson.current_source_dir()), - 'G_TEST_BUILDDIR=@0@'.format(meson.current_build_dir()) - ], - suite: 'css') + args: [ '--tap', '-k' ], + protocol: 'tap', + env: csstest_env, + suite: 'css', +) test_data = [ 'test1.css', 'test1.ui', 'test1.nodes', diff --git a/testsuite/css/meson.build b/testsuite/css/meson.build index bac0ebd074..931502bef7 100644 --- a/testsuite/css/meson.build +++ b/testsuite/css/meson.build @@ -1,3 +1,12 @@ +csstest_env = environment() +csstest_env.set('GTK_TEST_ACCESSIBLE', '1') +csstest_env.set('GSK_RENDERER', 'cairo') +csstest_env.set('G_TEST_SRCDIR', meson.current_source_dir()) +csstest_env.set('G_TEST_BUILDDIR', meson.current_build_dir()) +csstest_env.set('GIO_USE_VFS', 'local') +csstest_env.set('GSETTINGS_BACKEND', 'memory') +csstest_env.set('G_ENABLE_DIAGNOSTIC', '0') + subdir('parser') subdir('nodes') subdir('style') @@ -14,10 +23,7 @@ test_api = executable('api', 'api.c', test('api', test_api, args: ['--tap', '-k' ], protocol: 'tap', - env: [ - 'G_TEST_SRCDIR=@0@'.format(meson.current_source_dir()), - 'G_TEST_BUILDDIR=@0@'.format(meson.current_build_dir()) - ], + env: csstest_env, suite: 'css') test_data = executable('data', ['data.c', '../../gtk/css/gtkcssdataurl.c'], @@ -29,10 +35,7 @@ test_data = executable('data', ['data.c', '../../gtk/css/gtkcssdataurl.c'], test('data', test_data, args: ['--tap', '-k' ], protocol: 'tap', - env: [ - 'G_TEST_SRCDIR=@0@'.format(meson.current_source_dir()), - 'G_TEST_BUILDDIR=@0@'.format(meson.current_build_dir()) - ], + env: csstest_env, suite: 'css') if get_option('install-tests') @@ -46,20 +49,24 @@ endif if false and get_option ('profiler') + adwaita_env = csstest_env + adwaita_env.set('GTK_THEME', 'Adwaita') test('performance-adwaita', test_performance, args: [ '--mark', 'css validation', '--name', 'performance-adwaita', '--output', join_paths(meson.current_build_dir(), 'output'), join_paths(meson.current_build_dir(), '../../demos/widget-factory/gtk4-widget-factory') ], - env: [ 'GTK_THEME=Adwaita' ], + env: adwaita_env, suite: [ 'css' ]) + empty_env = csstest_env + empty_env.set('GTK_THEME', 'Empty') test('performance-empty', test_performance, args: [ '--mark', 'css validation', '--name', 'performance-empty', '--output', join_paths(meson.current_build_dir(), 'output'), join_paths(meson.current_build_dir(), '../../demos/widget-factory/gtk4-widget-factory') ], - env: [ 'GTK_THEME=Empty' ], + env: empty_env, suite: [ 'css' ]) endif diff --git a/testsuite/css/nodes/meson.build b/testsuite/css/nodes/meson.build index 720ba46db8..11ed48b9e5 100644 --- a/testsuite/css/nodes/meson.build +++ b/testsuite/css/nodes/meson.build @@ -7,13 +7,11 @@ test_nodes = executable('test-css-nodes', 'test-css-nodes.c', install_dir: testexecdir, dependencies: libgtk_dep) test('nodes', test_nodes, - args: [ '--tap', '-k' ], - protocol: 'tap', - env: [ - 'G_TEST_SRCDIR=@0@'.format(meson.current_source_dir()), - 'G_TEST_BUILDDIR=@0@'.format(meson.current_build_dir()) - ], - suite: 'css') + args: [ '--tap', '-k' ], + protocol: 'tap', + env: csstest_env, + suite: 'css', +) test_data = [ 'box.ltr.nodes', @@ -77,5 +75,4 @@ if get_option('install-tests') install_dir: testdatadir) install_data(test_data, install_dir: testexecdir) - endif diff --git a/testsuite/css/nodes/nodes.test.in b/testsuite/css/nodes/nodes.test.in index bf5331999f..e662b746b6 100644 --- a/testsuite/css/nodes/nodes.test.in +++ b/testsuite/css/nodes/nodes.test.in @@ -1,4 +1,4 @@ [Test] -Exec=@libexecdir@/installed-tests/gtk-4.0/css/nodes/test-css-nodes --tap -k +Exec=/bin/sh -c "env G_ENABLE_DIAGNOSTIC=0 GTK_TEST_ACCESSIBLE=1 @libexecdir@/installed-tests/gtk-4.0/css/nodes/test-css-nodes --tap -k" Type=session Output=TAP diff --git a/testsuite/css/parser/parser.test.in b/testsuite/css/parser/parser.test.in index 2312d7305d..91bb6d20f6 100644 --- a/testsuite/css/parser/parser.test.in +++ b/testsuite/css/parser/parser.test.in @@ -1,4 +1,4 @@ [Test] -Exec=@libexecdir@/installed-tests/gtk-4.0/css/parser/test-css-parser --tap -k +Exec=/bin/sh -c "env G_ENABLE_DIAGNOSTIC=0 GTK_TEST_ACCESSIBLE=1 @libexecdir@/installed-tests/gtk-4.0/css/parser/test-css-parser --tap -k" Type=session Output=TAP diff --git a/testsuite/css/style/meson.build b/testsuite/css/style/meson.build index cadbf8dc16..b54adf7c68 100644 --- a/testsuite/css/style/meson.build +++ b/testsuite/css/style/meson.build @@ -17,13 +17,11 @@ test_style = executable( install_dir: testexecdir, ) test('style', test_style, - args: [ '--tap', '-k' ], - protocol: 'tap', - env: [ - 'G_TEST_SRCDIR=@0@'.format(meson.current_source_dir()), - 'G_TEST_BUILDDIR=@0@'.format(meson.current_build_dir()) - ], - suite: 'css') + args: [ '--tap', '-k' ], + protocol: 'tap', + env: csstest_env, + suite: 'css', +) test_data = [ 'adjacent-states.css', @@ -65,5 +63,4 @@ if get_option('install-tests') install_dir: testdatadir) install_data(test_data, install_dir: testexecdir) - endif diff --git a/testsuite/css/style/style.test.in b/testsuite/css/style/style.test.in index 2990614b51..6cce32a0a0 100644 --- a/testsuite/css/style/style.test.in +++ b/testsuite/css/style/style.test.in @@ -1,4 +1,4 @@ [Test] -Exec=@libexecdir@/installed-tests/gtk-4.0/css/style/test-css-style --tap -k +Exec=/bin/sh -c "env G_ENABLE_DIAGNOSTIC=0 GTK_TEST_ACCESSIBLE=1 @libexecdir@/installed-tests/gtk-4.0/css/style/test-css-style --tap -k" Type=session Output=TAP diff --git a/testsuite/gdk/gdk.test.in b/testsuite/gdk/gdk.test.in index 709cb6a6bd..ad90aa61ed 100644 --- a/testsuite/gdk/gdk.test.in +++ b/testsuite/gdk/gdk.test.in @@ -1,4 +1,4 @@ [Test] -Exec=@testexecdir@/@test@ --tap -k --verbose +Exec=/bin/sh -c "env G_ENABLE_DIAGNOSTIC=0 GTK_TEST_ACCESSIBLE=1 @testexecdir@/@test@ --tap -k --verbose" Type=session Output=TAP diff --git a/testsuite/gdk/meson.build b/testsuite/gdk/meson.build index bd7744ee0e..aa93f8384d 100644 --- a/testsuite/gdk/meson.build +++ b/testsuite/gdk/meson.build @@ -16,28 +16,31 @@ tests = [ foreach t : tests test_exe = executable(t, '@0@.c'.format(t), - c_args: common_cflags, - dependencies: libgtk_dep, - install: get_option('install-tests'), - install_dir: testexecdir) + c_args: common_cflags, + dependencies: libgtk_dep, + install: get_option('install-tests'), + install_dir: testexecdir, + ) test(t, test_exe, - args: [ '--tap', '-k' ], - protocol: 'tap', - env: [ - 'G_TEST_SRCDIR=@0@'.format(meson.current_source_dir()), - 'G_TEST_BUILDDIR=@0@'.format(meson.current_build_dir()) - ], - suite: 'gdk') + args: [ '--tap', '-k' ], + protocol: 'tap', + env: [ + 'G_TEST_SRCDIR=@0@'.format(meson.current_source_dir()), + 'G_TEST_BUILDDIR=@0@'.format(meson.current_build_dir()), + ], + suite: 'gdk', + ) if get_option('install-tests') test_cdata = configuration_data() test_cdata.set('testexecdir', testexecdir) test_cdata.set('test', t) configure_file(input: 'gdk.test.in', - output: '@0@.test'.format(t), - configuration: test_cdata, - install: true, - install_dir: testdatadir) + output: '@0@.test'.format(t), + configuration: test_cdata, + install: true, + install_dir: testdatadir, + ) endif endforeach diff --git a/testsuite/gsk/meson.build b/testsuite/gsk/meson.build index d9cb63f1ec..07b2d05955 100644 --- a/testsuite/gsk/meson.build +++ b/testsuite/gsk/meson.build @@ -101,6 +101,7 @@ foreach renderer : renderers join_paths(meson.current_source_dir(), 'compare', test + '.png')], env: [ 'GSK_RENDERER=' + renderer[0], + 'GTK_TEST_ACCESSIBLE=1', 'G_TEST_SRCDIR=@0@'.format(meson.current_source_dir()), 'G_TEST_BUILDDIR=@0@'.format(meson.current_build_dir()) ], @@ -178,6 +179,7 @@ foreach test : node_parser_tests ], env: [ 'GSK_RENDERER=opengl', + 'GTK_TEST_ACCESSIBLE=1', 'G_TEST_SRCDIR=@0@'.format(meson.current_source_dir()), 'G_TEST_BUILDDIR=@0@'.format(meson.current_build_dir()) ], @@ -211,6 +213,7 @@ foreach t : tests protocol: 'tap', env: [ 'GSK_RENDERER=cairo', + 'GTK_TEST_ACCESSIBLE=1', 'G_TEST_SRCDIR=@0@'.format(meson.current_source_dir()), 'G_TEST_BUILDDIR=@0@'.format(meson.current_build_dir()) ], diff --git a/testsuite/gsk/render-nodes-cairo.test.in b/testsuite/gsk/render-nodes-cairo.test.in index 28a4f55d76..8976ff03d8 100644 --- a/testsuite/gsk/render-nodes-cairo.test.in +++ b/testsuite/gsk/render-nodes-cairo.test.in @@ -1,4 +1,4 @@ [Test] -Exec=/bin/sh -c "env GSK_RENDERER=cairo @libexecdir@/installed-tests/gtk-4.0/gsk/test-render-nodes --tap -k --verbose" +Exec=/bin/sh -c "env G_ENABLE_DIAGNOSTIC=0 GSK_RENDERER=cairo GTK_TEST_ACCESSIBLE=1 @libexecdir@/installed-tests/gtk-4.0/gsk/test-render-nodes --tap -k --verbose" Type=session Output=TAP diff --git a/testsuite/gsk/render-nodes-vulkan.test.in b/testsuite/gsk/render-nodes-vulkan.test.in index d16ba906e4..4ad9523ed4 100644 --- a/testsuite/gsk/render-nodes-vulkan.test.in +++ b/testsuite/gsk/render-nodes-vulkan.test.in @@ -1,4 +1,4 @@ [Test] -Exec=/bin/sh -c "env GSK_RENDERER=vulkan @libexecdir@/installed-tests/gtk-4.0/gsk/test-render-nodes --tap -k --verbose" +Exec=/bin/sh -c "env GTK_TEST_ACCESSIBLE=1 GSK_RENDERER=vulkan @libexecdir@/installed-tests/gtk-4.0/gsk/test-render-nodes --tap -k --verbose" Type=session Output=TAP diff --git a/testsuite/gtk/gtk.test.in b/testsuite/gtk/gtk.test.in index 709cb6a6bd..ad90aa61ed 100644 --- a/testsuite/gtk/gtk.test.in +++ b/testsuite/gtk/gtk.test.in @@ -1,4 +1,4 @@ [Test] -Exec=@testexecdir@/@test@ --tap -k --verbose +Exec=/bin/sh -c "env G_ENABLE_DIAGNOSTIC=0 GTK_TEST_ACCESSIBLE=1 @testexecdir@/@test@ --tap -k --verbose" Type=session Output=TAP diff --git a/testsuite/gtk/meson.build b/testsuite/gtk/meson.build index 11509fa5bc..6b973c1d9b 100644 --- a/testsuite/gtk/meson.build +++ b/testsuite/gtk/meson.build @@ -146,6 +146,15 @@ is_debug = get_option('buildtype').startswith('debug') test_cargs = [] +test_env = environment() +test_env.set('GTK_TEST_ACCESSIBLE', '1') +test_env.set('GSK_RENDERER', 'cairo') +test_env.set('G_TEST_SRCDIR', meson.current_source_dir()) +test_env.set('G_TEST_BUILDDIR', meson.current_build_dir()) +test_env.set('GIO_USE_VFS', 'local') +test_env.set('GSETTINGS_BACKEND', 'memory') +test_env.set('G_ENABLE_DIAGNOSTIC', '0') + if os_unix # tests += [['defaultvalue']] # disabled in Makefile.am as well test_cargs += ['-DHAVE_UNIX_PRINT_WIDGETS'] @@ -182,11 +191,7 @@ foreach t : tests args: [ '--tap', '-k' ], protocol: 'tap', timeout: test_timeout, - env: [ - 'GSK_RENDERER=cairo', - 'G_TEST_SRCDIR=@0@'.format(meson.current_source_dir()), - 'G_TEST_BUILDDIR=@0@'.format(meson.current_build_dir()), - ], + env: test_env, suite: ['gtk'] + test_extra_suites, should_fail: expect_fail, ) @@ -203,10 +208,7 @@ if add_languages('cpp', required: false) test('c++ keywords', test_exe, args: [ '--tap', '-k' ], #protocol: 'tap', - env: [ - 'G_TEST_SRCDIR=@0@'.format(meson.current_source_dir()), - 'G_TEST_BUILDDIR=@0@'.format(meson.current_build_dir()), - ], + env: test_env, suite: 'gtk') if get_option('install-tests') conf = configuration_data() @@ -254,10 +256,7 @@ foreach test : focus_chain_tests test(test[0] + ' ' + test[1], focus_chain, args: [ join_paths(meson.current_source_dir(), 'focus-chain', test[0] + '.ui'), join_paths(meson.current_source_dir(), 'focus-chain', test[0] + '.' + test[1]) ], - env: [ - 'G_TEST_SRCDIR=@0@'.format(meson.current_source_dir()), - 'G_TEST_BUILDDIR=@0@'.format(meson.current_build_dir()) - ], + env: test_env, suite: [ 'gtk', 'focus' ]) endforeach @@ -282,14 +281,17 @@ endif if false and get_option ('profiler') + performance_env = test_env + performance_env.set('GTK_THEME', 'Empty') + test('performance-layout', test_performance, args: [ '--mark', 'size allocation', join_paths(meson.current_build_dir(), '../../demos/widget-factory/gtk4-widget-factory') ], - env: [ 'GTK_THEME=Empty' ], + env: performance_env, suite: [ 'gtk' ]) test('performance-snapshot', test_performance, args: [ '--mark', 'widget snapshot', join_paths(meson.current_build_dir(), '../../demos/widget-factory/gtk4-widget-factory') ], - env: [ 'GTK_THEME=Empty' ], + env: performance_end, suite: [ 'gtk' ]) endif diff --git a/testsuite/reftests/meson.build b/testsuite/reftests/meson.build index 0e8bdbc16e..da1edd67c8 100644 --- a/testsuite/reftests/meson.build +++ b/testsuite/reftests/meson.build @@ -450,6 +450,16 @@ xfails = [ 'label-sizing.ui', ] +reftest_env = environment() +reftest_env.set('GTK_TEST_ACCESSIBLE', '1') +reftest_env.set('GSK_RENDERER', 'opengl') +reftest_env.set('G_TEST_SRCDIR', meson.current_source_dir()) +reftest_env.set('G_TEST_BUILDDIR', meson.current_build_dir()) +reftest_env.set('GIO_USE_VFS', 'local') +reftest_env.set('GSETTINGS_BACKEND', 'memory') +reftest_env.set('G_ENABLE_DIAGNOSTIC', '0') +reftest_env.set('REFTEST_MODULE_DIR', meson.current_build_dir()) + foreach testname : testdata if testname.endswith('.ui') and not testname.endswith('.ref.ui') test('reftest ' + testname, gtk_reftest, @@ -459,12 +469,7 @@ foreach testname : testdata join_paths(meson.current_source_dir(), testname), ], protocol: 'tap', - env: [ - 'GSK_RENDERER=opengl', - 'G_TEST_SRCDIR=@0@'.format(meson.current_source_dir()), - 'G_TEST_BUILDDIR=@0@'.format(meson.current_build_dir()), - 'REFTEST_MODULE_DIR=@0@'.format(meson.current_build_dir()) - ], + env: reftest_env, suite: 'reftest', should_fail: xfails.contains(testname)) endif diff --git a/testsuite/reftests/reftests-dark.test.in b/testsuite/reftests/reftests-dark.test.in index ab0e2fb19f..d30a4f2099 100644 --- a/testsuite/reftests/reftests-dark.test.in +++ b/testsuite/reftests/reftests-dark.test.in @@ -1,4 +1,4 @@ [Test] -Exec=/bin/sh -c "env GTK_THEME=Adwaita:dark G_ENABLE_DIAGNOSTIC=0 @libexecdir@/installed-tests/gtk-4.0/gtk-reftest -k --verbose --tap --output $(pwd) --directory @libexecdir@/installed-tests/gtk-4.0/reftests" +Exec=/bin/sh -c "env GTK_THEME=Adwaita:dark GTK_TEST_ACCESSIBLE=1 G_ENABLE_DIAGNOSTIC=0 @libexecdir@/installed-tests/gtk-4.0/gtk-reftest -k --verbose --tap --output $(pwd) --directory @libexecdir@/installed-tests/gtk-4.0/reftests" Type=session-exclusive Output=TAP diff --git a/testsuite/reftests/reftests-hc.test.in b/testsuite/reftests/reftests-hc.test.in index 9d6cb5655d..88627b7ae4 100644 --- a/testsuite/reftests/reftests-hc.test.in +++ b/testsuite/reftests/reftests-hc.test.in @@ -1,4 +1,4 @@ [Test] -Exec=/bin/sh -c "env GTK_THEME=HighContrast G_ENABLE_DIAGNOSTIC=0 @libexecdir@/installed-tests/gtk-4.0/gtk-reftest -k --verbose --tap --output $(pwd) --directory @libexecdir@/installed-tests/gtk-4.0/reftests" +Exec=/bin/sh -c "env GTK_THEME=HighContrast GTK_TEST_ACCESSIBLE=1 G_ENABLE_DIAGNOSTIC=0 @libexecdir@/installed-tests/gtk-4.0/gtk-reftest -k --verbose --tap --output $(pwd) --directory @libexecdir@/installed-tests/gtk-4.0/reftests" Type=session-exclusive Output=TAP diff --git a/testsuite/reftests/reftests-hci.test.in b/testsuite/reftests/reftests-hci.test.in index 6ea0b1aa3a..24e785551c 100644 --- a/testsuite/reftests/reftests-hci.test.in +++ b/testsuite/reftests/reftests-hci.test.in @@ -1,4 +1,4 @@ [Test] -Exec=/bin/sh -c "env GTK_THEME=HighContrastInverse G_ENABLE_DIAGNOSTIC=0 @libexecdir@/installed-tests/gtk-4.0/gtk-reftest -k --verbose --tap --output $(pwd) --directory @libexecdir@/installed-tests/gtk-4.0/reftests" +Exec=/bin/sh -c "env GTK_THEME=HighContrastInverse GTK_TEST_ACCESSIBLE=1 G_ENABLE_DIAGNOSTIC=0 @libexecdir@/installed-tests/gtk-4.0/gtk-reftest -k --verbose --tap --output $(pwd) --directory @libexecdir@/installed-tests/gtk-4.0/reftests" Type=session-exclusive Output=TAP diff --git a/testsuite/reftests/reftests.test.in b/testsuite/reftests/reftests.test.in index 9eb1864bfa..53971961d6 100644 --- a/testsuite/reftests/reftests.test.in +++ b/testsuite/reftests/reftests.test.in @@ -1,4 +1,4 @@ [Test] -Exec=/bin/sh -c "env G_ENABLE_DIAGNOSTIC=0 @libexecdir@/installed-tests/gtk-4.0/gtk-reftest -k --verbose --tap --output $(pwd) --directory @libexecdir@/installed-tests/gtk-4.0/reftests" +Exec=/bin/sh -c "env GTK_TEST_ACCESSIBLE=1 G_ENABLE_DIAGNOSTIC=0 @libexecdir@/installed-tests/gtk-4.0/gtk-reftest -k --verbose --tap --output $(pwd) --directory @libexecdir@/installed-tests/gtk-4.0/reftests" Type=session-exclusive Output=TAP diff --git a/testsuite/tools/meson.build b/testsuite/tools/meson.build index 85fc54aac4..1369ccfedc 100644 --- a/testsuite/tools/meson.build +++ b/testsuite/tools/meson.build @@ -23,6 +23,7 @@ if bash.found() env: [ 'G_TEST_SRCDIR=@0@'.format(meson.current_source_dir()), 'G_TEST_BUILDDIR=@0@'.format(meson.current_build_dir()), + 'GTK_TEST_ACCESSIBLE=1', 'GTK_BUILDER_TOOL=@0@'.format(get_variable('gtk4_builder_tool').full_path()), 'GTK_QUERY_SETTINGS=@0@'.format(get_variable('gtk4_query_settings').full_path()) ], diff --git a/testsuite/tools/tools.test.in b/testsuite/tools/tools.test.in index 24c47ce34f..d0f3810a44 100644 --- a/testsuite/tools/tools.test.in +++ b/testsuite/tools/tools.test.in @@ -1,4 +1,4 @@ [Test] -Exec=/bin/sh -c "env G_ENABLE_DIAGNOSTIC=0 TEST_DATA_DIR=@testexecdir@/@test@-data @testexecdir@/@test@" +Exec=/bin/sh -c "env GTK_TEST_ACCESSIBLE=1 G_ENABLE_DIAGNOSTIC=0 TEST_DATA_DIR=@testexecdir@/@test@-data @testexecdir@/@test@" Type=session Output=TAP |