/* gtkaccessible.c: Accessible interface
 *
 * 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/>.
 */

/**
 * SECTION:gtkaccessible
 * @Title: GtkAccessible
 * @Short_description: Accessible interface
 *
 * GtkAccessible provides an interface for describing a UI element, like a
 * #GtkWidget, in a way that can be consumed by Assistive Technologies, or
 * “AT”. Every accessible implementation has:
 *
 *  - a “role”, represented by a value of the #GtkAccessibleRole enumeration
 *  - an “attribute”, represented by a set of #GtkAccessibleState,
 *    #GtkAccessibleProperty and #GtkAccessibleRelation values
 *
 * The role cannot be changed after instantiating a #GtkAccessible
 * implementation.
 *
 * The attributes are updated every time a UI element's state changes in a way that
 * should be reflected by assistive technologies. For instance, if a #GtkWidget
 * visibility changes, the %GTK_ACCESSIBLE_STATE_HIDDEN state will also change
 * to reflect the #GtkWidget:visible property.
 */

#include "config.h"

#include "gtkaccessibleprivate.h"

#include "gtkatcontextprivate.h"
#include "gtkenums.h"
#include "gtktypebuiltins.h"

#include <stdarg.h>

G_DEFINE_INTERFACE (GtkAccessible, gtk_accessible, G_TYPE_OBJECT)

static void
gtk_accessible_default_init (GtkAccessibleInterface *iface)
{
  /**
   * GtkAccessible:accessible-role:
   *
   * The accessible role of the given #GtkAccessible implementation.
   *
   * The accessible role cannot be changed once set.
   */
  GParamSpec *pspec =
    g_param_spec_enum ("accessible-role",
                       "Accessible Role",
                       "The role of the accessible object",
                       GTK_TYPE_ACCESSIBLE_ROLE,
                       GTK_ACCESSIBLE_ROLE_NONE,
                       G_PARAM_READWRITE |
                       G_PARAM_STATIC_STRINGS);

  g_object_interface_install_property (iface, pspec);
}

/*< private >
 * gtk_accessible_get_at_context:
 * @self: a #GtkAccessible
 *
 * Retrieves the #GtkATContext for the given #GtkAccessible.
 *
 * Returns: (transfer none): the #GtkATContext
 */
GtkATContext *
gtk_accessible_get_at_context (GtkAccessible *self)
{
  g_return_val_if_fail (GTK_IS_ACCESSIBLE (self), NULL);

  return GTK_ACCESSIBLE_GET_IFACE (self)->get_at_context (self);
}

/**
 * gtk_accessible_get_accessible_role:
 * @self: a #GtkAccessible
 *
 * Retrieves the #GtkAccessibleRole for the given #GtkAccessible.
 *
 * Returns: a #GtkAccessibleRole
 */
GtkAccessibleRole
gtk_accessible_get_accessible_role (GtkAccessible *self)
{
  g_return_val_if_fail (GTK_IS_ACCESSIBLE (self), GTK_ACCESSIBLE_ROLE_NONE);

  GtkATContext *context = gtk_accessible_get_at_context (self);
  if (context == NULL)
    return GTK_ACCESSIBLE_ROLE_NONE;

  return gtk_at_context_get_accessible_role (context);
}

/**
 * gtk_accessible_update_state:
 * @self: a #GtkAccessible
 * @first_state: the first #GtkAccessibleState
 * @...: a list of state and value pairs, terminated by -1
 *
 * Updates a list of accessible states. See the #GtkAccessibleState
 * documentation for the value types of accessible states.
 *
 * This function should be called by #GtkWidget types whenever an accessible
 * state change must be communicated to assistive technologies.
 *
 * Example:
 * |[
 *   value = GTK_ACCESSIBLE_TRISTATE_MIXED;
 *   gtk_accessible_update_state (GTK_ACCESSIBLE (check_button),
 *                                GTK_ACCESSIBLE_STATE_CHECKED, value,
 *                                -1);
 * ]|
 */
void
gtk_accessible_update_state (GtkAccessible      *self,
                             GtkAccessibleState  first_state,
                             ...)
{
  GtkAccessibleState state;
  GtkATContext *context;
  va_list args;

  g_return_if_fail (GTK_IS_ACCESSIBLE (self));

  context = gtk_accessible_get_at_context (self);
  if (context == NULL)
    return;

  va_start (args, first_state);

  state = first_state;

  while (state != -1)
    {
      GError *error = NULL;
      GtkAccessibleValue *value =
        gtk_accessible_value_collect_for_state (state, &error, &args);

      if (error != NULL)
        {
          g_critical ("Unable to collect value for state “%s”: %s",
                      gtk_accessible_state_get_attribute_name (state),
                      error->message);
          g_error_free (error);
          goto out;
        }

      gtk_at_context_set_accessible_state (context, state, value);

      if (value != NULL)
        gtk_accessible_value_unref (value);

      state = va_arg (args, int);
    }

  gtk_at_context_update (context);

out:
  va_end (args);
}

