/* GAIL - The GNOME Accessibility Implementation Library
 * Copyright 2001, 2002, 2003 Sun Microsystems 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, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

#include "config.h"

#include <string.h>
#include <gtk/gtk.h>
#include "gtkbuttonaccessible.h"


static void atk_action_interface_init (AtkActionIface *iface);
static void atk_image_interface_init  (AtkImageIface  *iface);

G_DEFINE_TYPE_WITH_CODE (GtkButtonAccessible, _gtk_button_accessible, GTK_TYPE_CONTAINER_ACCESSIBLE,
                         G_IMPLEMENT_INTERFACE (ATK_TYPE_ACTION, atk_action_interface_init)
                         G_IMPLEMENT_INTERFACE (ATK_TYPE_IMAGE, atk_image_interface_init))

static void
state_changed_cb (GtkWidget *widget, GtkStateFlags previous_flags)
{
  AtkObject *accessible;
  GtkStateFlags flags;
  gboolean was_active;
  gboolean active;

  flags = gtk_widget_get_state_flags (widget);

  was_active = (previous_flags & GTK_STATE_FLAG_ACTIVE) != 0;
  active = (flags & GTK_STATE_FLAG_ACTIVE) != 0;

  accessible = gtk_widget_get_accessible (widget);
  if (active && !was_active)
    atk_object_notify_state_change (accessible, ATK_STATE_ARMED, TRUE);
  else if (!active && was_active)
    atk_object_notify_state_change (accessible, ATK_STATE_ARMED, FALSE);
}

static void
gtk_button_accessible_initialize (AtkObject *obj,
                                  gpointer   data)
{
  GtkWidget *parent;

  ATK_OBJECT_CLASS (_gtk_button_accessible_parent_class)->initialize (obj, data);

  g_signal_connect (data, "state-flags-changed", G_CALLBACK (state_changed_cb), NULL);

  parent = gtk_widget_get_parent (gtk_accessible_get_widget (GTK_ACCESSIBLE (obj)));
  if (GTK_IS_TREE_VIEW (parent))
    {
      /* Even though the accessible parent of the column header will
       * be reported as the table because the parent widget of the
       * GtkTreeViewColumn's button is the GtkTreeView we set
       * the accessible parent for column header to be the table
       * to ensure that atk_object_get_index_in_parent() returns
       * the correct value; see gail_widget_get_index_in_parent().
       */
      atk_object_set_parent (obj, gtk_widget_get_accessible (parent));
      obj->role = ATK_ROLE_TABLE_COLUMN_HEADER;
    }
  else
    obj->role = ATK_ROLE_PUSH_BUTTON;
}

static GtkWidget *
get_image_from_button (GtkWidget *button)
{
  GtkWidget *image;

  image = gtk_button_get_image (GTK_BUTTON (button));
  if (GTK_IS_IMAGE (image))
    return image;

  return NULL;
}

static GtkWidget *
find_label_child (GtkContainer *container)
{
  GList *children, *tmp_list;
  GtkWidget *child;

  children = gtk_container_get_children (container);

  child = NULL;
  for (tmp_list = children; tmp_list != NULL; tmp_list = tmp_list->next)
    {
      if (GTK_IS_LABEL (tmp_list->data))
        {
          child = GTK_WIDGET (tmp_list->data);
          break;
        }
      else if (GTK_IS_CONTAINER (tmp_list->data))
        {
          child = find_label_child (GTK_CONTAINER (tmp_list->data));
          if (child)
            break;
        }
    }
  g_list_free (children);
  return child;
}

static GtkWidget *
get_label_from_button (GtkWidget *button)
{
  GtkWidget *child;

  child = gtk_bin_get_child (GTK_BIN (button));
  if (GTK_IS_ALIGNMENT (child))
    child = gtk_bin_get_child (GTK_BIN (child));

  if (GTK_IS_CONTAINER (child))
    child = find_label_child (GTK_CONTAINER (child));
  else if (!GTK_IS_LABEL (child))
    child = NULL;

  return child;
}

