/* if gcc $(pkg-config gtk+-2.0 --cflags --libs) -Wall -O2 -g motion-feedback-mainloop.c -o motion-feedback-mainloop ; then ./motion-feedback-mainloop ; fi

 * 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 <stdlib.h>
#include <unistd.h>
#include <gtk/gtk.h>

static GtkWidget *drawing_area, *vbox, *motion_mask, *motion_hint_mask, *fsty_reqn, *fsty_reqa, *fsty_gpob, *fsty_reqb, *idle_delay, *update_pos_in_cb, *srcode, *srhint;
static GdkGC *gc, *ungc;
static GdkColor fg, bg, red = {.red = 65535, .green = 0, .blue = 0};
static gdouble oldx = 0, oldy = 0;
static gint events = 0;
static gboolean reqa = FALSE, gpob = FALSE, reqb = FALSE, idel = FALSE, posincb = FALSE;
static gchar *code = NULL, *hint;

static void update_srcode (void)
{
  g_free (code);
  code = g_strdup_printf (
			  (idel ?
			    (gpob && posincb ?
			    "static gboolean\n"
			    "widget_motion_cb (GtkWidget *widget, GdkEventMotion *event, gpointer data)\n"
			    "{\n"
			    "%s%s  g_idle_add (draw_update_func, struct_with_position);\n"
			    "  return FALSE;\n"
			    "}\n"
			    "\n"
			    "static gboolean\n"
			    "draw_update_func (gpointer struct_with_position)\n"
			    "{\n"
			    "  ... process struct_with_position using stored %s ...\n"
			    "%s  return FALSE;\n"
			    "}\n"
			    "\n"
			    "  ...\n"
			    "  g_signal_connect (widget,\n"
			    "    \"motion-notify-event\",\n"
			    "    G_CALLBACK (widget_motion_cb), data);\n"
			    "  gtk_widget_set_events (widget, ...%s%s );\n"
			    "  ...\n" :
			    (posincb ?
			    "static gboolean\n"
			    "widget_motion_cb (GtkWidget *widget, GdkEventMotion *event, gpointer data)\n"
			    "{\n"
			    "  %s%s  g_idle_add (draw_update_func, gdk_event_copy ((GdkEvent *)event));\n"
			    "  return FALSE;\n"
			    "}\n"
			    "\n"
			    "static gboolean\n"
			    "draw_update_func (gpointer data)\n"
			    "{\n"
			    "  ... process event using %s ...\n"
			    "%s  gdk_event_free ((GdkEvent *)event);\n"
			    "return FALSE;\n"
			    "}\n"
			    "\n"
			    "  ...\n"
			    "  g_signal_connect (widget,\n"
			    "    \"motion-notify-event\",\n"
			    "    G_CALLBACK (widget_motion_cb), data);\n"
			    "  gtk_widget_set_events (widget, ...%s%s );\n"
			    "  ...\n" :
			    "static gboolean\n"
			    "widget_motion_cb (GtkWidget *widget, GdkEventMotion *event, gpointer data)\n"
			    "{\n"
			    "  g_idle_add (draw_update_func, gdk_event_copy ((GdkEvent *)event));\n"
			    "  return FALSE;\n"
			    "}\n"
			    "\n"
			    "static gboolean\n"
			    "draw_update_func (gpointer data)\n"
			    "{\n"
			    "%s%s  ... process event using %s ...\n"
			    "%s  gdk_event_free ((GdkEvent *)event);\n"
			    "return FALSE;\n"
			    "}\n"
			    "\n"
			    "  ...\n"
			    "  g_signal_connect (widget,\n"
			    "    \"motion-notify-event\",\n"
			    "    G_CALLBACK (widget_motion_cb), data);\n"
			    "  gtk_widget_set_events (widget, ...%s%s );\n"
			    "  ...\n" )
			    ) :
			  "static gboolean\n"
			  "widget_motion_cb (GtkWidget *widget, GdkEventMotion *event, gpointer data)\n"
			  "{\n"
			  "%s%s  ... process event using %s ...\n"
			  "%s}\n"
			  "\n"
			  "  ...\n"
			  "  g_signal_connect (widget,\n"
			  "    \"motion-notify-event\",\n"
			  "    G_CALLBACK (widget_motion_cb), data);\n"
			  "  gtk_widget_set_events (widget, ...%s%s );\n"
			  "  ...\n" ),
			  (reqb ? "  gdk_event_request_motions(event);\n" : ""),
			  (gpob ? "  gint x, y;\n  gdk_window_get_pointer (drawing_area->window, &x, &y, NULL);\n" : ""),
			  (gpob ? "x and y" : "event->x and event->y"),
			  (reqa ? "  gdk_event_request_motions(event);\n" : ""),
			  (events & GDK_POINTER_MOTION_MASK ? " |\n    GDK_POINTER_MOTION_MASK" : ""),
			  (events & GDK_POINTER_MOTION_HINT_MASK ? " |\n    GDK_POINTER_MOTION_HINT_MASK" : ""));

  gtk_label_set_text (GTK_LABEL(srcode), code);

  if (events & GDK_POINTER_MOTION_MASK)
    if (events & GDK_POINTER_MOTION_HINT_MASK)
      {
	if (reqb)
	  hint = "Often used in GTK+ code.\nFeedback significantly delayed.";
	else
	  if (reqa)
	    hint = "Recommended in doc.\nFeedback often delayed even with no motion.";
	  else
	    if (gpob)
	      hint = "Deprecated. Works nice with core events.\nDoes not work with non-core events.";
	    else
	      hint = "Expected to be broken.";
      }
    else
      if (gpob)
	hint = "Queue full of events after stopping the motion.";
      else
	if (reqa || reqb)
	  hint = "Slow feedback.\nUse of gdk_event_request_motions() has no effect.";
	else
	  hint = "Slow feedback.";
  else
    hint = "This combination does nothing useful.";

  gtk_label_set_text (GTK_LABEL(srhint), hint);
}

static gboolean
expose_cb (GtkWidget      *drawing_area,
	   GdkEventExpose *event,
	   gpointer        data)
{
  gdk_draw_rectangle (drawing_area->window,
		      ungc, TRUE,
		      0, 0,
		      drawing_area->allocation.width, drawing_area->allocation.height);
  return FALSE;
}

static gboolean
draw_update_func (gpointer data)
{
  gint x, y;
  GdkEventMotion *event = data;

  x = event->x;
  y = event->y;

  if (gpob && !(idel && posincb))
    gdk_window_get_pointer (drawing_area->window, &x, &y, NULL);
  if (reqb && !(idel && posincb))
    gdk_event_request_motions(event);

  gdk_draw_rectangle (drawing_area->window,
		      ungc, TRUE,
		      oldx-2, oldy-2,
		      4, 4);
  oldx = x;
  oldy = y;
  gdk_draw_rectangle (drawing_area->window,
		      gc, TRUE,
		      oldx-2, oldy-2,
		      4, 4);
  usleep (100000);
  gdk_window_process_all_updates();
  if (reqa)
    gdk_event_request_motions(event);
  gdk_event_free ((GdkEvent *)event);
  return FALSE;
}

static gboolean
motion_cb (GtkWidget *drawing_area, GdkEventMotion *event, gpointer data)
{
  GdkEvent *event_copy = gdk_event_copy ((GdkEvent *)event);
  if (idel)
  {
    if (gpob && posincb)
    {
      /* Ugly float <-> int double conversion makes possible to pass just the event_copy */
      gint x, y;
      gdk_window_get_pointer (drawing_area->window, &x, &y, NULL);
      ((GdkEventMotion *)event_copy)->x = x;
      ((GdkEventMotion *)event_copy)->y = y;
    }
    if (reqb && posincb)
      gdk_event_request_motions(event);
    g_idle_add (draw_update_func, event_copy);
  }
  else
    draw_update_func (event_copy);
  return FALSE;
}

