/* 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/gtkx.h>

#include "gailwindow.h"
#include "gailtoplevel.h"
#include "gail-private-macros.h"

enum {
  ACTIVATE,
  CREATE,
  DEACTIVATE,
  DESTROY,
  MAXIMIZE,
  MINIMIZE,
  MOVE,
  RESIZE,
  RESTORE,
  LAST_SIGNAL
};

static void gail_window_class_init (GailWindowClass *klass);

static void                  gail_window_init            (GailWindow   *accessible);

static void                  gail_window_real_initialize (AtkObject    *obj,
                                                          gpointer     data);
static void                  gail_window_finalize        (GObject      *object);

static const gchar* gail_window_get_name       (AtkObject     *accessible);

static AtkObject*            gail_window_get_parent     (AtkObject     *accessible);
static gint                  gail_window_get_index_in_parent (AtkObject *accessible);
static gboolean              gail_window_real_focus_gtk (GtkWidget     *widget,
                                                         GdkEventFocus *event);

static AtkStateSet*          gail_window_ref_state_set  (AtkObject     *accessible);
static AtkRelationSet*       gail_window_ref_relation_set  (AtkObject     *accessible);
static void                  gail_window_real_notify_gtk (GObject      *obj,
                                                          GParamSpec   *pspec);
static gint                  gail_window_get_mdi_zorder (AtkComponent  *component);

static gboolean              gail_window_state_event_gtk (GtkWidget           *widget,
                                                          GdkEventWindowState *event);

/* atkcomponent.h */
static void                  atk_component_interface_init (AtkComponentIface    *iface);

static void                  gail_window_get_extents      (AtkComponent         *component,
                                                           gint                 *x,
                                                           gint                 *y,
                                                           gint                 *width,
                                                           gint                 *height,
                                                           AtkCoordType         coord_type);
static void                  gail_window_get_size         (AtkComponent         *component,
                                                           gint                 *width,
                                                           gint                 *height);

static guint gail_window_signals [LAST_SIGNAL] = { 0, };

G_DEFINE_TYPE_WITH_CODE (GailWindow, gail_window, GAIL_TYPE_CONTAINER,
                         G_IMPLEMENT_INTERFACE (ATK_TYPE_COMPONENT, atk_component_interface_init))

static void
gail_window_class_init (GailWindowClass *klass)
{
  GailWidgetClass *widget_class;
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
  AtkObjectClass  *class = ATK_OBJECT_CLASS (klass);

  gobject_class->finalize = gail_window_finalize;

  widget_class = (GailWidgetClass*)klass;
  widget_class->focus_gtk = gail_window_real_focus_gtk;
  widget_class->notify_gtk = gail_window_real_notify_gtk;

  class->get_name = gail_window_get_name;
  class->get_parent = gail_window_get_parent;
  class->get_index_in_parent = gail_window_get_index_in_parent;
  class->ref_relation_set = gail_window_ref_relation_set;
  class->ref_state_set = gail_window_ref_state_set;
  class->initialize = gail_window_real_initialize;

  gail_window_signals [ACTIVATE] =
    g_signal_new ("activate",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_LAST,
                  0, /* default signal handler */
                  NULL, NULL,
                  g_cclosure_marshal_VOID__VOID,
                  G_TYPE_NONE, 0);
  gail_window_signals [CREATE] =
    g_signal_new ("create",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_LAST,
                  0, /* default signal handler */
                  NULL, NULL,
                  g_cclosure_marshal_VOID__VOID,
                  G_TYPE_NONE, 0);
  gail_window_signals [DEACTIVATE] =
    g_signal_new ("deactivate",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_LAST,
                  0, /* default signal handler */
                  NULL, NULL,
                  g_cclosure_marshal_VOID__VOID,
                  G_TYPE_NONE, 0);
  gail_window_signals [DESTROY] =
    g_signal_new ("destroy",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_LAST,
                  0, /* default signal handler */
                  NULL, NULL,
                  g_cclosure_marshal_VOID__VOID,
                  G_TYPE_NONE, 0);
  gail_window_signals [MAXIMIZE] =
    g_signal_new ("maximize",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_LAST,
                  0, /* default signal handler */
                  NULL, NULL,
                  g_cclosure_marshal_VOID__VOID,
                  G_TYPE_NONE, 0);
  gail_window_signals [MINIMIZE] =
    g_signal_new ("minimize",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_LAST,
                  0, /* default signal handler */
                  NULL, NULL,
                  g_cclosure_marshal_VOID__VOID,
                  G_TYPE_NONE, 0);
  gail_window_signals [MOVE] =
    g_signal_new ("move",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_LAST,
                  0, /* default signal handler */
                  NULL, NULL,
                  g_cclosure_marshal_VOID__VOID,
                  G_TYPE_NONE, 0);
  gail_window_signals [RESIZE] =
    g_signal_new ("resize",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_LAST,
                  0, /* default signal handler */
                  NULL, NULL,
                  g_cclosure_marshal_VOID__VOID,
                  G_TYPE_NONE, 0);
  gail_window_signals [RESTORE] =
    g_signal_new ("restore",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_LAST,
                  0, /* default signal handler */
                  NULL, NULL,
                  g_cclosure_marshal_VOID__VOID,
                  G_TYPE_NONE, 0);
}