static const gchar *
gtk_button_accessible_get_name (AtkObject *obj)
{
  const gchar *name = NULL;
  GtkWidget *widget;
  GtkWidget *child;

  widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (obj));
  if (widget == NULL)
    return NULL;

  name = ATK_OBJECT_CLASS (_gtk_button_accessible_parent_class)->get_name (obj);
  if (name != NULL)
    return name;

  child = get_label_from_button (widget);
  if (GTK_IS_LABEL (child))
    name = gtk_label_get_text (GTK_LABEL (child));
  else
    {
      GtkWidget *image;

      image = get_image_from_button (widget);
      if (GTK_IS_IMAGE (image))
        {
          AtkObject *atk_obj;

          atk_obj = gtk_widget_get_accessible (image);
          name = atk_object_get_name (atk_obj);
        }
    }

  return name;
}

static gint
gtk_button_accessible_get_n_children (AtkObject* obj)
{
  return 0;
}

static AtkObject *
gtk_button_accessible_ref_child (AtkObject *obj,
                                 gint       i)
{
  return NULL;
}

static AtkStateSet *
gtk_button_accessible_ref_state_set (AtkObject *obj)
{
  AtkStateSet *state_set;
  GtkWidget *widget;

  widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (obj));
  if (widget == NULL)
    return NULL;

  state_set = ATK_OBJECT_CLASS (_gtk_button_accessible_parent_class)->ref_state_set (obj);

  if ((gtk_widget_get_state_flags (widget) & GTK_STATE_FLAG_ACTIVE) != 0)
    atk_state_set_add_state (state_set, ATK_STATE_ARMED);

  if (!gtk_widget_get_can_focus (widget))
    atk_state_set_remove_state (state_set, ATK_STATE_SELECTABLE);

  return state_set;
}

static void
gtk_button_accessible_notify_gtk (GObject    *obj,
                                  GParamSpec *pspec)
{
  GtkWidget *widget = GTK_WIDGET (obj);
  AtkObject *atk_obj = gtk_widget_get_accessible (widget);

  if (strcmp (pspec->name, "label") == 0)
    {
      if (atk_obj->name == NULL)
        g_object_notify (G_OBJECT (atk_obj), "accessible-name");

      g_signal_emit_by_name (atk_obj, "visible-data-changed");
    }
  else
    GTK_WIDGET_ACCESSIBLE_CLASS (_gtk_button_accessible_parent_class)->notify_gtk (obj, pspec);
}

static void
_gtk_button_accessible_class_init (GtkButtonAccessibleClass *klass)
{
  AtkObjectClass *class = ATK_OBJECT_CLASS (klass);
  GtkContainerAccessibleClass *container_class = (GtkContainerAccessibleClass*)klass;
  GtkWidgetAccessibleClass *widget_class = (GtkWidgetAccessibleClass*)klass;

  class->get_name = gtk_button_accessible_get_name;
  class->get_n_children = gtk_button_accessible_get_n_children;
  class->ref_child = gtk_button_accessible_ref_child;
  class->ref_state_set = gtk_button_accessible_ref_state_set;
  class->initialize = gtk_button_accessible_initialize;

  widget_class->notify_gtk = gtk_button_accessible_notify_gtk;

  container_class->add_gtk = NULL;
  container_class->remove_gtk = NULL;
}

static void
_gtk_button_accessible_init (GtkButtonAccessible *button)
{
}

static gboolean
gtk_button_accessible_do_action (AtkAction *action,
                                 gint       i)
{
  GtkWidget *widget;

  widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (action));
  if (widget == NULL)
    return FALSE;

  if (i != 0)
    return FALSE;

  if (!gtk_widget_is_sensitive (widget) || !gtk_widget_get_visible (widget))
    return FALSE;

  gtk_button_clicked (GTK_BUTTON (widget));
  return TRUE;
}

static gint
gtk_button_accessible_get_n_actions (AtkAction *action)
{
  return 1;
}