/**
 * gtk_accessible_update_state_value: (rename-to gtk_accessible_update_state)
 * @self: a #GtkAccessible
 * @n_states: the number of accessible states to set
 * @states: (array length=n_states): an array of #GtkAccessibleState
 * @values: (array length=n_states): an array of #GValues, one for each state
 *
 * Updates an array of accessible states.
 *
 * This function should be called by #GtkWidget types whenever an accessible
 * state change must be communicated to assistive technologies.
 *
 * This function is meant to be used by language bindings.
 */
void
gtk_accessible_update_state_value (GtkAccessible      *self,
                                   int                 n_states,
                                   GtkAccessibleState  states[],
                                   const GValue        values[])
{
  g_return_if_fail (GTK_IS_ACCESSIBLE (self));
  g_return_if_fail (n_states > 0);

  GtkATContext *context = gtk_accessible_get_at_context (self);
  if (context == NULL)
    return;

  for (int i = 0; i < n_states; i++)
    {
      GtkAccessibleState state = states[i];
      const GValue *value = &(values[i]);
      GError *error = NULL;
      GtkAccessibleValue *real_value =
        gtk_accessible_value_collect_for_state_value (state, value, &error);

      if (error != NULL)
        {
          g_critical ("Unable to collect the value for state “%s”: %s",
                      gtk_accessible_state_get_attribute_name (state),
                      error->message);
          g_error_free (error);
          break;
        }

      gtk_at_context_set_accessible_state (context, state, real_value);

      if (real_value != NULL)
        gtk_accessible_value_unref (real_value);
    }

  gtk_at_context_update (context);
}

/**
 * gtk_accessible_reset_state:
 * @self: a #GtkAccessible
 * @state: a #GtkAccessibleState
 *
 * Resets the accessible @state to its default value.
 */
void
gtk_accessible_reset_state (GtkAccessible      *self,
                            GtkAccessibleState  state)
{
  GtkATContext *context;

  g_return_if_fail (GTK_IS_ACCESSIBLE (self));

  context = gtk_accessible_get_at_context (self);
  if (context == NULL)
    return;

  gtk_at_context_set_accessible_state (context, state, NULL);
  gtk_at_context_update (context);
}

/**
 * gtk_accessible_update_property:
 * @self: a #GtkAccessible
 * @first_property: the first #GtkAccessibleProperty
 * @...: a list of property and value pairs, terminated by -1
 *
 * Updates a list of accessible properties. See the #GtkAccessibleProperty
 * documentation for the value types of accessible properties.
 *
 * This function should be called by #GtkWidget types whenever an accessible
 * property change must be communicated to assistive technologies.
 *
 * Example:
 * |[
 *   value = gtk_adjustment_get_value (adjustment);
 *   gtk_accessible_update_property (GTK_ACCESSIBLE (spin_button),
                                     GTK_ACCESSIBLE_PROPERTY_VALUE_NOW, value,
                                     -1);
 * ]|
 */
void
gtk_accessible_update_property (GtkAccessible         *self,
                                GtkAccessibleProperty  first_property,
                                ...)
{
  GtkAccessibleProperty property;
  GtkATContext *context;
  va_list args;

  g_return_if_fail (GTK_IS_ACCESSIBLE (self));

  context = gtk_accessible_get_at_context (self);
  if (context == NULL)
    return;

  va_start (args, first_property);

  property = first_property;

  while (property != -1)
    {
      GError *error = NULL;
      GtkAccessibleValue *value =
        gtk_accessible_value_collect_for_property (property, &error, &args);

      if (error != NULL)
        {
          g_critical ("Unable to collect the value for property “%s”: %s",
                      gtk_accessible_property_get_attribute_name (property),
                      error->message);
          g_error_free (error);
          goto out;
        }

      gtk_at_context_set_accessible_property (context, property, value);

      if (value != NULL)
        gtk_accessible_value_unref (value);

      property = va_arg (args, int);
    }

  gtk_at_context_update (context);

out:
  va_end (args);
}

/**
 * gtk_accessible_update_property_value: (rename-to gtk_accessible_update_property)
 * @self: a #GtkAccessible
 * @n_properties: the number of accessible properties to set
 * @properties: (array length=n_properties): an array of #GtkAccessibleProperty
 * @values: (array length=n_properties): an array of #GValues, one for each property
 *
 * Updates an array of accessible properties.
 *
 * This function should be called by #GtkWidget types whenever an accessible
 * property change must be communicated to assistive technologies.
 *
 * This function is meant to be used by language bindings.
 */