static void
gail_window_init (GailWindow   *accessible)
{
}

static void
gail_window_real_initialize (AtkObject *obj,
                             gpointer  data)
{
  GtkWidget *widget = GTK_WIDGET (data);
  GailWindow *window;

  /*
   * A GailWindow can be created for a GtkHandleBox or a GtkWindow
   */
  if (!GTK_IS_WINDOW (widget) &&
      !GTK_IS_HANDLE_BOX (widget))
    gail_return_if_fail (FALSE);

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

  window = GAIL_WINDOW (obj);
  window->name_change_handler = 0;
  window->previous_name = g_strdup (gtk_window_get_title (GTK_WINDOW (data)));

  g_signal_connect (data,
                    "window_state_event",
                    G_CALLBACK (gail_window_state_event_gtk),
                    NULL);
  g_object_set_data (G_OBJECT (obj), "atk-component-layer",
                     GINT_TO_POINTER (ATK_LAYER_WINDOW));

  if (GTK_IS_FILE_CHOOSER_DIALOG (widget))
    obj->role = ATK_ROLE_FILE_CHOOSER;
  else if (GTK_IS_COLOR_SELECTION_DIALOG (widget))
    obj->role = ATK_ROLE_COLOR_CHOOSER;
  else if (GTK_IS_FONT_SELECTION_DIALOG (widget))
    obj->role = ATK_ROLE_FONT_CHOOSER;
  else if (GTK_IS_MESSAGE_DIALOG (widget))
    obj->role = ATK_ROLE_ALERT;
  else if (GTK_IS_DIALOG (widget))
    obj->role = ATK_ROLE_DIALOG;
  else
    {
      const gchar *name;

      name = gtk_widget_get_name (widget);

      if (!g_strcmp0 (name, "gtk-tooltip"))
        obj->role = ATK_ROLE_TOOL_TIP;
#ifdef  GDK_WINDOWING_X11
      else if (GTK_IS_PLUG (widget))
        obj->role = ATK_ROLE_PANEL;
#endif
      else if (gtk_window_get_window_type (GTK_WINDOW (widget)) == GTK_WINDOW_POPUP)
        obj->role = ATK_ROLE_WINDOW;
      else
        obj->role = ATK_ROLE_FRAME;
    }

  /*
   * Notify that tooltip is showing
   */
  if (obj->role == ATK_ROLE_TOOL_TIP &&
      gtk_widget_get_mapped (widget))
    atk_object_notify_state_change (obj, ATK_STATE_SHOWING, 1);
}

static void
gail_window_finalize (GObject *object)
{
  GailWindow* window = GAIL_WINDOW (object);

  if (window->name_change_handler)
    {
      g_source_remove (window->name_change_handler);
      window->name_change_handler = 0;
    }
  if (window->previous_name)
    {
      g_free (window->previous_name);
      window->previous_name = NULL;
    }

  G_OBJECT_CLASS (gail_window_parent_class)->finalize (object);
}

