/*
 * testcrossing.c: Automated tests for widget sensitivity and mouse crossings
 *
 * Authors:
 *   Cody Russell <bratsche@gnome.org>
 *
 * 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 <gtk/gtk.h>

typedef struct {
  gint iteration;
  GtkWidget *window;
  GtkWidget *eventbox;
  GtkWidget *button1;
  GtkWidget *check1;
} TestCrossing;

void set_cursor (GtkWidget *widget)
{
    int x, y, w, h;

    gdk_window_get_origin (widget->window, &x, &y);

    x += widget->allocation.x;
    y += widget->allocation.y;
    w = widget->allocation.width;
    h = widget->allocation.height;

    gdk_display_warp_pointer (gtk_widget_get_display (widget),
                              gtk_widget_get_screen (widget),
                              x + w / 2,
                              y + h / 2);
}

static void
on_button_clicked (GtkWidget *button)
{
    g_print ("Clicked!\n");
}

static const char*
nick_of_enum_value (GType type, gint value)
{
  GEnumClass *eclass;
  GEnumValue *evalue;
  
  eclass = g_type_class_ref (type);
  evalue = g_enum_get_value (eclass, value);
  g_type_class_unref (eclass);

  return evalue ? evalue->value_nick : "unknown";
}

static const char*
mode_to_string (GdkCrossingMode mode)
{
  return nick_of_enum_value (GDK_TYPE_CROSSING_MODE, mode);
}

static const char*
detail_to_string (GdkNotifyType detail)
{
  return nick_of_enum_value (GDK_TYPE_NOTIFY_TYPE, detail);
}

static gboolean
on_enter (GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
{
  g_print (" ++ enter %s: %s, %s, %s\n",
           gtk_widget_get_name (widget),
           event->send_event ? "synthesized" : "native",
           mode_to_string (event->mode), 
           detail_to_string (event->detail));
  return FALSE;
}

static gboolean
on_leave (GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
{
  g_print (" -- leave %s: %s, %s, %s\n",
           gtk_widget_get_name (widget), 
           event->send_event ? "synthesized" : "native",
           mode_to_string (event->mode), 
           detail_to_string (event->detail));
  return FALSE;
}

static void
on_check_toggled (GtkWidget *toggle, GtkWidget *button)
{
    gtk_widget_set_sensitive (button, gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (toggle)));
}

static gboolean
test_timeout (TestCrossing *test)
{
  switch (test->iteration)
    {
    case -2:
      g_print ("        Setting cursor to sensitive widget %s.\n", 
               gtk_widget_get_name (test->button1));
      set_cursor (test->button1);
      break;
    case -1:
      g_print ("        Setting pointer away\n");
      gdk_display_warp_pointer (gtk_widget_get_display (test->window),
                                gtk_widget_get_screen (test->window),
                                1000, -1000);
      break;
    case 0:
      // Verify that we generate the expected sequence of crossing
      // events with correct details when the real pointer widget
      // becomes insensitive.
      g_print ("        Setting cursor to sensitive widget %s.\n", 
               gtk_widget_get_name (test->button1));
      set_cursor (test->button1);
      break;

    case 1:
      g_print ("        Setting widget %s insensitive.\n",
               gtk_widget_get_name (test->check1));
      gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (test->check1), FALSE);
      break;

    case 2:
      // Start with the real pointer widget being insensitive, verify
      // that moving the pointer to a sensitive sibling widget creates
      // the expected event sequence.
      g_print ("        Setting cursor to sensitive sibling %s.\n",
               gtk_widget_get_name (test->check1));
      set_cursor (test->check1);
      break;

    case 3:
      g_print ("        Moving cursor back to insensitive button %s.\n",
               gtk_widget_get_name (test->button1));
      set_cursor (test->button1);
      break;

    case 4:
      // Verify the event sequence when the real pointer widget becomes
      // sensitive again.
      g_print ("        Setting widget %s sensitive again.\n",
               gtk_widget_get_name (test->check1));
      gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (test->check1), TRUE);
      break;

    case 5:
      // Verify the event sequence when a sibling widget of the real
      // pointer widget establishes a GTK grab.
      g_print ("        Establishing GTK grab on check button %s.\n",
               gtk_widget_get_name (test->check1));
      gtk_grab_add (test->check1);
      break;

    case 6:
      // Start with the real pointer widget being grab-shadowed, verify that
      // moving the pointer to a not grab-shadowed widget creates the expected
      // event sequence.
      g_print ("        Moving cursor away from grab-shadowed widget to %s.\n",
               gtk_widget_get_name (test->check1));
      set_cursor (test->check1);
      break;

    case 7:
      g_print ("        Moving cursor back to grab-shadowed widget %s.\n",
               gtk_widget_get_name (test->button1));
      set_cursor (test->button1);
      break;

    case 8:
      // Verify the event sequence when the sibling widget releases the
      // grab again.
      g_print ("        Removing GTK grab from check button.\n");
      gtk_grab_remove (test->check1);
      break;

    default:
      g_print (" === Finished all tests! ===\n");
      return FALSE;
    }

  test->iteration++;
  
  return TRUE;
}

int main (int argc, char **argv)
{
    gtk_init (&argc, &argv);

    TestCrossing *test = g_new0 (TestCrossing, 1);

    test->window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
    gtk_widget_set_name (test->window, "W");
    g_object_connect (G_OBJECT (test->window),
                      "signal::destroy", gtk_main_quit, NULL,
                      "signal::enter-notify-event", on_enter, NULL,
                      "signal::leave-notify-event", on_leave, NULL,
                      NULL);
    GtkWidget *frame = gtk_frame_new ("Test #1");
    test->eventbox = gtk_event_box_new ();
    g_object_connect (G_OBJECT (test->eventbox),
                      "signal::enter-notify-event", on_enter, NULL,
                      "signal::leave-notify-event", on_leave, NULL,
                      NULL);
    gtk_widget_set_name (test->eventbox, "E");
    GtkWidget *vbox = gtk_vbox_new (FALSE, 10);
    gtk_container_add (GTK_CONTAINER (test->window), frame);
    gtk_container_add (GTK_CONTAINER (frame), test->eventbox);
    gtk_container_add (GTK_CONTAINER (test->eventbox), vbox);

    test->button1 = gtk_button_new_with_label ("Click me!");
    gtk_widget_set_name (test->button1, "B");
    g_object_connect (G_OBJECT (test->button1),
                      "signal::clicked", on_button_clicked, NULL,
                      "signal::enter-notify-event", on_enter, NULL,
                      "signal::leave-notify-event", on_leave, NULL,
                      NULL);
    gtk_widget_set_sensitive (test->button1, TRUE);
    gtk_box_pack_start (GTK_BOX (vbox), test->button1, FALSE, TRUE, 0);

    test->check1 = gtk_check_button_new_with_label ("Sensitive?");
    gtk_widget_set_name (test->check1, "C");
    gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (test->check1), TRUE);
    g_object_connect (G_OBJECT (test->check1),
		      "signal::toggled", G_CALLBACK (on_check_toggled), test->button1,
                      "signal::enter-notify-event", on_enter, NULL,
                      "signal::leave-notify-event", on_leave, NULL,
                      NULL);
                      
    gtk_box_pack_start (GTK_BOX (vbox), test->check1, FALSE, TRUE, 0);

    gtk_widget_show_all (test->window);

    test->iteration = -2;
    g_timeout_add_seconds (4, (GSourceFunc)test_timeout, test);

    gtk_main ();

    return 0;
}
