/*  gfocustimer
 *  Copyright (C) 1999 James Cameron, <quozl@us.netrek.org>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/

#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

#include <stdio.h>
#include <gtk/gtk.h>
#include <gdk/gdkx.h>
#include <X11/X.h>

#include "callbacks.h"
#include "interface.h"
#include "support.h"
#include "utilities.h"
#include "data.h"
#include "charge.h"
#include "entry.h"
#include "connect.h"
#include "observation.h"


/* main window */
extern struct mw mw;

/* preferences data */
extern struct pd pd;

/* preferences window */
extern struct pw pw;


int focus_timeout_tag   = 0;

/* binary tree of windows, keyed by XID */
GTree *windows;


void focus_initialise()
{
  windows = g_tree_new (compare);
}


static int focus_errors (Display *display, XErrorEvent *event)
{
  /* ignore XFetchName problems */
  return TRUE;
}


/* perform focus monitoring periodically */
static gint focus_timeout (gpointer data)
{
  time_t now = time (NULL);
  static Window old = -1;
  Window new, me;
  int ignored;
  struct wd *wd;
  int go;
  char *name = "(unset)";

  /* wait until current input focus window changes */
  XGetInputFocus (gdk_display, &new, &ignored);
  if (new == old) return TRUE;
  old = new;

  /* ignore the new window if it is my top level main window */
  me = GDK_WINDOW_XWINDOW(mw.widget->window);
  if (new == me) return TRUE;
  
  /* ignore the new window if it is my top level preferences window */
  if (pw.widget != NULL) {
    me = GDK_WINDOW_XWINDOW(pw.widget->window);
    if (new == me) return TRUE;
  }

  /* fetch the window name */
  {
    int status;
    int (*old)(Display *, XErrorEvent *);

    old = XSetErrorHandler (focus_errors);

    status = XFetchName (gdk_display, new, &name);
    if (name == NULL) {
      unsigned int numkids;
      Window r, p, *kids;
      
      XQueryTree(gdk_display, new, &r, &p, &kids, &numkids);
      status = XFetchName(gdk_display, p, &name);
      if (name == NULL) name = "(not set)";
    }
    /* BUG: XFetchName expects caller to free the returned name */

#ifdef DEBUG
    printf ("XFetchName (display, %08x, *\"%s\") = %d\n",
	    (int) new, name, status);
#endif

    (void) XSetErrorHandler (old);
  }

#ifdef LOG
  {
    char *stamp = log_stamp(now);
    printf("%s\tfocus\t%s\n", stamp, name);
    g_free(stamp);
  }
#endif

  /* do we have this window in our binary tree? */
  wd = g_tree_lookup (windows, &new);

#ifdef CREATE_FOR_NEW_WINDOWS
  /* variant provided by Ian Peake <peaki@csee.uq.edu.au> that will
     create a new task for each window used on screen, for tracking
     time in each window rather than specifically attaching windows to
     tasks */
  if (wd == NULL) {
    /* no, we do not know of it */

    /* create a new entry with this name */
    mw.ed = entry_append_internal (name, 0);

    /* create a new window */
    wd = g_new (struct wd, 1);
    wd->window = new;
    g_tree_insert (windows, &wd->window, wd);
    wd->ed = mw.ed;

    wd->connected = now;
    if (pd.connect_do == CONNECT_DO_BEEP) gdk_beep ();
  }

  /* now we know the window: move to corresponding entry */
  go = wd->ed->row;
#else
  /* standard behaviour, if the window has not been associated with a
     task by the user, we either ignore it or move to the top task */
  if (wd == NULL) {
    /* no, we do not know of it */

    /* if we were told to ignore new windows, return now */
    if (pd.unknown_do == UNKNOWN_DO_CURRENT) return TRUE;

    /* if we are not currently on the top task, move to it */
    if (mw.row == 0) return TRUE;
    go = 0;

  } else {
    /* yes, we know the window, move to corresponding entry */
    go = wd->ed->row;
  }
#endif /* CREATE_FOR_NEW_WINDOWS */

  if (mw.charge) entry_highlight_inactive (mw.row);

  /* complete the current observation */
  if (mw.charge) observation_end (now);
  mw.row = go;
  mw.ed = (struct ed *) gtk_clist_get_row_data (mw.list, mw.row);
  if (mw.charge) entry_highlight_active (mw.row);

  /* start the new observation */
  if (mw.charge) observation_start_focus (now, mw.ed, new, name, wd);

  return TRUE;
}


void focus_schedule()
{
  focus_timeout_tag = gtk_timeout_add (pd.focus_rate, focus_timeout, NULL);
}


/* cancel the focus watch timeout */
void focus_deschedule()
{
  if (focus_timeout_tag != 0) {
    gtk_timeout_remove (focus_timeout_tag);
    focus_timeout_tag = 0;
  }
}


void focus_reschedule()
{
  if (focus_timeout_tag != 0) {
    focus_deschedule();
    focus_schedule();
  }
}


/* called when the Watch Focus toggle button is toggled */
void
on_focus_toggled                       (GtkToggleButton *togglebutton,
                                        gpointer         user_data)
{
  time_t now = time (NULL);

  /* is the toggle button being turned on or off? */
  if (gtk_toggle_button_get_active(togglebutton)) {

    /* start running the timeout process */
    focus_schedule();
    pd.watching = TRUE;
  } else {

    /* complete the current observation */
    if (mw.charge) observation_end (now);

    /* stop running the connect timeout */
    connect_deschedule();

    /* stop running the focus timeout */
    focus_deschedule();

    /* restore the highlight in case a connect was in progress */
    entry_highlight_active (mw.row);

    /* re-commence the observation on the current entry */
    if (mw.charge) observation_start_manual (now, mw.ed);
    pd.watching = FALSE;
  }
}