static const gchar*
gail_window_get_name (AtkObject *accessible)
{
  const gchar* name;

  name = ATK_OBJECT_CLASS (gail_window_parent_class)->get_name (accessible);
  if (name == NULL)
    {
      /*
       * Get the window title if it exists
       */
      GtkWidget* widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));

      if (widget == NULL)
        /*
         * State is defunct
         */
        return NULL;

      gail_return_val_if_fail (GTK_IS_WIDGET (widget), NULL);

      if (GTK_IS_WINDOW (widget))
        {
          GtkWindow *window = GTK_WINDOW (widget);
 
          name = gtk_window_get_title (window);
          if (name == NULL &&
              accessible->role == ATK_ROLE_TOOL_TIP)
            {
              GtkWidget *child;

              child = gtk_bin_get_child (GTK_BIN (window));
              /* could be some kind of egg notification bubble thingy? */

              /* Handle new GTK+ GNOME 2.20 tooltips */
              if (GTK_IS_ALIGNMENT(child))
                {
                  child = gtk_bin_get_child (GTK_BIN (child));
                  if (GTK_IS_BOX(child)) 
                    {
                      GList *children;
                      guint count;
                      children = gtk_container_get_children (GTK_CONTAINER (child));
                      count = g_list_length (children);
                      if (count == 2) 
                        {
                          child = (GtkWidget *) g_list_nth_data (children, 1);
                        }
                      g_list_free (children);                
                    }
                }

              if (!GTK_IS_LABEL (child)) 
              { 
                  g_message ("ATK_ROLE_TOOLTIP object found, but doesn't look like a tooltip.");
                  return NULL;
              }
              name = gtk_label_get_text (GTK_LABEL (child));
            }
        }
    }
  return name;
}

static AtkObject*
gail_window_get_parent (AtkObject *accessible)
{
  AtkObject* parent;

  parent = ATK_OBJECT_CLASS (gail_window_parent_class)->get_parent (accessible);

  return parent;
}

static gint
gail_window_get_index_in_parent (AtkObject *accessible)
{
  GtkWidget* widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
  AtkObject* atk_obj = atk_get_root ();
  gint index = -1;

  if (widget == NULL)
    /*
     * State is defunct
     */
    return -1;

  gail_return_val_if_fail (GTK_IS_WIDGET (widget), -1);

  index = ATK_OBJECT_CLASS (gail_window_parent_class)->get_index_in_parent (accessible);
  if (index != -1)
    return index;

  if (GTK_IS_WINDOW (widget))
    {
      GtkWindow *window = GTK_WINDOW (widget);
      if (GAIL_IS_TOPLEVEL (atk_obj))
        {
	  GailToplevel* toplevel = GAIL_TOPLEVEL (atk_obj);
	  index = g_list_index (toplevel->window_list, window);
	}
      else
        {
	  int i, sibling_count = atk_object_get_n_accessible_children (atk_obj);
	  for (i = 0; i < sibling_count && index == -1; ++i)
	    {
	      AtkObject *child = atk_object_ref_accessible_child (atk_obj, i);
	      if (accessible == child) index = i;
	      g_object_unref (G_OBJECT (child));
	    }
	}
    }
  return index;
}

static gboolean
gail_window_real_focus_gtk (GtkWidget     *widget,
                            GdkEventFocus *event)
{
  AtkObject* obj;

  obj = gtk_widget_get_accessible (widget);
  atk_object_notify_state_change (obj, ATK_STATE_ACTIVE, event->in);

  return FALSE;
}

static AtkRelationSet*
gail_window_ref_relation_set (AtkObject *obj)
{
  GtkWidget *widget;
  AtkRelationSet *relation_set;
  AtkObject *array[1];
  AtkRelation* relation;
  GtkWidget *current_widget;

  gail_return_val_if_fail (GAIL_IS_WIDGET (obj), NULL);

  widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (obj));
  if (widget == NULL)
    /*
     * State is defunct
     */
    return NULL;

  relation_set = ATK_OBJECT_CLASS (gail_window_parent_class)->ref_relation_set (obj);

  if (atk_object_get_role (obj) == ATK_ROLE_TOOL_TIP)
    {
      relation = atk_relation_set_get_relation_by_type (relation_set, ATK_RELATION_POPUP_FOR);

      if (relation)
        {
          atk_relation_set_remove (relation_set, relation);
        }
      if (gtk_widget_get_visible(widget) && FALSE /* FIXME gtk_tooltips_get_info_from_tip_window (GTK_WINDOW (widget), NULL, &current_widget) */)
        {
          array [0] = gtk_widget_get_accessible (current_widget);

          relation = atk_relation_new (array, 1, ATK_RELATION_POPUP_FOR);
          atk_relation_set_add (relation_set, relation);
          g_object_unref (relation);
        }
    }
  return relation_set;
}

