diff --git a/docs/reference/gtk/section-accessibility.md b/docs/reference/gtk/section-accessibility.md index 1885b928f8..c3b965da82 100644 --- a/docs/reference/gtk/section-accessibility.md +++ b/docs/reference/gtk/section-accessibility.md @@ -45,14 +45,13 @@ Each role name is part of the #GtkAccessibleRole enumeration. | Role name | Description | Related GTK widget | |-----------|-------------|--------------------| -| `ALERT` | A message with important information | - | -| `BUTTON` | A control that performs an action when pressed | #GtkButton | +| `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 | -| `COLUMNHEADER` | The header of a column in a list or grid | - | | `COMBOBOX` | A control that can be expanded to show a list of possible values to select | #GtkComboBox | | `DIALOG` | A dialog that prompts the user to enter information or require a response | #GtkDialog and subclasses | | `IMG` | An image | #GtkImage, #GtkPicture | -| `LABEL` | A visible name or caption for a user interface component. | #GtkLabel | +| `LABEL` | A visible name or caption for a user interface component | #GtkLabel | +| `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 | #GtkRadioButton | | `SCROLLBAR` | A graphical object controlling the scolling of content | #GtkScrollbar | @@ -60,7 +59,7 @@ Each role name is part of the #GtkAccessibleRole enumeration. | `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 | -| `TEXT_BOX` | A type of input that allows free-form text as its value. | #GtkEntry, #GtkPasswordEntry | +| `TEXT_BOX` | A type of input that allows free-form text as its value. | #GtkEntry, #GtkPasswordEntry, #GtkTextView | | `WINDOW` | An application window | #GtkWindow | | `...` | … | diff --git a/gtk/gtkenums.h b/gtk/gtkenums.h index 23add4f560..d8dbe725b0 100644 --- a/gtk/gtkenums.h +++ b/gtk/gtkenums.h @@ -1194,7 +1194,7 @@ typedef enum { * @GTK_ACCESSIBLE_ROLE_MAIN: Unused * @GTK_ACCESSIBLE_ROLE_MARQUEE: Unused * @GTK_ACCESSIBLE_ROLE_MATH: Unused - * @GTK_ACCESSIBLE_ROLE_METER: Unused + * @GTK_ACCESSIBLE_ROLE_METER: An element that represents a value within a known range. * @GTK_ACCESSIBLE_ROLE_MENU: Unused * @GTK_ACCESSIBLE_ROLE_MENU_BAR: Unused * @GTK_ACCESSIBLE_ROLE_MENU_ITEM: Unused diff --git a/gtk/gtkexpander.c b/gtk/gtkexpander.c index 7993e506d4..343701c2a8 100644 --- a/gtk/gtkexpander.c +++ b/gtk/gtkexpander.c @@ -106,6 +106,10 @@ * GtkExpander has three CSS nodes, the main node with the name expander, * a subnode with name title and node below it with name arrow. The arrow of an * expander that is showing its child gets the :checked pseudoclass added to it. + * + * # Accessibility + * + * GtkExpander uses the #GTK_ACCESSIBLE_ROLE_BUTTON role. */ #include "config.h" @@ -374,6 +378,7 @@ gtk_expander_class_init (GtkExpanderClass *klass) G_TYPE_NONE, 0); gtk_widget_class_set_css_name (widget_class, I_("expander-widget")); + gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_BUTTON); } static void @@ -420,6 +425,10 @@ gtk_expander_init (GtkExpander *expander) gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (gesture), GTK_PHASE_BUBBLE); gtk_widget_add_controller (GTK_WIDGET (expander->title_widget), GTK_EVENT_CONTROLLER (gesture)); + + gtk_accessible_update_state (GTK_ACCESSIBLE (expander), + GTK_ACCESSIBLE_STATE_EXPANDED, FALSE, + -1); } static GtkBuildableIface *parent_buildable_iface; @@ -877,6 +886,10 @@ gtk_expander_set_expanded (GtkExpander *expander, gtk_expander_resize_toplevel (expander); } + gtk_accessible_update_state (GTK_ACCESSIBLE (expander), + GTK_ACCESSIBLE_STATE_EXPANDED, expanded, + -1); + g_object_notify (G_OBJECT (expander), "expanded"); } @@ -1164,6 +1177,8 @@ void gtk_expander_set_child (GtkExpander *expander, GtkWidget *child) { + GList *list = NULL; + g_return_if_fail (GTK_IS_EXPANDER (expander)); g_return_if_fail (child == NULL || GTK_IS_WIDGET (child)); @@ -1188,6 +1203,13 @@ gtk_expander_set_child (GtkExpander *expander, } } + if (expander->child) + list = g_list_append (list, expander->child); + gtk_accessible_update_relation (GTK_ACCESSIBLE (expander), + GTK_ACCESSIBLE_RELATION_CONTROLS, list, + -1); + g_list_free (list); + g_object_notify (G_OBJECT (expander), "child"); } diff --git a/gtk/gtklevelbar.c b/gtk/gtklevelbar.c index b0e286fff3..c324bf7fdc 100644 --- a/gtk/gtklevelbar.c +++ b/gtk/gtklevelbar.c @@ -116,6 +116,10 @@ * * In horizontal orientation, the nodes are always arranged from left to right, * regardless of text direction. + * + * # Accessibility + * + * GtkLevelBar uses the #GTK_ACCESSIBLE_ROLE_METER role. */ #include "config.h" @@ -1007,6 +1011,7 @@ gtk_level_bar_class_init (GtkLevelBarClass *klass) gtk_widget_class_set_layout_manager_type (wclass, GTK_TYPE_BIN_LAYOUT); gtk_widget_class_set_css_name (wclass, I_("levelbar")); + gtk_widget_class_set_accessible_role (wclass, GTK_ACCESSIBLE_ROLE_METER); } static void @@ -1041,6 +1046,12 @@ gtk_level_bar_init (GtkLevelBar *self) update_mode_style_classes (self); update_block_nodes (self); update_level_style_classes (self); + + gtk_accessible_update_property (GTK_ACCESSIBLE (self), + GTK_ACCESSIBLE_PROPERTY_VALUE_MAX, 1.0, + GTK_ACCESSIBLE_PROPERTY_VALUE_MIN, 0.0, + GTK_ACCESSIBLE_PROPERTY_VALUE_NOW, 0.0, + -1); } /** @@ -1163,6 +1174,12 @@ gtk_level_bar_set_min_value (GtkLevelBar *self, update_block_nodes (self); update_level_style_classes (self); + + gtk_accessible_update_property (GTK_ACCESSIBLE (self), + GTK_ACCESSIBLE_PROPERTY_VALUE_MIN, self->min_value, + GTK_ACCESSIBLE_PROPERTY_VALUE_NOW, self->cur_value, + -1); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MIN_VALUE]); } @@ -1194,6 +1211,12 @@ gtk_level_bar_set_max_value (GtkLevelBar *self, gtk_level_bar_ensure_offsets_in_range (self); update_block_nodes (self); update_level_style_classes (self); + + gtk_accessible_update_property (GTK_ACCESSIBLE (self), + GTK_ACCESSIBLE_PROPERTY_VALUE_MAX, self->max_value, + GTK_ACCESSIBLE_PROPERTY_VALUE_NOW, self->cur_value, + -1); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MAX_VALUE]); } @@ -1216,6 +1239,10 @@ gtk_level_bar_set_value (GtkLevelBar *self, gtk_level_bar_set_value_internal (self, value); update_level_style_classes (self); + + gtk_accessible_update_property (GTK_ACCESSIBLE (self), + GTK_ACCESSIBLE_PROPERTY_VALUE_NOW, self->cur_value, + -1); } /** diff --git a/gtk/gtklinkbutton.c b/gtk/gtklinkbutton.c index df25f264ae..521c196a04 100644 --- a/gtk/gtklinkbutton.c +++ b/gtk/gtklinkbutton.c @@ -47,6 +47,10 @@ * * GtkLinkButton has a single CSS node with name button. To differentiate * it from a plain #GtkButton, it gets the .link style class. + * + * # Accessibility + * + * GtkLinkButton uses the #GTK_ACCESSIBKE_ROLE_BUTTON role. */ #include "config.h" @@ -217,6 +221,7 @@ gtk_link_button_class_init (GtkLinkButtonClass *klass) G_TYPE_BOOLEAN, 0); gtk_widget_class_set_css_name (widget_class, I_("button")); + gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_BUTTON); /** * GtkLinkButton|clipboard.copy: diff --git a/gtk/gtktextview.c b/gtk/gtktextview.c index 8be1e73a86..f59b259277 100644 --- a/gtk/gtktextview.c +++ b/gtk/gtktextview.c @@ -89,9 +89,12 @@ * * If a context menu is opened, the window node will appear as a subnode * of the main node. + * + * # Accessibility + * + * GtkTextView uses the #GTK_ACCESSIBLE_ROLE_TEXT_BOX role. */ - /* How scrolling, validation, exposes, etc. work. * * The expose_event handler has the invariant that the onscreen lines @@ -1801,6 +1804,7 @@ gtk_text_view_class_init (GtkTextViewClass *klass) "(i)", GTK_DIR_TAB_BACKWARD); gtk_widget_class_set_css_name (widget_class, I_("textview")); + gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_TEXT_BOX); quark_text_selection_data = g_quark_from_static_string ("gtk-text-view-text-selection-data"); quark_gtk_signal = g_quark_from_static_string ("gtk-signal"); @@ -1942,6 +1946,10 @@ gtk_text_view_init (GtkTextView *text_view) gtk_widget_action_set_enabled (GTK_WIDGET (text_view), "text.can-redo", FALSE); gtk_widget_action_set_enabled (GTK_WIDGET (text_view), "text.can-undo", FALSE); + + gtk_accessible_update_property (GTK_ACCESSIBLE (widget), + GTK_ACCESSIBLE_PROPERTY_MULTI_LINE, TRUE, + -1); } GtkCssNode * @@ -3074,11 +3082,11 @@ gtk_text_view_set_editable (GtkTextView *text_view, if (priv->editable != setting) { if (!setting) - { - gtk_text_view_reset_im_context(text_view); - if (gtk_widget_has_focus (GTK_WIDGET (text_view))) - gtk_im_context_focus_out (priv->im_context); - } + { + gtk_text_view_reset_im_context (text_view); + if (gtk_widget_has_focus (GTK_WIDGET (text_view))) + gtk_im_context_focus_out (priv->im_context); + } priv->editable = setting; @@ -3090,12 +3098,16 @@ gtk_text_view_set_editable (GtkTextView *text_view, if (priv->layout && priv->layout->default_style) { - gtk_text_layout_set_overwrite_mode (priv->layout, - priv->overwrite_mode && priv->editable); + gtk_text_layout_set_overwrite_mode (priv->layout, + priv->overwrite_mode && priv->editable); priv->layout->default_style->editable = priv->editable; gtk_text_layout_default_style_changed (priv->layout); } + gtk_accessible_update_property (GTK_ACCESSIBLE (text_view), + GTK_ACCESSIBLE_PROPERTY_READ_ONLY, !setting, + -1); + g_object_notify (G_OBJECT (text_view), "editable"); } } diff --git a/testsuite/a11y/button.c b/testsuite/a11y/button.c index 9c5fdad777..f78207fb31 100644 --- a/testsuite/a11y/button.c +++ b/testsuite/a11y/button.c @@ -25,6 +25,28 @@ button_label (void) g_object_unref (button); } +static void +linkbutton_role (void) +{ + GtkWidget *button = gtk_link_button_new ("Hello"); + g_object_ref_sink (button); + + gtk_test_accessible_assert_role (button, GTK_ACCESSIBLE_ROLE_BUTTON); + + g_object_unref (button); +} + +static void +linkbutton_label (void) +{ + GtkWidget *button = gtk_link_button_new ("Hello"); + g_object_ref_sink (button); + + gtk_test_accessible_assert_property (button, GTK_ACCESSIBLE_PROPERTY_LABEL, "Hello"); + + g_object_unref (button); +} + int main (int argc, char *argv[]) { @@ -32,6 +54,8 @@ main (int argc, char *argv[]) g_test_add_func ("/a11y/button/role", button_role); g_test_add_func ("/a11y/button/label", button_label); + g_test_add_func ("/a11y/linkbutton/role", linkbutton_role); + g_test_add_func ("/a11y/linkbutton/label", linkbutton_label); return g_test_run (); } diff --git a/testsuite/a11y/expander.c b/testsuite/a11y/expander.c new file mode 100644 index 0000000000..169898f796 --- /dev/null +++ b/testsuite/a11y/expander.c @@ -0,0 +1,57 @@ +#include + +static void +expander_role (void) +{ + GtkWidget *widget = gtk_expander_new ("Hello"); + g_object_ref_sink (widget); + + gtk_test_accessible_assert_role (widget, GTK_ACCESSIBLE_ROLE_BUTTON); + + g_object_unref (widget); +} + +static void +expander_state (void) +{ + GtkWidget *widget = gtk_expander_new ("Hello"); + g_object_ref_sink (widget); + + gtk_test_accessible_assert_state (widget, GTK_ACCESSIBLE_STATE_EXPANDED, FALSE); + + gtk_expander_set_expanded (GTK_EXPANDER (widget), TRUE); + + gtk_test_accessible_assert_state (widget, GTK_ACCESSIBLE_STATE_EXPANDED, TRUE); + + g_object_unref (widget); +} + +static void +expander_relations (void) +{ + GtkWidget *widget = gtk_expander_new ("Hello"); + GtkWidget *child = gtk_label_new ("Child"); + GList *list; + + g_object_ref_sink (widget); + + gtk_expander_set_child (GTK_EXPANDER (widget), child); + + list = g_list_append (NULL, child); + gtk_test_accessible_assert_relation (widget, GTK_ACCESSIBLE_RELATION_CONTROLS, list); + g_list_free (list); + + g_object_unref (widget); +} + +int +main (int argc, char *argv[]) +{ + gtk_test_init (&argc, &argv, NULL); + + g_test_add_func ("/a11y/expander/role", expander_role); + g_test_add_func ("/a11y/expander/state", expander_state); + g_test_add_func ("/a11y/expander/relations", expander_relations); + + return g_test_run (); +} diff --git a/testsuite/a11y/levelbar.c b/testsuite/a11y/levelbar.c new file mode 100644 index 0000000000..de7e391f09 --- /dev/null +++ b/testsuite/a11y/levelbar.c @@ -0,0 +1,50 @@ +#include + +static void +level_bar_role (void) +{ + GtkWidget *widget = gtk_level_bar_new (); + g_object_ref_sink (widget); + + gtk_test_accessible_assert_role (widget, GTK_ACCESSIBLE_ROLE_METER); + + g_object_unref (widget); +} + +static void +level_bar_properties (void) +{ + GtkWidget *widget = gtk_level_bar_new (); + g_object_ref_sink (widget); + + gtk_test_accessible_assert_property (widget, GTK_ACCESSIBLE_PROPERTY_VALUE_MAX, 1.); + gtk_test_accessible_assert_property (widget, GTK_ACCESSIBLE_PROPERTY_VALUE_MIN, 0.); + gtk_test_accessible_assert_property (widget, GTK_ACCESSIBLE_PROPERTY_VALUE_NOW, 0.); + gtk_test_accessible_assert_property (widget, GTK_ACCESSIBLE_PROPERTY_VALUE_TEXT, NULL); + + gtk_level_bar_set_max_value (GTK_LEVEL_BAR (widget), 100.0); + gtk_level_bar_set_min_value (GTK_LEVEL_BAR (widget), 10.0); + + gtk_test_accessible_assert_property (widget, GTK_ACCESSIBLE_PROPERTY_VALUE_MAX, 100.0); + gtk_test_accessible_assert_property (widget, GTK_ACCESSIBLE_PROPERTY_VALUE_MIN, 10.0); + gtk_test_accessible_assert_property (widget, GTK_ACCESSIBLE_PROPERTY_VALUE_NOW, 10.0); + + gtk_level_bar_set_value (GTK_LEVEL_BAR (widget), 40.0); + + gtk_test_accessible_assert_property (widget, GTK_ACCESSIBLE_PROPERTY_VALUE_MAX, 100.0); + gtk_test_accessible_assert_property (widget, GTK_ACCESSIBLE_PROPERTY_VALUE_MIN, 10.0); + gtk_test_accessible_assert_property (widget, GTK_ACCESSIBLE_PROPERTY_VALUE_NOW, 40.0); + + g_object_unref (widget); +} + +int +main (int argc, char *argv[]) +{ + gtk_test_init (&argc, &argv, NULL); + + g_test_add_func ("/a11y/levelbar/role", level_bar_role); + g_test_add_func ("/a11y/levelbar/properties", level_bar_properties); + + return g_test_run (); +} diff --git a/testsuite/a11y/meson.build b/testsuite/a11y/meson.build index 2d828e36bd..bed847ec91 100644 --- a/testsuite/a11y/meson.build +++ b/testsuite/a11y/meson.build @@ -15,8 +15,10 @@ tests = [ { 'name': 'checkbutton' }, { 'name': 'dialog' }, { 'name': 'entry' }, + { 'name': 'expander' }, { 'name': 'image' }, { 'name': 'label' }, + { 'name': 'levelbar' }, { 'name': 'passwordentry' }, { 'name': 'progressbar' }, { 'name': 'scrollbar' }, @@ -24,6 +26,7 @@ tests = [ { 'name': 'separator' }, { 'name': 'spinbutton' }, { 'name': 'switch' }, + { 'name': 'textview' }, { 'name': 'window' }, ] diff --git a/testsuite/a11y/textview.c b/testsuite/a11y/textview.c new file mode 100644 index 0000000000..cdbccda8e4 --- /dev/null +++ b/testsuite/a11y/textview.c @@ -0,0 +1,39 @@ +#include + +static void +textview_role (void) +{ + GtkWidget *widget = gtk_text_view_new (); + g_object_ref_sink (widget); + + gtk_test_accessible_assert_role (widget, GTK_ACCESSIBLE_ROLE_TEXT_BOX); + + g_object_unref (widget); +} + +static void +textview_properties (void) +{ + GtkWidget *widget = gtk_text_view_new (); + g_object_ref_sink (widget); + + gtk_test_accessible_assert_property (widget, GTK_ACCESSIBLE_PROPERTY_MULTI_LINE, TRUE); + gtk_test_accessible_assert_property (widget, GTK_ACCESSIBLE_PROPERTY_READ_ONLY, FALSE); + + gtk_text_view_set_editable (GTK_TEXT_VIEW (widget), FALSE); + + gtk_test_accessible_assert_property (widget, GTK_ACCESSIBLE_PROPERTY_READ_ONLY, TRUE); + + g_object_unref (widget); +} + +int +main (int argc, char *argv[]) +{ + gtk_test_init (&argc, &argv, NULL); + + g_test_add_func ("/a11y/textview/role", textview_role); + g_test_add_func ("/a11y/textview/properties", textview_properties); + + return g_test_run (); +}