static void update_events (gint new_events)
{
  events = new_events;
  gtk_container_remove (GTK_CONTAINER(vbox), drawing_area);
  drawing_area = gtk_drawing_area_new ();
  g_signal_connect (drawing_area, "expose-event",
		    G_CALLBACK (expose_cb), NULL);
  g_signal_connect (GTK_WIDGET(drawing_area), "motion-notify-event",
		    G_CALLBACK (motion_cb), NULL);
  gtk_widget_set_events (drawing_area, new_events);
  gtk_widget_set_size_request (drawing_area, 200, 200);
  gtk_box_pack_start_defaults (GTK_BOX(vbox), drawing_area);
  gtk_widget_show_all (drawing_area);
}

static gboolean
motion_mask_toggled (GtkButton *button, gpointer data)
{
  if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(button)))
    update_events (events | GDK_POINTER_MOTION_MASK);
  else
    update_events (events & ~GDK_POINTER_MOTION_MASK);
  update_srcode ();
  return FALSE;
}

static gboolean
motion_hint_mask_toggled (GtkButton *button, gpointer data)
{
  if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(button)))
    update_events (events | GDK_POINTER_MOTION_HINT_MASK);
  else
    update_events (events & ~GDK_POINTER_MOTION_HINT_MASK);
  update_srcode ();
  return FALSE;
}