static AtkStateSet*
gail_window_ref_state_set (AtkObject *accessible)
{
  AtkStateSet *state_set;
  GtkWidget *widget;
  GtkWindow *window;
  GdkWindow *gdk_window;
  GdkWindowState state;

  state_set = ATK_OBJECT_CLASS (gail_window_parent_class)->ref_state_set (accessible);
  widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
 
  if (widget == NULL)
    return state_set;

  window = GTK_WINDOW (widget);

  if (gtk_window_has_toplevel_focus (window) && gtk_window_is_active (window))
    atk_state_set_add_state (state_set, ATK_STATE_ACTIVE);

  gdk_window = gtk_widget_get_window (widget);
  if (window)
    {
      state = gdk_window_get_state (gdk_window);
      if (state & GDK_WINDOW_STATE_ICONIFIED)
        atk_state_set_add_state (state_set, ATK_STATE_ICONIFIED);
    } 
  if (gtk_window_get_modal (window))
    atk_state_set_add_state (state_set, ATK_STATE_MODAL);

  if (gtk_window_get_resizable (window))
    atk_state_set_add_state (state_set, ATK_STATE_RESIZABLE);
 
  return state_set;
}

static gboolean
idle_notify_name_change (gpointer data)
{
  GailWindow *window;
  AtkObject *obj;

  window = GAIL_WINDOW (data);
  window->name_change_handler = 0;
  if (gtk_accessible_get_widget (GTK_ACCESSIBLE (window)) == NULL)
    return FALSE;

  obj = ATK_OBJECT (window);
  if (obj->name == NULL)
    {
    /*
     * The title has changed so notify a change in accessible-name
     */
      g_object_notify (G_OBJECT (obj), "accessible-name");
    }
  g_signal_emit_by_name (obj, "visible_data_changed");

  return FALSE;
}

static void
gail_window_real_notify_gtk (GObject		*obj,
                             GParamSpec		*pspec)
{
  GtkWidget *widget = GTK_WIDGET (obj);
  AtkObject* atk_obj = gtk_widget_get_accessible (widget);
  GailWindow *window = GAIL_WINDOW (atk_obj);
  const gchar *name;
  gboolean name_changed = FALSE;

  if (strcmp (pspec->name, "title") == 0)
    {
      name = gtk_window_get_title (GTK_WINDOW (widget));
      if (name)
        {
         if (window->previous_name == NULL ||
             strcmp (name, window->previous_name) != 0)
           name_changed = TRUE;
        }
      else if (window->previous_name != NULL)
        name_changed = TRUE;

      if (name_changed)
        {
          g_free (window->previous_name);
          window->previous_name = g_strdup (name);
       
          if (window->name_change_handler == 0)
            window->name_change_handler = gdk_threads_add_idle (idle_notify_name_change, atk_obj);
        }
    }
  else
    GAIL_WIDGET_CLASS (gail_window_parent_class)->notify_gtk (obj, pspec);
}

static gboolean
gail_window_state_event_gtk (GtkWidget           *widget,
                             GdkEventWindowState *event)
{
  AtkObject* obj;

  obj = gtk_widget_get_accessible (widget);
  atk_object_notify_state_change (obj, ATK_STATE_ICONIFIED,
                         (event->new_window_state & GDK_WINDOW_STATE_ICONIFIED) != 0);
  return FALSE;
}

static void
atk_component_interface_init (AtkComponentIface *iface)
{
  iface->get_extents = gail_window_get_extents;
  iface->get_size = gail_window_get_size;
  iface->get_mdi_zorder = gail_window_get_mdi_zorder;
}