void
gtk_accessible_update_property_value (GtkAccessible         *self,
                                      int                    n_properties,
                                      GtkAccessibleProperty  properties[],
                                      const GValue           values[])
{
  g_return_if_fail (GTK_IS_ACCESSIBLE (self));
  g_return_if_fail (n_properties > 0);

  GtkATContext *context = gtk_accessible_get_at_context (self);
  if (context == NULL)
    return;

  for (int i = 0; i < n_properties; i++)
    {
      GtkAccessibleProperty property = properties[i];
      const GValue *value = &(values[i]);
      GError *error = NULL;
      GtkAccessibleValue *real_value =
        gtk_accessible_value_collect_for_property_value (property, value, &error);

      if (error != NULL)
        {
          g_critical ("Unable to collect the value for property “%s”: %s",
                      gtk_accessible_property_get_attribute_name (property),
                      error->message);
          g_error_free (error);
          break;
        }

      gtk_at_context_set_accessible_property (context, property, real_value);

      if (real_value != NULL)
        gtk_accessible_value_unref (real_value);
    }

  gtk_at_context_update (context);
}

/**
 * gtk_accessible_reset_property:
 * @self: a #GtkAccessible
 * @property: a #GtkAccessibleProperty
 *
 * Resets the accessible @property to its default value.
 */
void
gtk_accessible_reset_property (GtkAccessible         *self,
                               GtkAccessibleProperty  property)
{
  GtkATContext *context;

  g_return_if_fail (GTK_IS_ACCESSIBLE (self));

  context = gtk_accessible_get_at_context (self);
  if (context == NULL)
    return;

  gtk_at_context_set_accessible_property (context, property, NULL);
  gtk_at_context_update (context);
}

/**
 * gtk_accessible_update_relation:
 * @self: a #GtkAccessible
 * @first_relation: the first #GtkAccessibleRelation
 * @...: a list of relation and value pairs, terminated by -1
 *
 * Updates a list of accessible relations.
 *
 * This function should be called by #GtkWidget types whenever an accessible
 * relation change must be communicated to assistive technologies.
 */
void
gtk_accessible_update_relation (GtkAccessible         *self,
                                GtkAccessibleRelation  first_relation,
                                ...)
{
  GtkAccessibleRelation relation;
  GtkATContext *context;
  va_list args;

  g_return_if_fail (GTK_IS_ACCESSIBLE (self));

  context = gtk_accessible_get_at_context (self);
  if (context == NULL)
    return;

  va_start (args, first_relation);

  relation = first_relation;

  while (relation != -1)
    {
      GError *error = NULL;
      GtkAccessibleValue *value =
        gtk_accessible_value_collect_for_relation (relation, &error, &args);

      if (error != NULL)
        {
          g_critical ("Unable to collect the value for relation “%s”: %s",
                      gtk_accessible_relation_get_attribute_name (relation),
                      error->message);
          g_error_free (error);
          goto out;
        }

      gtk_at_context_set_accessible_relation (context, relation, value);

      if (value != NULL)
        gtk_accessible_value_unref (value);

      relation = va_arg (args, int);
    }

  gtk_at_context_update (context);

out:
  va_end (args);
}

/**
 * gtk_accessible_update_relation_value: (rename-to gtk_accessible_update_relation)
 * @self: a #GtkAccessible
 * @n_relations: the number of accessible relations to set
 * @relations: (array length=n_relations): an array of #GtkAccessibleRelation
 * @values: (array length=n_relations): an array of #GValues, one for each relation
 *
 * Updates an array of accessible relations.
 *
 * This function should be called by #GtkWidget types whenever an accessible
 * relation change must be communicated to assistive technologies.
 *
 * This function is meant to be used by language bindings.
 */
void
gtk_accessible_update_relation_value (GtkAccessible         *self,
                                      int                    n_relations,
                                      GtkAccessibleRelation  relations[],
                                      const GValue           values[])
{
  g_return_if_fail (GTK_IS_ACCESSIBLE (self));
  g_return_if_fail (n_relations > 0);

  GtkATContext *context = gtk_accessible_get_at_context (self);
  if (context == NULL)
    return;

  for (int i = 0; i < n_relations; i++)
    {
      GtkAccessibleRelation relation = relations[i];
      const GValue *value = &(values[i]);
      GError *error = NULL;
      GtkAccessibleValue *real_value =
        gtk_accessible_value_collect_for_relation_value (relation, value, &error);

      if (error != NULL)
        {
          g_critical ("Unable to collect the value for relation “%s”: %s",
                      gtk_accessible_relation_get_attribute_name (relation),
                      error->message);
          g_error_free (error);
          break;
        }

      gtk_at_context_set_accessible_relation (context, relation, real_value);

      if (real_value != NULL)
        gtk_accessible_value_unref (real_value);
    }

  gtk_at_context_update (context);
}

/**
 * gtk_accessible_reset_relation:
 * @self: a #GtkAccessible
 * @relation: a #GtkAccessibleRelation
 *
 * Resets the accessible @relation to its default value.
 */
void
gtk_accessible_reset_relation (GtkAccessible         *self,
                               GtkAccessibleRelation  relation)
{
  GtkATContext *context;

  g_return_if_fail (GTK_IS_ACCESSIBLE (self));

  context = gtk_accessible_get_at_context (self);
  if (context == NULL)
    return;

  gtk_at_context_set_accessible_relation (context, relation, NULL);
  gtk_at_context_update (context);
}