static gboolean
reqn_toggled (GtkButton *button, gpointer data)
{
  if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(button)))
    {
      reqa = FALSE;
      reqb = FALSE;
      gpob = FALSE;
    }
  gtk_widget_set_sensitive (update_pos_in_cb, idel && (gpob || reqb));
  update_srcode ();
  return FALSE;
}
static gboolean
reqa_toggled (GtkButton *button, gpointer data)
{
  if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(button)))
    {
      reqa = TRUE;
      reqb = FALSE;
      gpob = FALSE;
    }
  gtk_widget_set_sensitive (update_pos_in_cb, idel && (gpob || reqb));
  update_srcode ();
  return FALSE;
}
static gboolean
gpob_toggled (GtkButton *button, gpointer data)
{
  if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(button)))
    {
      reqa = FALSE;
      reqb = FALSE;
      gpob = TRUE;
    }
  gtk_widget_set_sensitive (update_pos_in_cb, idel && (gpob || reqb));
  update_srcode ();
  return FALSE;
}
static gboolean
reqb_toggled (GtkButton *button, gpointer data)
{
  if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(button)))
    {
      reqa = FALSE;
      reqb = TRUE;
      gpob = FALSE;
    }
  update_srcode ();
  return FALSE;
}

static gboolean
idle_delay_toggled (GtkButton *button, gpointer data)
{
  idel = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(button));
  gtk_widget_set_sensitive (update_pos_in_cb, idel && (gpob || reqb));
  update_srcode ();
  return FALSE;
}

static gboolean
update_pos_in_cb_toggled (GtkButton *button, gpointer data)
{
  posincb = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(button));
  update_srcode ();
  return FALSE;
}

static void
destroy_cb (GtkObject *object, gpointer data)
{
  gtk_main_quit ();
}