static void
gail_window_get_extents (AtkComponent  *component,
                         gint          *x,
                         gint          *y,
                         gint          *width,
                         gint          *height,
                         AtkCoordType  coord_type)
{
  GtkWidget *widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (component)); 
  GdkRectangle rect;
  gint x_toplevel, y_toplevel;

  if (widget == NULL)
    /*
     * State is defunct
     */
    return;

  gail_return_if_fail (GTK_IS_WINDOW (widget));

  if (!gtk_widget_is_toplevel (widget))
    {
      AtkComponentIface *parent_iface;

      parent_iface = (AtkComponentIface *) g_type_interface_peek_parent (ATK_COMPONENT_GET_IFACE (component));
      parent_iface->get_extents (component, x, y, width, height, coord_type);
      return;
    }

  gdk_window_get_frame_extents (gtk_widget_get_window (widget),
                                &rect);

  *width = rect.width;
  *height = rect.height;
  if (!gtk_widget_is_drawable (widget))
    {
      *x = G_MININT;
      *y = G_MININT;
      return;
    }
  *x = rect.x;
  *y = rect.y;
  if (coord_type == ATK_XY_WINDOW)
    {
      gdk_window_get_origin (gtk_widget_get_window (widget),
                             &x_toplevel, &y_toplevel);
      *x -= x_toplevel;
      *y -= y_toplevel;
    }
}

static void
gail_window_get_size (AtkComponent *component,
                      gint         *width,
                      gint         *height)
{
  GtkWidget *widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (component));
  GdkRectangle rect;

  if (widget == NULL)
    /*
     * State is defunct
     */
    return;

  gail_return_if_fail (GTK_IS_WINDOW (widget));

  if (!gtk_widget_is_toplevel (widget))
    {
      AtkComponentIface *parent_iface;

      parent_iface = (AtkComponentIface *) g_type_interface_peek_parent (ATK_COMPONENT_GET_IFACE (component));
      parent_iface->get_size (component, width, height);
      return;
    }
  gdk_window_get_frame_extents (gtk_widget_get_window (widget), &rect);

  *width = rect.width;
  *height = rect.height;
}

#if defined (GDK_WINDOWING_X11)

#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <gdk/x11/gdkx.h>

/* _NET_CLIENT_LIST_STACKING monitoring */

typedef struct {
  Window     *stacked_windows;
  int         stacked_windows_len;
  GdkWindow  *root_window;
  guint       update_handler;
  int        *desktop;
  guint       update_desktop_handler;
  gboolean   *desktop_changed;

  guint       screen_initialized : 1;
  guint       update_stacked_windows : 1;
} GailScreenInfo;

static GailScreenInfo *gail_screens = NULL;
static int             num_screens = 0;
static Atom            _net_client_list_stacking = None;
static Atom            _net_wm_desktop = None;

static gint
get_window_desktop (Window window)
{
  Atom            ret_type;
  int             format;
  gulong          nitems;
  gulong          bytes_after;
  guchar         *cardinals;
  int             error;
  int             result;
  int             desktop;

  if (_net_wm_desktop == None)
    _net_wm_desktop =
		XInternAtom (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), "_NET_WM_DESKTOP", False);

  gdk_error_trap_push ();
  result = XGetWindowProperty (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), window, _net_wm_desktop,
                               0, G_MAXLONG,
                               False, XA_CARDINAL,
                               &ret_type, &format, &nitems,
                               &bytes_after, &cardinals);
  error = gdk_error_trap_pop();
  /* nitems < 1 will occur if the property is not set */
  if (error != Success || result != Success || nitems < 1)
    return -1;

  desktop = *cardinals;

  XFree (cardinals);
  if (nitems != 1)
    return -1;
  return desktop;
}

static void
free_screen_info (GailScreenInfo *info)
{
  if (info->stacked_windows)
    XFree (info->stacked_windows);
  if (info->desktop)
    g_free (info->desktop);
  if (info->desktop_changed)
    g_free (info->desktop_changed);

  info->stacked_windows = NULL;
  info->stacked_windows_len = 0;
  info->desktop = NULL;
  info->desktop_changed = NULL;
}