static const gchar *
gtk_button_accessible_get_keybinding (AtkAction *action,
                                      gint       i)
{
  gchar *return_value = NULL;
  GtkWidget *widget;
  GtkWidget *label;
  guint key_val;

  widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (action));
  if (widget == NULL)
    return NULL;

  if (i != 0)
    return NULL;

  label = get_label_from_button (widget);
  if (GTK_IS_LABEL (label))
    {
      key_val = gtk_label_get_mnemonic_keyval (GTK_LABEL (label));
      if (key_val != GDK_KEY_VoidSymbol)
        return_value = gtk_accelerator_name (key_val, GDK_MOD1_MASK);
    }
  if (return_value == NULL)
    {
      /* Find labelled-by relation */
      AtkRelationSet *set;
      AtkRelation *relation;
      GPtrArray *target;
      gpointer target_object;

      set = atk_object_ref_relation_set (ATK_OBJECT (action));
      if (set)
        {
          relation = atk_relation_set_get_relation_by_type (set, ATK_RELATION_LABELLED_BY);
          if (relation)
            {
              target = atk_relation_get_target (relation);
              target_object = g_ptr_array_index (target, 0);
              label = gtk_accessible_get_widget (GTK_ACCESSIBLE (target_object));
            }
          g_object_unref (set);
        }

      if (GTK_IS_LABEL (label))
        {
          key_val = gtk_label_get_mnemonic_keyval (GTK_LABEL (label));
          if (key_val != GDK_KEY_VoidSymbol)
            return_value = gtk_accelerator_name (key_val, GDK_MOD1_MASK);
        }
    }
  return return_value;
}

static const gchar *
gtk_button_accessible_action_get_name (AtkAction *action,
                                       gint       i)
{
  if (i != 0)
    return NULL;

  return "click";
}

static void
atk_action_interface_init (AtkActionIface *iface)
{
  iface->do_action = gtk_button_accessible_do_action;
  iface->get_n_actions = gtk_button_accessible_get_n_actions;
  iface->get_keybinding = gtk_button_accessible_get_keybinding;
  iface->get_name = gtk_button_accessible_action_get_name;
}

static const gchar *
gtk_button_accessible_get_image_description (AtkImage *image)
{
  GtkWidget *widget;
  GtkWidget  *button_image;
  AtkObject *obj;

  widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (image));
  if (widget == NULL)
    return NULL;

  button_image = get_image_from_button (widget);
  if (GTK_IS_IMAGE (button_image))
    {
      obj = gtk_widget_get_accessible (button_image);
      return atk_image_get_image_description (ATK_IMAGE (obj));
    }

  return NULL;
}

static void
gtk_button_accessible_get_image_position (AtkImage     *image,
                                          gint         *x,
                                          gint         *y,
                                          AtkCoordType  coord_type)
{
  GtkWidget *widget;
  GtkWidget *button_image;
  AtkObject *obj;

  widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (image));
  if (widget == NULL)
    {
      *x = G_MININT;
      *y = G_MININT;
      return;
    }

  button_image = get_image_from_button (widget);
  if (button_image != NULL)
    {
      obj = gtk_widget_get_accessible (button_image);
      atk_component_get_position (ATK_COMPONENT (obj), x, y, coord_type);
    }
  else
    {
      *x = G_MININT;
      *y = G_MININT;
    }
}

static void
gtk_button_accessible_get_image_size (AtkImage *image,
                                      gint     *width,
                                      gint     *height)
{
  GtkWidget *widget;
  GtkWidget *button_image;
  AtkObject *obj;

  widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (image));
  if (widget == NULL)
    {
      *width = -1;
      *height = -1;
      return;
    }

  button_image = get_image_from_button (widget);
  if (GTK_IS_IMAGE (button_image))
    {
      obj = gtk_widget_get_accessible (GTK_WIDGET (button_image));
      atk_image_get_image_size (ATK_IMAGE (obj), width, height);
    }
  else
    {
      *width = -1;
      *height = -1;
    }
}

static gboolean
gtk_button_accessible_set_image_description (AtkImage    *image,
                                             const gchar *description)
{
  GtkWidget *widget;
  GtkWidget *button_image;
  AtkObject *obj;

  widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (image));

  if (widget == NULL)
    return FALSE;

  button_image = get_image_from_button (widget);
  if (GTK_IMAGE (button_image))
    {
      obj = gtk_widget_get_accessible (GTK_WIDGET (button_image));
      return atk_image_set_image_description (ATK_IMAGE (obj), description);
    }

  return FALSE;
}

static void
atk_image_interface_init (AtkImageIface *iface)
{
  iface->get_image_description = gtk_button_accessible_get_image_description;
  iface->get_image_position = gtk_button_accessible_get_image_position;
  iface->get_image_size = gtk_button_accessible_get_image_size;
  iface->set_image_description = gtk_button_accessible_set_image_description;
}