int
main (int argc, char **argv)
{
  GtkWidget *window;

  gtk_init (&argc, &argv);

  window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  g_signal_connect (window, "destroy",
		    G_CALLBACK (destroy_cb), NULL);

  vbox = gtk_vbox_new (FALSE, 6);
  gtk_container_add (GTK_CONTAINER (window), vbox);

  drawing_area = gtk_drawing_area_new ();
  g_signal_connect (drawing_area, "expose-event",
		    G_CALLBACK(expose_cb), NULL);
  g_signal_connect (GTK_WIDGET(drawing_area), "motion-notify-event",
		    G_CALLBACK(motion_cb), NULL);
  gtk_widget_set_events (drawing_area, events);
  gtk_widget_set_size_request (drawing_area, 200, 200);
  gtk_box_pack_start_defaults (GTK_BOX(vbox), drawing_area);

  srhint = gtk_label_new ("");
  gtk_widget_modify_fg (srhint, GTK_STATE_NORMAL, &red);
  gtk_box_pack_end_defaults (GTK_BOX(vbox), srhint);

  srcode = gtk_label_new ("");
  gtk_label_set_selectable(GTK_LABEL(srcode), TRUE);
  gtk_box_pack_end_defaults (GTK_BOX(vbox), srcode);

  update_srcode ();

  update_pos_in_cb = gtk_check_button_new_with_label ("Update position in the motion callback");
  g_signal_connect (GTK_BUTTON (update_pos_in_cb), "toggled", G_CALLBACK (update_pos_in_cb_toggled), NULL);
  gtk_widget_set_sensitive (update_pos_in_cb, FALSE);
  gtk_box_pack_end_defaults (GTK_BOX(vbox), update_pos_in_cb);

  idle_delay = gtk_check_button_new_with_label ("Move slow processing to the idle loop");
  g_signal_connect (GTK_BUTTON (idle_delay), "toggled", G_CALLBACK (idle_delay_toggled), NULL);
  gtk_box_pack_end_defaults (GTK_BOX(vbox), idle_delay);

  fsty_reqn = gtk_radio_button_new_with_label (NULL,
					       "No special action, just process event");
  g_signal_connect (GTK_BUTTON (fsty_reqn), "toggled", G_CALLBACK (reqn_toggled), NULL);
  fsty_reqa = gtk_radio_button_new_with_label (gtk_radio_button_get_group (GTK_RADIO_BUTTON (fsty_reqn)),
					       "Use gdk_window_request_motions() after processing");
  g_signal_connect (GTK_BUTTON (fsty_reqa), "toggled", G_CALLBACK (reqa_toggled), NULL);
  fsty_gpob = gtk_radio_button_new_with_label (gtk_radio_button_get_group (GTK_RADIO_BUTTON (fsty_reqn)),
					       "Use gdk_window_get_pointer() before processing");
  g_signal_connect (GTK_BUTTON (fsty_gpob), "toggled", G_CALLBACK (gpob_toggled), NULL);
  fsty_reqb = gtk_radio_button_new_with_label (gtk_radio_button_get_group (GTK_RADIO_BUTTON (fsty_reqn)),
					       "Use gdk_window_request_motions() before processing");
  g_signal_connect (GTK_BUTTON (fsty_reqb), "toggled", G_CALLBACK (reqb_toggled), NULL);
  gtk_box_pack_end_defaults (GTK_BOX(vbox), fsty_reqb);
  gtk_box_pack_end_defaults (GTK_BOX(vbox), fsty_gpob);
  gtk_box_pack_end_defaults (GTK_BOX(vbox), fsty_reqa);
  gtk_box_pack_end_defaults (GTK_BOX(vbox), fsty_reqn);

  motion_hint_mask = gtk_check_button_new_with_label ("Set GDK_POINTER_MOTION_HINT_MASK");
  g_signal_connect (GTK_BUTTON (motion_hint_mask), "toggled", G_CALLBACK (motion_hint_mask_toggled), NULL);
  gtk_box_pack_end_defaults (GTK_BOX(vbox), motion_hint_mask);

  motion_mask = gtk_check_button_new_with_label ("Set GDK_POINTER_MOTION_MASK");
  g_signal_connect (GTK_BUTTON (motion_mask), "toggled", G_CALLBACK (motion_mask_toggled), NULL);
  gtk_box_pack_end_defaults (GTK_BOX(vbox), motion_mask);

  gtk_widget_show_all (window);

  gc = gdk_gc_new (drawing_area->window);
  ungc = gdk_gc_new (drawing_area->window);
  bg.red = 65535;
  bg.green = 65535;
  bg.blue = 65535;
  fg.red = 30000;
  fg.green = 30000;
  fg.blue = 30000;
  gdk_gc_set_rgb_fg_color (gc, &fg);
  gdk_gc_set_rgb_fg_color (ungc, &bg);

  gtk_main ();

  return 0;
}