static gboolean
get_stacked_windows (GailScreenInfo *info)
{
  Atom    ret_type;
  int     format;
  gulong  nitems;
  gulong  bytes_after;
  guchar *data;
  int     error;
  int     result;
  int     i;
  int     j;
  int    *desktops;
  gboolean *desktops_changed;

  if (_net_client_list_stacking == None)
    _net_client_list_stacking =
		XInternAtom (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), "_NET_CLIENT_LIST_STACKING", False);

  gdk_error_trap_push ();
  ret_type = None;
  result = XGetWindowProperty (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()),
                               GDK_WINDOW_XID (info->root_window),
                               _net_client_list_stacking,
                               0, G_MAXLONG,
                               False, XA_WINDOW, &ret_type, &format, &nitems,
                               &bytes_after, &data);
  error = gdk_error_trap_pop ();
  /* nitems < 1 will occur if the property is not set */
  if (error != Success || result != Success || nitems < 1)
    {
      free_screen_info (info);
      return FALSE;
    }

  if (ret_type != XA_WINDOW)
    {
      XFree (data);
      free_screen_info (info);
      return FALSE;
    }

  desktops = g_malloc0 (nitems * sizeof (int));
  desktops_changed = g_malloc0 (nitems * sizeof (gboolean));
  for (i = 0; i < nitems; i++)
    {
      gboolean window_found = FALSE;

      for (j = 0; j < info->stacked_windows_len; j++)
        {
          if (info->stacked_windows [j] == data [i])
            {
              desktops [i] = info->desktop [j];
              desktops_changed [i] = info->desktop_changed [j];
              window_found = TRUE;
              break;
            }
        }
      if (!window_found)
        {
          desktops [i] = get_window_desktop (data [i]);
          desktops_changed [i] = FALSE;
        }
    }
  free_screen_info (info);
  info->stacked_windows = (Window*) data;
  info->stacked_windows_len = nitems;
  info->desktop = desktops;
  info->desktop_changed = desktops_changed;

  return TRUE;
}

static gboolean
update_screen_info (gpointer data)
{
  int screen_n = GPOINTER_TO_INT (data);

  gail_screens [screen_n].update_handler = 0;
  gail_screens [screen_n].update_stacked_windows = FALSE;

  get_stacked_windows (&gail_screens [screen_n]);

  return FALSE;
}

static gboolean
update_desktop_info (gpointer data)
{
  int screen_n = GPOINTER_TO_INT (data);
  GailScreenInfo *info;
  int i;

  info = &gail_screens [screen_n];
  info->update_desktop_handler = 0;

  for (i = 0; i < info->stacked_windows_len; i++)
    {
      if (info->desktop_changed [i])
        {
          info->desktop [i] = get_window_desktop (info->stacked_windows [i]);
          info->desktop_changed [i] = FALSE;
        }
    }

  return FALSE;
}

static GdkFilterReturn
filter_func (GdkXEvent *gdkxevent,
	     GdkEvent  *event,
	     gpointer   data)
{
  XEvent *xevent = gdkxevent;

  if (xevent->type == PropertyNotify)
    {
      if (xevent->xproperty.atom == _net_client_list_stacking)
        {
          int     screen_n;
          GdkWindow *window;

          window = event->any.window;

          if (window)
            {
              screen_n = gdk_screen_get_number (gdk_window_get_screen (window));

              gail_screens [screen_n].update_stacked_windows = TRUE;
              if (!gail_screens [screen_n].update_handler)
                {
                  gail_screens [screen_n].update_handler = gdk_threads_add_idle (update_screen_info,
	        						                 GINT_TO_POINTER (screen_n));
                }
            }
        }
      else if (xevent->xproperty.atom == _net_wm_desktop)
        {
          int     i;
          int     j;
          GailScreenInfo *info;

          for (i = 0; i < num_screens; i++)
            {
              info = &gail_screens [i];
              for (j = 0; j < info->stacked_windows_len; j++)
                {
                  if (xevent->xany.window == info->stacked_windows [j])
                    {
                      info->desktop_changed [j] = TRUE;
                      if (!info->update_desktop_handler)
                        {
                          info->update_desktop_handler = gdk_threads_add_idle (update_desktop_info,
                                                                               GINT_TO_POINTER (i));
                        }
                      break;
                    }
                }
            }
        }
    }
  return GDK_FILTER_CONTINUE;
}

static void
display_closed (GdkDisplay *display,
		gboolean    is_error)
{
  int i;

  for (i = 0; i < num_screens; i++)
    {
      if (gail_screens [i].update_handler)
	{
	  g_source_remove (gail_screens [i].update_handler);
	  gail_screens [i].update_handler = 0;
	}

      if (gail_screens [i].update_desktop_handler)
	{
	  g_source_remove (gail_screens [i].update_desktop_handler);
	  gail_screens [i].update_desktop_handler = 0;
	}

      free_screen_info (&gail_screens [i]);
    }

  g_free (gail_screens);
  gail_screens = NULL;
  num_screens = 0;
}

static void
init_gail_screens (void)
{
  GdkDisplay *display;

  display = gdk_display_get_default ();

  num_screens = gdk_display_get_n_screens (display);

  gail_screens = g_new0 (GailScreenInfo, num_screens);
  gdk_window_add_filter (NULL, filter_func, NULL);

  g_signal_connect (display, "closed", G_CALLBACK (display_closed), NULL);
}

static void
init_gail_screen (GdkScreen *screen,
                  int        screen_n)
{
  XWindowAttributes attrs;

  gail_screens [screen_n].root_window = gdk_screen_get_root_window (screen);

  get_stacked_windows (&gail_screens [screen_n]);

  XGetWindowAttributes (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()),
			GDK_WINDOW_XID (gail_screens [screen_n].root_window),
			&attrs); 

  XSelectInput (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()),
		GDK_WINDOW_XID (gail_screens [screen_n].root_window),
		attrs.your_event_mask | PropertyChangeMask);
           
  gail_screens [screen_n].screen_initialized = TRUE;
}

static GailScreenInfo *
get_screen_info (GdkScreen *screen)
{
  int screen_n;

  gail_return_val_if_fail (GDK_IS_SCREEN (screen), NULL);

  screen_n = gdk_screen_get_number (screen);

  if (gail_screens && gail_screens [screen_n].screen_initialized)
    return &gail_screens [screen_n];

  if (!gail_screens)
    init_gail_screens ();

  g_assert (gail_screens != NULL);

  init_gail_screen (screen, screen_n);

  g_assert (gail_screens [screen_n].screen_initialized);

  return &gail_screens [screen_n];
}

static gint
get_window_zorder (GdkWindow *window)
{
  GailScreenInfo *info;
  Window          xid;
  int             i;
  int             zorder;
  int             w_desktop;

  gail_return_val_if_fail (GDK_IS_WINDOW (window), -1);

  info = get_screen_info (gdk_window_get_screen (window));

  gail_return_val_if_fail (info->stacked_windows != NULL, -1);

  xid = GDK_WINDOW_XID (window);

  w_desktop = -1;
  for (i = 0; i < info->stacked_windows_len; i++)
    {
      if (info->stacked_windows [i] == xid)
        {
          w_desktop = info->desktop[i];
          break;
        }
    }
  if (w_desktop < 0)
    return w_desktop;

  zorder = 0;
  for (i = 0; i < info->stacked_windows_len; i++)
    {
      if (info->stacked_windows [i] == xid)
        {
          return zorder;
        }
      else
        {
          if (info->desktop[i] == w_desktop)
            zorder++;
        }
     }

  return -1;
}

static gint
gail_window_get_mdi_zorder (AtkComponent *component)
{
  GtkWidget *widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (component));

  if (widget == NULL)
    /*
     * State is defunct
     */
    return -1;

  gail_return_val_if_fail (GTK_IS_WINDOW (widget), -1);

  return get_window_zorder (gtk_widget_get_window (widget));
}

#elif defined (GDK_WINDOWING_WIN32)

static gint
gail_window_get_mdi_zorder (AtkComponent *component)
{
  GtkWidget *widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (component));

  if (widget == NULL)
    /*
     * State is defunct
     */
    return -1;

  gail_return_val_if_fail (GTK_IS_WINDOW (widget), -1);

  return 0;			/* Punt, FIXME */
}

#else

static gint
gail_window_get_mdi_zorder (AtkComponent *component)
{
  GtkWidget *widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (component));

  if (widget == NULL)
    /*
     * State is defunct
     */
    return -1;

  gail_return_val_if_fail (GTK_IS_WINDOW (widget), -1);

  return 0;			/* Punt, FIXME */
}

#endif
