/*
 * gkrellm-reminder
 *
 * Copyright 2001, 2002 James Simonsen
 *
 */

/* 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
*/

#if 0
/* Many deprecated functions are still used. */
#define GDK_DISABLE_DEPRECATED
#define GTK_DISABLE_DEPRECATED
#endif


#if !defined(WIN32)
#include <gkrellm2/gkrellm.h>
#else
#include <src/gkrellm.h>
#include <src/win32-plugin.h>
#define F_RDLCK 0
#define S_IRUSR 0
#define S_IWUSR 0
#define S_IXUSR 0
#define F_WRLCK 0
#endif

#include <errno.h>

static gchar *str_title = "gkrellm-reminder";
static gchar *str_version = "2.0.0";
static gchar *str_date = "12/3/2002";
static gchar *str_copyright = "Copyright (c) 2001, 2002";
static gchar *str_author = "James Simonsen";
static gchar *str_email = "simonjam@ucs.orst.edu";
static gchar *str_url = "http://www.engr.orst.edu/~simonsen/reminder/";
static gchar *str_gpl = "Released under the GNU Public License";

#include "calendar.xpm"

#define	STYLE_NAME  "reminder"	/* Theme subdirectory name and gkrellmrc */
				/*  style name. 			 */
#define CONFIG_NAME "gkrellm-reminder"

#if defined(__NetBSD__) || defined(__FreeBSD__) || defined(__OpenBSD__)
  long bsd_timezone = 0;
# define TIMEZONE_DIFF ( bsd_timezone )
#else
# define TIMEZONE_DIFF ( timezone )
#endif

#define SECS_PER_DAY (86400)
#define SECS_PER_HOUR (3600)
#define SECS_PER_MIN (60)

#define FOREVER (0)

static struct reminder_config {
  gint remind_early;
  gint remind_early_diff;
  guint list_sort;
  guint alert;

  gboolean remind_old;
  gboolean delete_old;
  gboolean ampm;
  gboolean mdy;

  gchar *notify;

  gchar *event_file;
} config;

struct event_stored {
  gchar *name;
  guint id;
  gint days;
  gint occurs;
  time_t start;
  time_t end;
  time_t last_displayed;

  struct event_stored *next;
};

struct event_today {
  gchar *name;
  guint id;
  time_t occurs;
  gboolean last;

  struct event_today *next;
};

struct id_list {
  guint id;

  struct id_list *next;
};

enum {
  OPT_DAILY = 0,
  OPT_WEEKLY = 1,
  OPT_MONTHLY = 2
};

enum {
  ALERT_FLASH = 1,
  ALERT_POPUP = 2,
  ALERT_EXECUTE = 4
};

static struct event_today event_active;
static struct event_today *head_today;
static struct event_today *last_active;
static struct event_stored *head_stored;
static struct event_stored *head_temp;
static struct id_list *head_delete;

static GkrellmMonitor *reminder_mon;

static GkrellmPanel *panel;
static GkrellmDecal *reminder_icon_decal;
static GkrellmDecal *reminder_text_decal;
static GkrellmDecalbutton *reminder_text_button;
static GkrellmPiximage *reminder_icon_image;
static gint style_id;
static GkrellmTicks	*pGK;

static gint num_active, num_today;
static glong today_day;

/* Calendar window */
static GtkWidget *hbox_date;
static GtkWidget *hbox_start;
static GtkWidget *hbox_end;
static GtkWidget *notebook_occurs;

static GtkWidget *entry_event;

static GtkWidget *radio_daily;
static GtkWidget *radio_weekly;
static GtkWidget *radio_monthly;

static GtkWidget *spin_days;
static GtkWidget *spin_weeks;
static GtkWidget *spin_months;
static GtkWidget *spin_daymonth;

static GtkWidget *check_sun;
static GtkWidget *check_mon;
static GtkWidget *check_tue;
static GtkWidget *check_wed;
static GtkWidget *check_thu;
static GtkWidget *check_fri;
static GtkWidget *check_sat;

static GtkWidget *spin_start_month;
static GtkWidget *spin_start_day;
static GtkWidget *spin_start_year;
static GtkWidget *spin_end_month;
static GtkWidget *spin_end_day;
static GtkWidget *spin_end_year;
static GtkWidget *spin_time_hour;
static GtkWidget *spin_time_minute;
static GtkObject *adj_time_hour;
static GtkWidget *check_forever;

static GtkWidget *button_ampm;
static GtkWidget *label_ampm;

static GtkWidget *button_remove;
static GtkWidget *button_update;

static GtkWidget *list_main;

static gint occurs_option;
static gint list_main_row_selected;
static struct tm tm_input;
static gboolean is_pm;

/* Reminder window */
static GtkWidget *window_reminder;
static GtkWidget *spin_minutes;

/* Config window */
static GtkWidget *spin_remind_early;
static GtkWidget *check_remind_old;
static GtkWidget *check_delete_old;
static GtkWidget *check_alert_flash;
static GtkWidget *check_alert_popup;
static GtkWidget *check_alert_execute;
static GtkWidget *radio_12hour;
static GtkWidget *radio_24hour;
static GtkWidget *radio_mdy;
static GtkWidget *radio_dmy;
static GtkWidget *entry_notify;

/* Today window */
static GtkWidget *window_today;

static const gchar *str_delayed = "(Delayed) ";
static const gchar *str_null;
static const gchar *str_12hour = "%I:%M %p";
static const gchar *str_24hour = "%H:%M";
static const gchar *str_mdy = "%a %b %d %Y";
static const gchar *str_dmy = "%a %d %b %Y";

/* necessary prototypes */
static void reminder_display_reminder( void );

static int
reminder_lock_db( FILE *fp, int type )
{
#if !defined(WIN32)
  struct flock lock;

  lock.l_type = type;
  lock.l_start = 0;
  lock.l_whence = SEEK_SET;
  lock.l_len = 0;

  return fcntl( fileno( fp ), F_SETLKW, &lock );
#else
    return 0;
#endif
}

static int
reminder_unlock_db( FILE *fp )
{
#if !defined(WIN32)
  struct flock lock;

  lock.l_type = F_UNLCK;
  lock.l_start = 0;
  lock.l_whence = SEEK_SET;
  lock.l_len = 0;

  return fcntl( fileno( fp ), F_SETLK, &lock );
#else
  return 0;
#endif
}

static void
reminder_free_id_list()
{
  struct id_list *current, *next;

  current = head_delete;

  while( current )
    {
      next = current->next;

      free( current );

      current = next;
    }

  head_delete = NULL;
}

static void
reminder_free_stored( struct event_stored **head_list )
{
  struct event_stored *current, *next;

  current = *head_list;

  while( current )
    {
      next = current->next;

      g_free( current->name );
      free( current );

      current = next;
    }

  *head_list = NULL;
}

static void
reminder_load_stored()
{
  FILE *fp;
  gchar buffer[1024];
  struct event_stored *current, *tail = NULL;

  if( head_stored )
    return;

  fp = fopen( config.event_file, "r" );

  if( !fp )
    return; /* User hasn't saved any events yet */

  if( reminder_lock_db( fp, F_RDLCK ) )
    {
      gkrellm_message_window( str_title,
          "ERROR: Unable to lock event database for reading.", NULL );
      return;
    }

  while( fscanf( fp, "%[^\n]\n", buffer ) )
    {
      current = malloc( sizeof( struct event_stored ) );
      if( !current )
	break;

//      current->name = malloc( strlen( buffer ) + 1 );
//      if( !current->name )
//	{
//	  free( current );
//	  break;
//	}
  //    strcpy( current->name, buffer );
      current->name = g_strdup(buffer);

      if( fscanf( fp, "%u %d %d %ld %ld %ld\n", &current->id, &current->days,
          &current->occurs, &current->start, &current->end,
          &current->last_displayed ) != 6 )
	{
	  g_free( current->name );
	  free( current );
	  break;
	}
      if( current->occurs == TRUE && current->days >> 16 == 0 )
	{
	  current->occurs = OPT_WEEKLY;
	  current->days |= 1 << 16;
	}

      current->next = NULL;

      if( head_stored )
	tail->next = current;
      else
	head_stored = current;

      tail = current;
    }

  reminder_unlock_db( fp );
  fclose( fp );
}

static void
reminder_save_stored()
{
  FILE *fp;
  struct event_stored *current;

  fp = fopen( config.event_file, "a" ); /* "a" cuz we don't want to delete everything
				    before we get the write lock */
  if( !fp )
    {
      if( errno == ENOENT )
	{
      char *dir;
	  char *slash;

	  dir = malloc( strlen( config.event_file ) );
	  if( !dir )
	    return;

	  strcpy( dir, config.event_file );

	  slash = strrchr( dir, '/' );
	  if( !slash )
	    {
	      gkrellm_message_window( str_title, "ERROR: Unable to create event database.", NULL );
	      return;
	    }
	  *slash = '\0';

#if !defined(WIN32)
	  mkdir( dir, S_IRUSR | S_IWUSR | S_IXUSR );
#else
      mkdir(dir);
#endif

	  fp = fopen( config.event_file, "w" );
	  if( !fp )
	    {
	      gkrellm_message_window( str_title, "ERROR: Unable to open event database for writing.", NULL );
	      return;
	    }
	}
      else
	{
	  gkrellm_message_window( str_title, "ERROR: Unable to open event database for writing.", NULL );
	  return;
	}
    }

  if( reminder_lock_db( fp, F_WRLCK ) )
    {
      gkrellm_message_window( str_title, "ERROR: Unable to lock event database for writing.", NULL );
      return;
    }

#if defined(WIN32)
  // win32 can't lock the file, so just overwrite it
  fclose(fp);
  fopen( config.event_file, "w" );
#else
  if( ftruncate( fileno( fp ), 0 ) )
    {
      gkrellm_message_window( str_title, "ERROR: Unable to truncate event database.", NULL );
      return;
    }
#endif

  current = head_stored;
  while( current )
    {
      fprintf( fp, "%s\n%u %d %d %ld %ld %ld\n", current->name, current->id, current->days,
	       current->occurs, current->start, current->end, current->last_displayed );

      current = current->next;
    }

  reminder_unlock_db( fp );
  fclose( fp );
}

static void
reminder_remove_event_today( guint id )
{
  struct event_today *current, *temp;

  if( !head_today )
    return;

  if( head_today->id == id )
    {
      temp = head_today->next;

      g_free( head_today->name );
      free( head_today );

      head_today = temp;
    }
  else
    {
      current = head_today;

      while( current->next )
	{
	  if( current->next->id == id )
	    {
	      temp = current->next->next;

	      g_free( current->next->name );
	      free( current->next );

	      current->next = temp;

	      break;
	    }

	  current = current->next;
	}
    }
}

static gboolean
reminder_remove_event_stored( struct event_stored **head_list, guint id )
{
  struct event_stored *current, *temp;

  if( !*head_list )
    reminder_load_stored();

  if( !*head_list )
    return FALSE;

  if( (*head_list)->id == id )
    {
      temp = (*head_list)->next;

      g_free( (*head_list)->name );
      free( *head_list );

      *head_list = temp;

      return TRUE;
    }
  else
    {
      current = *head_list;

      while( current->next )
	{
	  if( current->next->id == id )
	    {
	      temp = current->next->next;

	      g_free( current->next->name );
	      free( current->next );

	      current->next = temp;

	      return TRUE;
	    }

	  current = current->next;
	}
    }

  return FALSE;
}

static struct event_stored *
reminder_find_event_stored( struct event_stored *head_list, guint id )
{
  struct event_stored *current;

  current = head_list;

  while( current )
    {
      if( current->id == id )
	return current;

      current = current->next;
    }

  return NULL;
}

static void
reminder_free_today()
{
  struct event_today *current, *next;

  current = head_today;

  while( current )
    {
      next = current->next;

      g_free( current->name );
      free( current );

      current = next;
    }

  head_today = NULL;
}

static struct event_today *
reminder_merge_sort( struct event_today *list )
{
  struct event_today *l, *r, *start_l, *start_r, *tail, *start;
  gboolean use_l = TRUE;

  if( !list || !list->next )
    return list;

  l = NULL;
  r = NULL;

  start = NULL;
  start_l = NULL;
  start_r = NULL;

  while( list )
    {
      if( use_l )
	{
	  if( l )
	    {
	      l->next = list;
	      l = l->next;
	    }
	  else
	    start_l = l = list;
	}
      else
	{
	  if( r )
	    {
	      r->next = list;
	      r = r->next;
	    }
	  else
	    start_r = r = list;
	}

      list = list->next;
      use_l = !use_l;
    }

  l->next = NULL;
  r->next = NULL;

  l = reminder_merge_sort( start_l );
  r = reminder_merge_sort( start_r );

  tail = NULL;

  while( l && r )
    {
      if( l->occurs < r->occurs )
	{
	  if( tail )
	    {
	      tail->next = l;
	      tail = tail->next;
	    }
	  else
	    start = tail = l;

	  l = l->next;
	}
      else
	{
	  if( tail )
	    {
	      tail->next = r;
	      tail = tail->next;
	    }
	  else
	    start = tail = r;

	  r = r->next;
	}
    }

  while( l )
    {
      if( tail )
	{
	  tail->next = l;
	  tail = tail->next;
	}
      else
	start = tail = l;

      l = l->next;
    }

  while( r )
    {
      if( tail )
	{
	  tail->next = r;
	  tail = tail->next;
	}
      else
	start = tail = r;

      r = r->next;
    }

  tail->next = NULL;

  return start;
}

static void
reminder_add_event_today( struct event_today **list, struct event_today *new,
			  struct event_today *tail )
{
  if( !(*list) )
    {
      *list = new;
    }
  else if( tail )
    {
      tail->next = new;
    }
  else
    {
      struct event_today *current = *list;

      while( current->next )
	current = current->next;

      current->next = new;
    }
}

static struct event_today *
reminder_create_event_today( struct event_stored *stored, struct tm *tm_stored,
			     gboolean last, gboolean is_tomorrow )
{
  struct event_today *new;
  struct tm tm_new;

  new = malloc( sizeof( struct event_today ) );
  if( !new )
    return NULL;

//  new->name = malloc( strlen( stored->name ) + 1 );
//  if( !new->name )
//    return NULL;

  //strcpy( new->name, stored->name );
  new->name = g_strdup(stored->name);

  memcpy( &tm_new, gkrellm_get_current_time(), sizeof( tm_new ) );

  tm_new.tm_mday += is_tomorrow ? 1 : 0;
  tm_new.tm_hour = tm_stored->tm_hour;
  tm_new.tm_min = tm_stored->tm_min - config.remind_early;
  tm_new.tm_sec = 0;
  tm_new.tm_isdst = -1;

  new->occurs = mktime( &tm_new );
  new->id = stored->id;
  new->last = last;
  new->next = NULL;

  return new;
}

static struct event_today *
reminder_weed_today( time_t now )
{
  struct event_today *current, *next, *tail, *weed;

  if( !head_today )
    return NULL;

  weed = NULL;
  tail = NULL;

  head_today->occurs += config.remind_early_diff * SECS_PER_MIN;

  if( ( now < head_today->occurs ) ||
      ( now > head_today->occurs + config.remind_early * SECS_PER_MIN && config.remind_old ) )
    {
      weed = head_today;
      head_today = NULL;
    }
  else
    {
      num_active = 1;
      num_today = 1;

      tail = current = head_today;
      while( current->next )
	{
	  next = current->next;

	  current->next->occurs += config.remind_early_diff;

	  if( ( now < current->next->occurs ) ||
	      ( now > current->next->occurs + config.remind_early * SECS_PER_MIN && config.remind_old ) )
	    {
	      current->next = NULL;

	      weed = next;

	      break;
	    }
	  else
	    {
	      tail = next;
	      num_active++;
	      num_today++;
	    }

	  current = next;
	}
    }

  while( weed )
    {
      next = weed->next;

      g_free( weed->name );
      free( weed );

      weed = next;
    }

  return tail;
}

static void
reminder_notify()
{
  gchar *cmd;

  if( !config.notify )
    return;

//  cmd = malloc( strlen( config.notify ) + 2 + 1 );
//  if( !cmd )
//    return;

//  strcpy( cmd, config.notify );
//  strcpy( cmd + strlen( cmd ), " &" );
  cmd = g_strdup_printf("%s &", config.notify);

//  gkrellm_system( cmd );
  g_spawn_command_line_async(cmd, NULL);

  g_free( cmd );
}

static void
reminder_check_new_active( struct event_today *list, struct event_today *active, time_t now )
{
  gint num_old_active = num_active;
  struct event_today *current;

  if( active )
    current = active->next;
  else
    current = list;

  while( current && current->occurs <= now )
    {
      num_active++;

      active = current;
      current = current->next;
    }

  last_active = active;

  if( num_active != num_old_active )
    {
      if( config.alert & ALERT_POPUP )
	reminder_display_reminder();
      if( config.alert & ALERT_EXECUTE )
	reminder_notify();
    }
}

static void
reminder_build_today( gboolean rebuild )
{
  time_t time_today;
  struct tm tm_today, tm_current, tm_start;
  struct event_stored *current, *next;
  struct event_today *new_today, *tail;
  long day_today, day_current, day_start, day_end, day_last_displayed;
  int secs_today, secs_start;
  gboolean is_today, is_tomorrow;
  int time_case, i;

  tail = NULL;

  last_active = NULL;
  num_today = 0;
  num_active = 0;

  /* Get current time */
  memcpy( &tm_today, gkrellm_get_current_time(), sizeof( tm_today ) );
  time_today = mktime( &tm_today );

  day_today = ( time_today - TIMEZONE_DIFF ) / SECS_PER_DAY;
  secs_today = ( time_today - TIMEZONE_DIFF ) % SECS_PER_DAY;

  if( head_today )
    {
      /* Don't delete old items if we're rebuilding */
      if( rebuild )
	tail = reminder_weed_today( time_today );
      else
	reminder_free_today();
    }

  current = head_stored;

  while( current )
    {
      next = current->next;

      if( rebuild && strstr( current->name, str_delayed ) )
	current->start += config.remind_early_diff * SECS_PER_MIN;

      memcpy( &tm_start, localtime( &current->start ), sizeof( tm_start ) );

      day_start = ( current->start - TIMEZONE_DIFF ) / SECS_PER_DAY;
      day_end = ( current->end - TIMEZONE_DIFF ) / SECS_PER_DAY;
      day_last_displayed = ( current->last_displayed - TIMEZONE_DIFF ) / SECS_PER_DAY;
      day_current = 0;

      secs_start = ( current->start - TIMEZONE_DIFF ) % SECS_PER_DAY;

      is_today = is_tomorrow = FALSE;

      /* is_tomorrow handles remind_early wrapping midnight */
      if( day_start <= day_today && ( day_today <= day_end || current->end == FOREVER ) )
	{
	  is_today = TRUE;
	  day_current = day_today;
	}
      else if( secs_start < config.remind_early * SECS_PER_MIN &&
	       day_start <= day_today+1 && ( day_today+1 <= day_end || current->end == FOREVER ) )
	{
	  is_tomorrow = TRUE;
	  day_current = day_today + 1;
	}

      /*
       * 1) Event happens well in the future
       * 2) Event happens in future, but we've already past the remind_early time
       * 3) Event happened in the past
       */
      if( secs_today > secs_start )
	time_case = 3;
      else if( secs_today < secs_start - config.remind_early * SECS_PER_MIN )
	time_case = 1;
      else
	time_case = 2;

      if( ( is_today && time_case == 1 ) ||
	  ( is_today && time_case == 2 && !rebuild && day_last_displayed < day_current ) ||
	  ( is_today && time_case == 2 && rebuild && day_last_displayed == 0 ) ||
	  ( is_today && time_case == 3 && config.remind_old && day_last_displayed < day_current ) ||
	  ( is_tomorrow ) )
	{
	  if( !current->last_displayed )
	    current->last_displayed = 10 * SECS_PER_DAY;

	  memcpy( &tm_current, localtime( &current->start ), sizeof( tm_current ) );

	  if( current->occurs == OPT_DAILY )
	    {
	      if( ( day_current - day_start ) % current->days == 0 )
		{
		  new_today = reminder_create_event_today( current, &tm_current,
							   day_end == day_current,
							   is_tomorrow);
		  reminder_add_event_today( &head_today, new_today, tail );
		  tail = new_today;
		  num_today++;
		}
	    }
	  else if( current->occurs == OPT_WEEKLY )
	    {
	      if( ( 1 << tm_today.tm_wday ) & current->days &&
		  ( ( day_today - ( day_start - tm_start.tm_wday ) ) / 7 ) % ( current->days >> 16 ) == 0 )
		{
		  new_today = reminder_create_event_today( current, &tm_current,
							   day_end == day_current,
							   is_tomorrow );
		  reminder_add_event_today( &head_today, new_today, tail );
		  tail = new_today;
		  num_today++;
		}
	    }
	  else if( current->occurs == OPT_MONTHLY )
	    {
	      if( ( tm_today.tm_mon - tm_start.tm_mon ) % ( current->days >> 16 ) == 0 &&
		  tm_today.tm_mday == ( current->days & 31 ) )
		{
		  new_today = reminder_create_event_today( current, &tm_current,
							   day_end == day_current,
							   is_tomorrow );
		  reminder_add_event_today( &head_today, new_today, tail );
		  tail = new_today;
		  num_today++;
		}
	    }
	}
      else if( config.delete_old && day_end <= day_today && current->end != FOREVER )
	{
	  reminder_remove_event_stored( &head_stored, current->id );
	}

      current = next;
    }

  head_today = reminder_merge_sort( head_today );
  for( i = 0; i < num_active; i++ )
    {
      if( last_active )
	last_active = last_active->next;
      else
	last_active = head_today;
    }

  reminder_save_stored();
  reminder_free_stored( &head_stored );
  reminder_check_new_active( head_today, last_active, time_today );
}

static void
cb_set_days( GtkWidget *window, gpointer data )
{
  gint weekdays = (gint) data & 1;
  gint weekends = (gint) data & 2;

  gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( check_sun ), weekends );
  gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( check_sat ), weekends );

  gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( check_mon ), weekdays );
  gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( check_tue ), weekdays );
  gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( check_wed ), weekdays );
  gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( check_thu ), weekdays );
  gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( check_fri ), weekdays );
}

static void
cb_select_radio( GtkWidget *window, gpointer data )
{
  gint option = -1;

  /* Figure out which one is selected */
  if( gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON( radio_daily ) ) )
    option = OPT_DAILY;
  else if( gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON( radio_weekly ) ) )
    option = OPT_WEEKLY;
  else if( gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON( radio_monthly ) ) )
    option = OPT_MONTHLY;

  if( occurs_option == option )
    return;

  gtk_notebook_set_page( GTK_NOTEBOOK( notebook_occurs ), option );

  occurs_option = option;
}

static gchar *
reminder_get_days_string( struct event_stored *current )
{
  gchar *string = NULL;
  gchar *string2;

//  string = malloc( 37 );
//  if( !string )
//    return NULL;

  if( current->occurs == OPT_DAILY )
    {
      if( current->days == 1 )
	//sprintf( string, "Everyday" );
    string = g_strdup_printf("Everyday");
      else
	//sprintf( string, "Every %d days", current->days );
      string = g_strdup_printf("Every %d days", current->days);
    }
  else if( current->occurs == OPT_WEEKLY )
    {
      int weeks = current->days >> 16;
      int days = current->days & 127;

      if( days == 127 )
	{
	  //pos = sprintf( string, "Everyday" );
          string = g_strdup_printf("Everyday");
	}
      else if( days == 62 )
	{
	  //pos = sprintf( string, "Every weekday" );
          string = g_strdup_printf("Every weekday");
	}
      else if( days == 65 )
	{
	  //pos = sprintf( string, "Every weekend" );
          string = g_strdup_printf("Every weekend");
	}
      else
	{
	  //pos = sprintf( string, "%s%s%s%s%s%s%s",
          string = g_strdup_printf("%s%s%s%s%s%s%s",
			 current->days & 1 ? "Sun " : str_null,
			 current->days & 2 ? "Mon " : str_null,
			 current->days & 4 ? "Tue " : str_null,
			 current->days & 8 ? "Wed " : str_null,
			 current->days & 16 ? "Thu " : str_null,
			 current->days & 32 ? "Fri " : str_null,
			 current->days & 64 ? "Sat" : str_null );
	}

      if( weeks > 1 )
	//sprintf( string + pos, "; Every %d weeks", weeks );
		{
        string2 = g_strdup_printf("; Every %d weeks", weeks );
        g_strconcat(string, string2, NULL);
        g_free(string2);
		}
    }
  else if( current->occurs == OPT_MONTHLY )
    {
      int day = current->days & 31;
      int month = current->days >> 16;

      if( day % 10 == 1 )
	//pos = sprintf( string, "%dst", day );
        string = g_strdup_printf("%dst", day );
      else if( day % 10 == 2 )
	//pos = sprintf( string, "%dnd", day );
        string = g_strdup_printf("%dnd", day );
      else if( day % 10 == 3 )
	//pos = sprintf( string, "%drd", day );
        string = g_strdup_printf("%drd", day );
      else
	//pos = sprintf( string, "%dth", day );
        string = g_strdup_printf("%dth", day );

      if( month == 1 ) {
	//sprintf( string + pos, " of every month" );
        string2 = g_strdup_printf(" of every month" );
        g_strconcat(string, string2, NULL);
        g_free(string2);
      }
      else {
	//sprintf( string + pos, " of every %d months", month );
          string2 = g_strdup_printf(" of every %d months", month );
          g_strconcat(string, string2, NULL);
          g_free(string2);
      }
    }

  return string;
}

static struct event_stored *
reminder_ui_to_event_stored( struct event_stored *current )
{
  gchar *name;

  name = (gchar *) gtk_entry_get_text( GTK_ENTRY( entry_event ) );
  if( strlen( name ) < 1 )
    {
      gkrellm_message_window( str_title, "ERROR: You must enter a name for this event.", entry_event );
      return NULL;
    }
//  current->name = malloc( strlen( name ) );
//  if( !current->name )
//    return NULL;
  current->name = g_strdup(name );

  tm_input.tm_mon = gtk_spin_button_get_value_as_int( GTK_SPIN_BUTTON( spin_start_month ) ) - 1;
  tm_input.tm_mday = gtk_spin_button_get_value_as_int( GTK_SPIN_BUTTON( spin_start_day ) );
  tm_input.tm_year = gtk_spin_button_get_value_as_int( GTK_SPIN_BUTTON( spin_start_year ) ) - 1900;
  tm_input.tm_hour = gtk_spin_button_get_value_as_int( GTK_SPIN_BUTTON( spin_time_hour ) );
  tm_input.tm_min = gtk_spin_button_get_value_as_int( GTK_SPIN_BUTTON( spin_time_minute ) );
  tm_input.tm_sec = 0;
  tm_input.tm_isdst = -1;

  if( config.ampm )
    {
      if( tm_input.tm_hour == 12 )
	tm_input.tm_hour = 0;

      if( is_pm )
	tm_input.tm_hour += 12;
    }

  current->start = mktime( &tm_input );

  if( gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON( check_forever ) ) )
    {
      current->end = FOREVER;
    }
  else
    {
      tm_input.tm_mon = gtk_spin_button_get_value_as_int( GTK_SPIN_BUTTON( spin_end_month ) ) - 1;
      tm_input.tm_mday = gtk_spin_button_get_value_as_int( GTK_SPIN_BUTTON( spin_end_day ) );
      tm_input.tm_year = gtk_spin_button_get_value_as_int( GTK_SPIN_BUTTON( spin_end_year ) ) - 1900;
      tm_input.tm_hour = 23;
      tm_input.tm_min = 59;
      tm_input.tm_sec = 59;
      tm_input.tm_isdst = -1;
      current->end = mktime( &tm_input );
    }

  if( current->end < current->start && current->end != FOREVER )
    {
      gkrellm_message_window( str_title, "ERROR: End date can't be smaller than start date.", spin_start_day );

      g_free( current->name );
      return NULL;
    }

  current->occurs = occurs_option;

  if( occurs_option == OPT_DAILY )
    {
      current->days = gtk_spin_button_get_value_as_int( GTK_SPIN_BUTTON( spin_days ) );
    }
  else if( occurs_option == OPT_WEEKLY )
    {
      current->days = 0;

      current->days |= gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON( check_sun ) ) ? 1 : 0;
      current->days |= gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON( check_mon ) ) ? 2 : 0;
      current->days |= gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON( check_tue ) ) ? 4 : 0;
      current->days |= gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON( check_wed ) ) ? 8 : 0;
      current->days |= gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON( check_thu ) ) ? 16 : 0;
      current->days |= gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON( check_fri ) ) ? 32 : 0;
      current->days |= gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON( check_sat ) ) ? 64 : 0;

      if( !current->days )
	{
	  gkrellm_message_window( str_title, "ERROR: You must select at least one day.", check_sun );
	  g_free( current->name );
	  return NULL;
	}

      current->days |= gtk_spin_button_get_value_as_int( GTK_SPIN_BUTTON( spin_weeks ) ) << 16;
    }
  else if( occurs_option == OPT_MONTHLY )
    {
      current->days = gtk_spin_button_get_value_as_int( GTK_SPIN_BUTTON( spin_daymonth ) ) |
	gtk_spin_button_get_value_as_int( GTK_SPIN_BUTTON( spin_months ) ) << 16;
    }

  return current;
}

static void
cb_add_entry( struct event_stored *event, gint row )
{
  gchar *array[] = { NULL, NULL, NULL, NULL, NULL };
  time_t real_start;

  array[2] = malloc( 9 );
  array[3] = malloc( 50 );
  array[4] = malloc( 50 );

  if( !array[2] || !array[3] || !array[4] )
    return;

  real_start = event->start;
  if( strstr( event->name, str_delayed ) )
    real_start -= config.remind_early * SECS_PER_MIN;

  array[0] = event->name;
  array[1] = reminder_get_days_string( event );

  if( config.ampm )
    strftime( array[2], 9, str_12hour, localtime( &real_start ) );
  else
    strftime( array[2], 9, str_24hour, localtime( &real_start ) );

  strftime( array[3], 50, config.mdy ? str_mdy : str_dmy, localtime( &real_start ) );
  if( event->end == FOREVER )
    strcpy( array[4], "Never" );
  else
    strftime( array[4], 50, config.mdy ? str_mdy : str_dmy, localtime( &event->end ) );

  if( row == -1 )
    {
      row = gtk_clist_append( GTK_CLIST( list_main ), array );
      gtk_clist_set_row_data( GTK_CLIST( list_main ), row, (gpointer) event->id );
    }
  else
    {
      gtk_clist_insert( GTK_CLIST( list_main ), row, array );
      gtk_clist_set_row_data( GTK_CLIST( list_main ), row, (gpointer) event->id );
    }

  gtk_clist_columns_autosize( GTK_CLIST( list_main ) );

  if( array[1] )
    g_free( array[1] );
  if( array[2] )
    free( array[2] );
  if( array[3] )
    free( array[3] );
  if( array[4] )
    free( array[4] );
}

static void
cb_populate()
{
  struct event_stored *current;

  gtk_clist_clear( GTK_CLIST( list_main ) );

  if( !head_stored )
    reminder_load_stored();

  current = head_stored;
  while( current )
    {
      cb_add_entry( current, -1 );

      current = current->next;
    }

  current = head_temp;
  while( current )
    {
      cb_add_entry( current, -1 );

      current = current->next;
    }
}

static void
reminder_add_event_stored( struct event_stored **list, struct event_stored *new, struct event_stored *tail )
{
  struct event_stored *current;

  if( !new )
    return;

  if( !(*list) )
    {
      *list = new;
    }
  else if( tail )
    {
      tail->next = new;
    }
  else
    {
      current = *list;

      while( current->next )
	current = current->next;

      current->next = new;
    }

  new->next = NULL;
}

static void
cb_add( GtkWidget *window, gpointer data )
{
  guint id;
  struct event_stored *current, *tail;

  id = (guint) time( NULL );

  tail = head_temp;
  if( tail )
    {
      if( tail->id == id )
	return;

      while( tail->next )
	{
	  if( tail->id == id )
	    return;

	  tail = tail->next;
	}
    }

  current = malloc( sizeof( struct event_stored ) );
  if( !current )
    return;

  if( !reminder_ui_to_event_stored( current ) )
    {
      free( current );
      return;
    }

  current->id = id;
  current->next = NULL;
  current->last_displayed = 0;

  reminder_add_event_stored( &head_temp, current, tail );

  cb_add_entry( current, -1 );
}

static void
cb_remove( GtkWidget *window, gpointer data )
{
  struct id_list *current;
  guint id;

  if( list_main_row_selected == -1 )
    return;

  id = (guint) gtk_clist_get_row_data( GTK_CLIST( list_main ), list_main_row_selected );

  /* Try to remove event from temp list. If not, add to to-be-deleted list */
  if( !reminder_remove_event_stored( &head_temp, id ) )
  {
    if( head_delete )
      {
	current = head_delete;
	while( current->next )
	  current = current->next;

	current->next = malloc( sizeof( struct id_list ) );
	if( !current->next )
	  return;

	current = current->next;
      }
    else
      {
	current = head_delete = malloc( sizeof( struct id_list ) );
	if( !current )
	  return;
      }

    current->id = id;
    current->next = NULL;
  }

  gtk_clist_remove( GTK_CLIST( list_main ), list_main_row_selected );
}

static void
cb_update( GtkWidget *window, gpointer data )
{
  if( list_main_row_selected == -1 )
    return;

  cb_remove( window, data );
  cb_add( window, data );
}

static void
cb_reset( GtkWidget *widget, gpointer data )
{
  gtk_entry_set_text( GTK_ENTRY( entry_event ), str_null );

  gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( radio_daily ), TRUE );
  cb_set_days( widget, 0 );
  gtk_spin_button_set_value( GTK_SPIN_BUTTON( spin_days ), 1 );
  gtk_spin_button_set_value( GTK_SPIN_BUTTON( spin_weeks ), 1 );
  gtk_spin_button_set_value( GTK_SPIN_BUTTON( spin_months ), 1 );

  gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( radio_daily ), TRUE );

  memcpy( &tm_input, gkrellm_get_current_time(), sizeof( tm_input ) );

  gtk_spin_button_set_value( GTK_SPIN_BUTTON( spin_start_month ), tm_input.tm_mon+1 );
  gtk_spin_button_set_value( GTK_SPIN_BUTTON( spin_start_day ), tm_input.tm_mday );
  gtk_spin_button_set_value( GTK_SPIN_BUTTON( spin_start_year ), tm_input.tm_year+1900 );
  gtk_spin_button_set_value( GTK_SPIN_BUTTON( spin_end_month ), tm_input.tm_mon+1 );
  gtk_spin_button_set_value( GTK_SPIN_BUTTON( spin_end_day ), tm_input.tm_mday );
  gtk_spin_button_set_value( GTK_SPIN_BUTTON( spin_end_year ), tm_input.tm_year+1900 );
  gtk_spin_button_set_value( GTK_SPIN_BUTTON( spin_daymonth ), tm_input.tm_mday );
  gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( check_forever ), FALSE );

  is_pm = tm_input.tm_hour >= 12;
  if( config.ampm )
    {
      gint val = tm_input.tm_hour;

      if( is_pm )
	val -= 12;
      if( val == 0 )
	val = 12;
	  
      gtk_spin_button_set_value( GTK_SPIN_BUTTON( spin_time_hour ), val );
    }
  else
    gtk_spin_button_set_value( GTK_SPIN_BUTTON( spin_time_hour ), tm_input.tm_hour );

  gtk_spin_button_set_value( GTK_SPIN_BUTTON( spin_time_minute ), tm_input.tm_min );
  gtk_label_set_text( GTK_LABEL( label_ampm ), is_pm ? "PM" : "AM" );
}

static void
cb_row_select( GtkWidget *widget, gint row, gint column, GdkEventButton *event,
	       gpointer data)
{
  struct event_stored *current;
  struct tm tm_event;

  list_main_row_selected = row;

  gtk_widget_set_sensitive( GTK_WIDGET( button_remove ), TRUE );
  gtk_widget_set_sensitive( GTK_WIDGET( button_update ), TRUE );

  if( !head_stored )
    reminder_load_stored();

  current = reminder_find_event_stored( head_stored, (glong) gtk_clist_get_row_data( GTK_CLIST( list_main ), row ) );
  if( !current )
    {
      current = reminder_find_event_stored( head_temp, (glong) gtk_clist_get_row_data( GTK_CLIST( list_main ), row ) );

      if( !current )
	return;
    }

  gtk_entry_set_text( GTK_ENTRY( entry_event ), current->name );

  if( current->occurs == OPT_DAILY )
    {
      gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( radio_daily ), TRUE );
      gtk_spin_button_set_value( GTK_SPIN_BUTTON( spin_days ), current->days );
    }
  else if( current->occurs == OPT_WEEKLY )
    {
      gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( radio_weekly ), TRUE);
      gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( check_sun ), current->days & 1 );
      gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( check_mon ), current->days & 2 );
      gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( check_tue ), current->days & 4 );
      gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( check_wed ), current->days & 8 );
      gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( check_thu ), current->days & 16 );
      gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( check_fri ), current->days & 32 );
      gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( check_sat ), current->days & 64 );
      gtk_spin_button_set_value( GTK_SPIN_BUTTON( spin_weeks ), current->days >> 16 );
    }
  else if( current->occurs == OPT_MONTHLY )
    {
      gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( radio_monthly ), TRUE );
      gtk_spin_button_set_value( GTK_SPIN_BUTTON( spin_daymonth ), current->days & 31 );
      gtk_spin_button_set_value( GTK_SPIN_BUTTON( spin_months ), current->days >> 16 );
    }

  memcpy( &tm_event, localtime( &current->start ), sizeof( tm_event ) );

  gtk_spin_button_set_value( GTK_SPIN_BUTTON( spin_start_month ), tm_event.tm_mon + 1 );
  gtk_spin_button_set_value( GTK_SPIN_BUTTON( spin_start_day ), tm_event.tm_mday );
  gtk_spin_button_set_value( GTK_SPIN_BUTTON( spin_start_year ), tm_event.tm_year + 1900 );

  is_pm = tm_event.tm_hour >= 12;
  if( config.ampm )
    {
      gint val = tm_event.tm_hour;

      if( is_pm )
	val -= 12;
      if( val == 0 )
	val = 12;
	  
      gtk_spin_button_set_value( GTK_SPIN_BUTTON( spin_time_hour ), val );
    }
  else
    gtk_spin_button_set_value( GTK_SPIN_BUTTON( spin_time_hour ), tm_event.tm_hour );

  gtk_spin_button_set_value( GTK_SPIN_BUTTON( spin_time_minute ), tm_event.tm_min );
  gtk_label_set_text( GTK_LABEL( label_ampm ), is_pm ? "PM" : "AM" );

  if( current->end == FOREVER )
    {
      gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( check_forever ), TRUE );
    }
  else
    {
      gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( check_forever ), FALSE );
      memcpy( &tm_event, localtime( &current->end ), sizeof( tm_event ) );

      gtk_spin_button_set_value( GTK_SPIN_BUTTON( spin_end_month ), tm_event.tm_mon + 1 );
      gtk_spin_button_set_value( GTK_SPIN_BUTTON( spin_end_day ), tm_event.tm_mday );
      gtk_spin_button_set_value( GTK_SPIN_BUTTON( spin_end_year ), tm_event.tm_year + 1900 );
    }
}

static void
cb_row_unselect( GtkWidget *widget, gint row, gint column, GdkEventButton *event,
		 gpointer data)
{
  if( list_main_row_selected == row )
    {
      list_main_row_selected = -1;
      gtk_widget_set_sensitive( GTK_WIDGET( button_remove ), FALSE );
      gtk_widget_set_sensitive( GTK_WIDGET( button_update ), FALSE );
    }
}

static void
cb_clamp_date( gboolean isStart )
{
  gint month, year, day;
  GtkAdjustment *adj;

  if( isStart )
    {
      month = gtk_spin_button_get_value_as_int( GTK_SPIN_BUTTON( spin_start_month ) );
      year = gtk_spin_button_get_value_as_int( GTK_SPIN_BUTTON( spin_start_year ) );
      adj = gtk_spin_button_get_adjustment( GTK_SPIN_BUTTON( spin_start_day ) );
    }
  else
    {
      month = gtk_spin_button_get_value_as_int( GTK_SPIN_BUTTON( spin_end_month ) );
      year = gtk_spin_button_get_value_as_int( GTK_SPIN_BUTTON( spin_end_year ) );
      adj = gtk_spin_button_get_adjustment( GTK_SPIN_BUTTON( spin_end_day ) );
    }

  if( month == 2 )
    {
      if( ( year % 400 == 0 ) ||
	  ( year % 100 != 0 && year % 4 == 0 ) )
	day = 29;
      else
	day = 28;
    }
  else if( ( month <= 7 && month % 2 == 1 ) ||
	   ( month >= 8 && month % 2 == 0 ) )
    day = 31;
  else
    day = 30;

  adj->upper = day;
  if( adj->value > day )
    adj->value = day;

  if( isStart )
    gtk_spin_button_set_adjustment( GTK_SPIN_BUTTON( spin_start_day ), GTK_ADJUSTMENT( adj ) );
  else
    gtk_spin_button_set_adjustment( GTK_SPIN_BUTTON( spin_end_day ), GTK_ADJUSTMENT( adj ) );
}

static void
cb_date_changed( GtkWidget *widget, gpointer data )
{
  if( data == spin_start_month || data == spin_start_year )
    cb_clamp_date( TRUE );
  else if( data == spin_end_month || data == spin_end_year )
    cb_clamp_date( FALSE );

  if( data == spin_start_month &&
      gtk_spin_button_get_value_as_int( GTK_SPIN_BUTTON( spin_start_year ) ) ==
      gtk_spin_button_get_value_as_int( GTK_SPIN_BUTTON( spin_end_year ) ) &&
      gtk_spin_button_get_value_as_int( GTK_SPIN_BUTTON( spin_start_month ) ) >
      gtk_spin_button_get_value_as_int( GTK_SPIN_BUTTON( spin_end_month ) ) )
    {
      gtk_spin_button_set_value( GTK_SPIN_BUTTON( spin_end_month ),
				 gtk_spin_button_get_value_as_int( GTK_SPIN_BUTTON( spin_start_month ) ) );
    }
  else if( data == spin_start_day &&
	   gtk_spin_button_get_value_as_int( GTK_SPIN_BUTTON( spin_start_year ) ) ==
	   gtk_spin_button_get_value_as_int( GTK_SPIN_BUTTON( spin_end_year ) ) &&
	   gtk_spin_button_get_value_as_int( GTK_SPIN_BUTTON( spin_start_month ) ) ==
	   gtk_spin_button_get_value_as_int( GTK_SPIN_BUTTON( spin_end_month ) ) &&
	   gtk_spin_button_get_value_as_int( GTK_SPIN_BUTTON( spin_start_day ) ) >
	   gtk_spin_button_get_value_as_int( GTK_SPIN_BUTTON( spin_end_day ) ) )
    {
      gtk_spin_button_set_value( GTK_SPIN_BUTTON( spin_end_day ),
				 gtk_spin_button_get_value_as_int( GTK_SPIN_BUTTON( spin_start_day ) ) );
    }
  else if( data == spin_start_year &&
	   gtk_spin_button_get_value_as_int( GTK_SPIN_BUTTON( spin_start_year ) ) >
	   gtk_spin_button_get_value_as_int( GTK_SPIN_BUTTON( spin_end_year ) ) )
    {
      gtk_spin_button_set_value( GTK_SPIN_BUTTON( spin_end_year ),
				 gtk_spin_button_get_value_as_int( GTK_SPIN_BUTTON( spin_start_year ) ) );
    }
}

static gint
cb_sort_days( GtkCList *clist, gconstpointer *p1, gconstpointer *p2 )
{
  GtkCListRow *row1 = (GtkCListRow *) p1;
  GtkCListRow *row2 = (GtkCListRow *) p2;

  struct event_stored *es1, *es2;

  es1 = reminder_find_event_stored( head_stored, (guint) row1->data );
  if( !es1 )
    es1 = reminder_find_event_stored( head_temp, (guint) row1->data );

  es2 = reminder_find_event_stored( head_stored, (guint) row2->data );
  if( !es2 )
    es2 = reminder_find_event_stored( head_temp, (guint) row2->data );

  if( es1 && es2 )
    {
      if( es1->occurs != es2->occurs )
	{
	  return es1->occurs - es2->occurs;
	}
      else
	{
	  return es1->days - es2->days;
	}
    }
  else
    return 0;
}

static gint
cb_sort_time( GtkCList *clist, gconstpointer *p1, gconstpointer *p2 )
{
  GtkCListRow *row1 = (GtkCListRow *) p1;
  GtkCListRow *row2 = (GtkCListRow *) p2;

  struct event_stored *es1, *es2;

  es1 = reminder_find_event_stored( head_stored, (guint) row1->data );
  if( !es1 )
    es1 = reminder_find_event_stored( head_temp, (guint) row1->data );

  es2 = reminder_find_event_stored( head_stored, (guint) row2->data );
  if( !es2 )
    es2 = reminder_find_event_stored( head_temp, (guint) row2->data );

  if( es1 && es2 )
    return( ( ( es1->start - TIMEZONE_DIFF ) % SECS_PER_DAY ) -
	    ( ( es2->start - TIMEZONE_DIFF ) % SECS_PER_DAY ) );
  else
    return 0;
}

static gint
cb_sort_start( GtkCList *clist, gconstpointer *p1, gconstpointer *p2 )
{
  GtkCListRow *row1 = (GtkCListRow *) p1;
  GtkCListRow *row2 = (GtkCListRow *) p2;

  struct event_stored *es1, *es2;

  es1 = reminder_find_event_stored( head_stored, (guint) row1->data );
  if( !es1 )
    es1 = reminder_find_event_stored( head_temp, (guint) row1->data );

  es2 = reminder_find_event_stored( head_stored, (guint) row2->data );
  if( !es2 )
    es2 = reminder_find_event_stored( head_temp, (guint) row2->data );

  if( es1 && es2 )
    return es1->start - es2->start;
  else
    return 0;
}

static gint
cb_sort_end( GtkCList *clist, gconstpointer *p1, gconstpointer *p2 )
{
  GtkCListRow *row1 = (GtkCListRow *) p1;
  GtkCListRow *row2 = (GtkCListRow *) p2;

  struct event_stored *es1, *es2;

  es1 = reminder_find_event_stored( head_stored, (guint) row1->data );
  if( !es1 )
    es1 = reminder_find_event_stored( head_temp, (guint) row1->data );

  es2 = reminder_find_event_stored( head_stored, (guint) row2->data );
  if( !es2 )
    es2 = reminder_find_event_stored( head_temp, (guint) row2->data );

  if( es1 && es2 )
    return es1->end - es2->end;
  else
    return 0;
}

static void
cb_sort()
{
  /* 0: GTK_SORT_ASCENDING
     1: GTK_SORT_DESCENDING
     2: name
     4: days
     8: time
     16: start
     32: end  */

  if( config.list_sort & 1 )
    gtk_clist_set_sort_type( GTK_CLIST( list_main ), GTK_SORT_DESCENDING );
  else
    gtk_clist_set_sort_type( GTK_CLIST( list_main ), GTK_SORT_ASCENDING );

  if( config.list_sort & 2 )
    {
      gtk_clist_set_sort_column( GTK_CLIST( list_main ), 0 );
      gtk_clist_set_compare_func( GTK_CLIST( list_main ), NULL );
    }
  else if( config.list_sort & 4 )
    {
      gtk_clist_set_sort_column( GTK_CLIST( list_main ), 1 );
      gtk_clist_set_compare_func( GTK_CLIST( list_main ), (GtkCListCompareFunc) cb_sort_days );
    }
  else if( config.list_sort & 8 )
    {
      gtk_clist_set_sort_column( GTK_CLIST( list_main ), 1 );
      gtk_clist_set_compare_func( GTK_CLIST( list_main ), (GtkCListCompareFunc) cb_sort_time );
    }
  else if( config.list_sort & 16 )
    {
      gtk_clist_set_sort_column( GTK_CLIST( list_main ), 2 );
      gtk_clist_set_compare_func( GTK_CLIST( list_main ), (GtkCListCompareFunc) cb_sort_start );
    }
  else if( config.list_sort & 32 )
    {
      gtk_clist_set_sort_column( GTK_CLIST( list_main ), 3 );
      gtk_clist_set_compare_func( GTK_CLIST( list_main ), (GtkCListCompareFunc) cb_sort_end );
    }

  gtk_clist_sort( GTK_CLIST( list_main ) );
}

static void
cb_column_click( GtkWidget *widget, gint column, gpointer data )
{
  column = 1 << ( column + 1 );

  if( config.list_sort & column )
    {
      if( config.list_sort & 1 )
	config.list_sort &= ~1;
      else
	config.list_sort |= 1;
    }
  else
    {
      config.list_sort = column;
    }

  cb_sort();
}

static void
cb_ampm_clicked( GtkWidget *window, gpointer data )
{
  is_pm = !is_pm;

  gtk_label_set_text( GTK_LABEL( label_ampm ), is_pm ? "PM" : "AM" );
}

static void
cb_reorder_date()
{
  if( config.mdy )
    {
      gtk_box_reorder_child( GTK_BOX( hbox_start ), spin_start_month, 1 );
      gtk_box_reorder_child( GTK_BOX( hbox_end ), spin_end_month, 1 );
    }
  else
    {
      gtk_box_reorder_child( GTK_BOX( hbox_start ), spin_start_month, 2 );
      gtk_box_reorder_child( GTK_BOX( hbox_end ), spin_end_month, 2 );
    }
}

static void
cb_forever( GtkWidget *window, gpointer data )
{
  if( gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON( check_forever ) ) )
    {
      gtk_widget_set_sensitive( spin_end_month, FALSE );
      gtk_widget_set_sensitive( spin_end_day, FALSE );
      gtk_widget_set_sensitive( spin_end_year, FALSE );
    }
  else
    {
      gtk_widget_set_sensitive( spin_end_month, TRUE );
      gtk_widget_set_sensitive( spin_end_day, TRUE );
      gtk_widget_set_sensitive( spin_end_year, TRUE );
    }
}

static GtkWidget *
create_calendar_event()
{
  GtkWidget *hbox_event;
  GtkWidget *label_event;
  /*        *entry_event */

  hbox_event = gtk_hbox_new( FALSE, 2 );
  label_event = gtk_label_new( "Event:" );
  entry_event = gtk_entry_new();

  gtk_box_pack_start( GTK_BOX( hbox_event ), label_event, FALSE, FALSE, 2 );
  gtk_box_pack_start( GTK_BOX( hbox_event ), entry_event, TRUE, TRUE, 2 );

  return hbox_event;
}

static GtkWidget *
create_calendar_time()
{
  GtkWidget *hbox_time;
  GtkWidget *label_time;
  /*        *adj_time_hour */
  GtkObject *adj_time_minute;
  /*        *spin_time_hour */
  /*        *spin_time_minute */
  /*        *button_ampm */

  hbox_time = gtk_hbox_new( FALSE, 2 );

  adj_time_hour = gtk_adjustment_new( 0, 0, 23, 1, 10, 0 );
  adj_time_minute = gtk_adjustment_new( 0, 0, 59, 1, 10, 0 );

  label_time = gtk_label_new( "Time:" );
  spin_time_hour = gtk_spin_button_new( GTK_ADJUSTMENT( adj_time_hour ), 0.0, 0 );
  spin_time_minute = gtk_spin_button_new( GTK_ADJUSTMENT( adj_time_minute ), 0.0, 0 );
  button_ampm = gtk_button_new();

  label_ampm = gtk_label_new( is_pm ? "PM" : "AM" );
  gtk_container_add( GTK_CONTAINER( button_ampm ), label_ampm );

  gtk_spin_button_set_numeric( GTK_SPIN_BUTTON( spin_time_hour ), TRUE );
  gtk_spin_button_set_numeric( GTK_SPIN_BUTTON( spin_time_minute ), TRUE );

  if( !config.ampm )
    gtk_widget_set_sensitive( button_ampm, FALSE );
  else
    {
      GTK_ADJUSTMENT( adj_time_hour )->lower = 1;
      GTK_ADJUSTMENT( adj_time_hour )->upper = 12;

      gtk_spin_button_update( GTK_SPIN_BUTTON( spin_time_hour ) );
    }

  g_signal_connect( G_OBJECT( button_ampm ), "clicked",
		      G_CALLBACK(cb_ampm_clicked), NULL );

  gtk_box_pack_start( GTK_BOX( hbox_time ), label_time, FALSE, FALSE, 2 );
  gtk_box_pack_start( GTK_BOX( hbox_time ), spin_time_hour, FALSE, FALSE, 2 );
  gtk_box_pack_start( GTK_BOX( hbox_time ), spin_time_minute, FALSE, FALSE, 2 );
  gtk_box_pack_start( GTK_BOX( hbox_time ), button_ampm, FALSE, FALSE, 2 );

  return hbox_time;
}

static GtkWidget *
create_calendar_date_date( gboolean isStart )
{
  GtkWidget *hbox_date_date;
  GtkWidget *label;
  GtkWidget **spin_month;
  GtkWidget **spin_day;
  GtkWidget **spin_year;
  GtkObject *adj_month;
  GtkObject *adj_day;
  GtkObject *adj_year;

  hbox_date_date = gtk_hbox_new( FALSE, 2 );

  adj_month = gtk_adjustment_new( 0, 1, 12, 1, 3, 0 );
  adj_day = gtk_adjustment_new( 0, 1, 31, 1, 10, 0 );
  adj_year = gtk_adjustment_new( 0, 1971, 2037, 1, 10, 0 );

  if( isStart )
    {
      label = gtk_label_new( "Start:" );

      spin_month = &spin_start_month;
      spin_day = &spin_start_day;
      spin_year = &spin_start_year;
    }
  else
    {
      label = gtk_label_new( "End:" );

      spin_month = &spin_end_month;
      spin_day = &spin_end_day;
      spin_year = &spin_end_year;
    }

  *spin_month = gtk_spin_button_new( GTK_ADJUSTMENT( adj_month ), 0.0, 0 );
  *spin_day = gtk_spin_button_new( GTK_ADJUSTMENT( adj_day ), 0.0, 0 );
  *spin_year = gtk_spin_button_new( GTK_ADJUSTMENT( adj_year ), 0.0, 0 );

  gtk_spin_button_set_numeric( GTK_SPIN_BUTTON( *spin_month ), TRUE );
  gtk_spin_button_set_numeric( GTK_SPIN_BUTTON( *spin_day ), TRUE );
  gtk_spin_button_set_numeric( GTK_SPIN_BUTTON( *spin_year ), TRUE );

  g_signal_connect( adj_month, "value-changed",
        G_CALLBACK( cb_date_changed ), *spin_month );
  g_signal_connect( adj_day, "value-changed",
        G_CALLBACK( cb_date_changed ), *spin_day );
  g_signal_connect( adj_year, "value-changed",
        G_CALLBACK( cb_date_changed ), *spin_year );

  gtk_box_pack_start( GTK_BOX( hbox_date_date ), label, FALSE, FALSE, 2 );
  gtk_box_pack_start( GTK_BOX( hbox_date_date ), *spin_month, FALSE, FALSE, 2 );
  gtk_box_pack_start( GTK_BOX( hbox_date_date ), *spin_day, FALSE, FALSE, 2 );
  gtk_box_pack_start( GTK_BOX( hbox_date_date ), *spin_year, TRUE, TRUE, 2 );

  cb_clamp_date( isStart );

  return hbox_date_date;
}

static GtkWidget *
create_calendar_date()
{
  GtkWidget *hbox_date;
  /*        *hbox_start */
  /*        *hbox_end */

  hbox_date = gtk_hbox_new( FALSE, 2 );
  hbox_start = create_calendar_date_date( TRUE );
  hbox_end = create_calendar_date_date( FALSE );

  check_forever = gtk_check_button_new_with_label( "Forever" );
  g_signal_connect( G_OBJECT( check_forever ), "clicked",
         G_CALLBACK( cb_forever ), NULL );

  gtk_box_pack_start( GTK_BOX( hbox_date ), hbox_start, TRUE, TRUE, 2 );
  gtk_box_pack_start( GTK_BOX( hbox_date ), hbox_end, TRUE, TRUE, 2 );
  gtk_box_pack_start( GTK_BOX( hbox_date ), check_forever, FALSE, FALSE, 2 );

  return hbox_date;
}

static GtkWidget *
create_calendar_ops()
{
  GtkWidget *hbox_ops;
  GtkWidget *button_add;
  GtkWidget *button_reset;
  /*        *button_remove */
  /*        *button_update */

  hbox_ops = gtk_hbox_new( TRUE, 2 );

  button_add = gtk_button_new_with_label( "Add" );
  button_remove = gtk_button_new_with_label( "Remove" );
  button_update = gtk_button_new_with_label( "Update" );
  button_reset = gtk_button_new_with_label( "Reset" );

  gtk_widget_set_sensitive( GTK_WIDGET( button_remove ), FALSE );
  gtk_widget_set_sensitive( GTK_WIDGET( button_update ), FALSE );

  g_signal_connect( G_OBJECT( button_add ), "clicked",
		      G_CALLBACK( cb_add ), NULL );
  g_signal_connect( G_OBJECT( button_remove ), "clicked",
		      G_CALLBACK( cb_remove ), NULL );
  g_signal_connect( G_OBJECT( button_update ), "clicked",
		      G_CALLBACK( cb_update ), NULL );
  g_signal_connect( G_OBJECT( button_reset ), "clicked",
		      G_CALLBACK( cb_reset ), NULL );

  gtk_box_pack_start( GTK_BOX( hbox_ops ), button_add, TRUE, TRUE, 10 );
  gtk_box_pack_start( GTK_BOX( hbox_ops ), button_remove, TRUE, TRUE, 10 );
  gtk_box_pack_start( GTK_BOX( hbox_ops ), button_update, TRUE, TRUE, 10 );
  gtk_box_pack_start( GTK_BOX( hbox_ops ), button_reset, TRUE, TRUE, 10 );

  return hbox_ops;
}

static GtkWidget *
create_calendar_list()
{
  GtkWidget *hbox_list;
  GtkWidget *list_scroll;
  /*        *list_main */

  static gchar *list_titles[] = { "Event", "Days", "Time", "Start", "End" };

  hbox_list = gtk_hbox_new( FALSE, 2 );

  list_scroll = gtk_scrolled_window_new( NULL, NULL );
  list_main = gtk_clist_new_with_titles( 5, list_titles );
  list_main_row_selected = -1;

  g_signal_connect( G_OBJECT( list_main ), "select_row",
		      G_CALLBACK( cb_row_select ), NULL );
  g_signal_connect( G_OBJECT( list_main ), "unselect_row",
		      G_CALLBACK( cb_row_unselect ), NULL );
  g_signal_connect( G_OBJECT( list_main ), "click_column",
		      G_CALLBACK( cb_column_click ), NULL );

  gtk_clist_set_selection_mode( GTK_CLIST( list_main ), GTK_SELECTION_SINGLE );
  gtk_clist_column_titles_active( GTK_CLIST( list_main ) );
  gtk_clist_set_auto_sort( GTK_CLIST( list_main ), TRUE );

  gtk_container_add( GTK_CONTAINER( list_scroll ), list_main );
  gtk_box_pack_start( GTK_BOX( hbox_list ), list_scroll, TRUE, TRUE, 2 );

  return hbox_list;
}

static GtkWidget *
create_calendar_occurs()
{
  GtkWidget *vbox_occurs;

  vbox_occurs = gtk_vbox_new( FALSE, 2 );

  radio_daily = gtk_radio_button_new_with_label( NULL, "Daily" );
  radio_weekly = gtk_radio_button_new_with_label( gtk_radio_button_group( GTK_RADIO_BUTTON( radio_daily ) ),
						  "Weekly" );
  radio_monthly = gtk_radio_button_new_with_label( gtk_radio_button_group( GTK_RADIO_BUTTON( radio_daily ) ),
						   "Monthly" );

  g_signal_connect( G_OBJECT( radio_daily ), "clicked",
		      G_CALLBACK( cb_select_radio ), NULL );
  g_signal_connect( G_OBJECT( radio_weekly ), "clicked",
		      G_CALLBACK( cb_select_radio ), NULL );
  g_signal_connect( G_OBJECT( radio_monthly ), "clicked",
		      G_CALLBACK( cb_select_radio ), NULL );

  gtk_box_pack_start( GTK_BOX( vbox_occurs ), radio_daily, FALSE, FALSE, 2 );
  gtk_box_pack_start( GTK_BOX( vbox_occurs ), radio_weekly, FALSE, FALSE, 2 );
  gtk_box_pack_start( GTK_BOX( vbox_occurs ), radio_monthly, FALSE, FALSE, 2 );

  gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( radio_daily ), TRUE );

  return vbox_occurs;
}

static GtkWidget *
create_calendar_daily()
{
  GtkWidget *vbox_daily;
  GtkWidget *hbox_days;
  GtkWidget *label_every;
  GtkWidget *label_days;
  GtkObject *adj_days;

  vbox_daily = gtk_vbox_new( FALSE, 2 );
  hbox_days = gtk_hbox_new( FALSE, 2 );

  label_every = gtk_label_new( "Every" );
  adj_days = gtk_adjustment_new( 1, 1, 365, 1, 10, 0 );
  spin_days = gtk_spin_button_new( GTK_ADJUSTMENT( adj_days ), 0.0, 0 );
  label_days = gtk_label_new( "day(s)" );

  gtk_spin_button_set_numeric( GTK_SPIN_BUTTON( spin_days ), TRUE );

  gtk_box_pack_start( GTK_BOX( hbox_days ), label_every, FALSE, FALSE, 2 );
  gtk_box_pack_start( GTK_BOX( hbox_days ), spin_days, FALSE, FALSE, 2 );
  gtk_box_pack_start( GTK_BOX( hbox_days ), label_days, FALSE, FALSE, 2 );

  gtk_box_pack_start( GTK_BOX( vbox_daily ), hbox_days, FALSE, FALSE, 2 );

  return vbox_daily;
}

static GtkWidget *
create_calendar_weekly()
{
  GtkWidget *vbox_weekly;
  GtkWidget *hbox_weekly_checks;
  GtkWidget *hbox_weekly_buttons;
  GtkWidget *hbox_weekly_label;
  GtkWidget *button_weekdays;
  GtkWidget *button_weekends;
  GtkWidget *button_clear;
  GtkWidget *label_every;
  GtkWidget *label_weeks;
  /*        *spin_weeks */
  GtkObject *adj_weeks;

  vbox_weekly = gtk_vbox_new( FALSE, 2 );
  hbox_weekly_checks = gtk_hbox_new( TRUE, 2 );
  hbox_weekly_buttons = gtk_hbox_new( FALSE, 2 );
  hbox_weekly_label = gtk_hbox_new( FALSE, 2 );

  /* labels */
  label_every = gtk_label_new( "Every" );
  label_weeks = gtk_label_new( "week(s)" );
  adj_weeks = gtk_adjustment_new( 1, 1, 100, 1, 4, 0 );
  spin_weeks = gtk_spin_button_new( GTK_ADJUSTMENT( adj_weeks ), 0.0, 0 );

  gtk_box_pack_start( GTK_BOX( hbox_weekly_label ), label_every, FALSE, FALSE, 2 );
  gtk_box_pack_start( GTK_BOX( hbox_weekly_label ), spin_weeks, FALSE, FALSE, 2 );
  gtk_box_pack_start( GTK_BOX( hbox_weekly_label ), label_weeks, FALSE, FALSE, 2 );

  /* hbox_weekly_checks */
  check_sun = gtk_check_button_new_with_label( "Sun" );
  check_mon = gtk_check_button_new_with_label( "Mon" );
  check_tue = gtk_check_button_new_with_label( "Tue" );
  check_wed = gtk_check_button_new_with_label( "Wed" );
  check_thu = gtk_check_button_new_with_label( "Thu" );
  check_fri = gtk_check_button_new_with_label( "Fri" );
  check_sat = gtk_check_button_new_with_label( "Sat" );

  gtk_box_pack_start( GTK_BOX( hbox_weekly_checks ), check_sun, FALSE, FALSE, 1 );
  gtk_box_pack_start( GTK_BOX( hbox_weekly_checks ), check_mon, FALSE, FALSE, 1 );
  gtk_box_pack_start( GTK_BOX( hbox_weekly_checks ), check_tue, FALSE, FALSE, 1 );
  gtk_box_pack_start( GTK_BOX( hbox_weekly_checks ), check_wed, FALSE, FALSE, 1 );
  gtk_box_pack_start( GTK_BOX( hbox_weekly_checks ), check_thu, FALSE, FALSE, 1 );
  gtk_box_pack_start( GTK_BOX( hbox_weekly_checks ), check_fri, FALSE, FALSE, 1 );
  gtk_box_pack_start( GTK_BOX( hbox_weekly_checks ), check_sat, FALSE, FALSE, 1 );

  /* hbox_weekly_buttons */
  button_weekdays = gtk_button_new_with_label( " Weekdays " );
  button_weekends = gtk_button_new_with_label( " Weekends " );
  button_clear = gtk_button_new_with_label( "   Clear   " );

  g_signal_connect( G_OBJECT( button_weekdays ), "clicked",
		      G_CALLBACK( cb_set_days ), GINT_TO_POINTER( 1 ) );
  g_signal_connect( G_OBJECT( button_weekends ), "clicked",
		      G_CALLBACK( cb_set_days ), GINT_TO_POINTER( 2 ) );
  g_signal_connect( G_OBJECT( button_clear ), "clicked",
		      G_CALLBACK( cb_set_days ), GINT_TO_POINTER( 0 ) );

  gtk_box_pack_start( GTK_BOX( hbox_weekly_buttons ), button_weekdays, FALSE, FALSE, 2 );
  gtk_box_pack_start( GTK_BOX( hbox_weekly_buttons ), button_weekends, FALSE, FALSE, 2 );
  gtk_box_pack_start( GTK_BOX( hbox_weekly_buttons ), button_clear, FALSE, FALSE, 2 );

  gtk_box_pack_end( GTK_BOX( hbox_weekly_label ), hbox_weekly_buttons, FALSE, FALSE, 2 );

  /* pack into vbox_weekly */
  gtk_box_pack_start( GTK_BOX( vbox_weekly ), hbox_weekly_label, FALSE, FALSE, 2 );
  gtk_box_pack_start( GTK_BOX( vbox_weekly ), hbox_weekly_checks, FALSE, FALSE, 2 );

  return vbox_weekly;
}

static GtkWidget *
create_calendar_monthly()
{
  GtkWidget *vbox_monthly;
  GtkWidget *hbox_top;
  GtkWidget *hbox_bottom;

  GtkWidget *label_every;
  GtkWidget *label_months;
  GtkWidget *label_repeat;
  GtkWidget *label_daymonth;
  GtkObject *adj_daymonth;
  GtkObject *adj_months;
  /*        *spin_months */
  /*        *spin_daymonth */

  vbox_monthly = gtk_vbox_new( FALSE, 2 );
  hbox_top = gtk_hbox_new( FALSE, 2 );
  hbox_bottom = gtk_hbox_new( FALSE, 2 );

  label_every = gtk_label_new( "Every" );
  label_months = gtk_label_new( "month(s)" );
  adj_months = gtk_adjustment_new( 1, 1, 12, 1, 3, 0 );
  spin_months = gtk_spin_button_new( GTK_ADJUSTMENT( adj_months ), 0.0, 0 );

  label_repeat = gtk_label_new( "Repeat on the" );
  adj_daymonth = gtk_adjustment_new( tm_input.tm_mday, 1, 31, 1, 10, 0 );
  spin_daymonth = gtk_spin_button_new( GTK_ADJUSTMENT( adj_daymonth ), 0.0, 0 );
  label_daymonth = gtk_label_new( "day of the month" );

  gtk_box_pack_start( GTK_BOX( hbox_top ), label_every, FALSE, FALSE, 2 );
  gtk_box_pack_start( GTK_BOX( hbox_top ), spin_months, FALSE, FALSE, 2 );
  gtk_box_pack_start( GTK_BOX( hbox_top ), label_months, FALSE, FALSE, 2 );

  gtk_box_pack_start( GTK_BOX( hbox_bottom ), label_repeat, FALSE, FALSE, 2 );
  gtk_box_pack_start( GTK_BOX( hbox_bottom ), spin_daymonth, FALSE, FALSE, 2 );
  gtk_box_pack_start( GTK_BOX( hbox_bottom ), label_daymonth, FALSE, FALSE, 2 );

  gtk_box_pack_start( GTK_BOX( vbox_monthly ), hbox_top, FALSE, FALSE, 2 );
  gtk_box_pack_start( GTK_BOX( vbox_monthly ), hbox_bottom, FALSE, FALSE, 2 );

  return vbox_monthly;
}

static GtkWidget *
create_calendar_details()
{
  GtkWidget *hbox_details;
  GtkWidget *vbox_occurs;
  GtkWidget *vbox_right;
  GtkWidget *vseparator;

  GtkWidget *hbox_daily;
  GtkWidget *hbox_weekly;
  GtkWidget *hbox_monthly;

  hbox_details = gtk_hbox_new( FALSE, 2 );
  vbox_occurs = create_calendar_occurs();
  vbox_right = gtk_vbox_new( FALSE, 2 );
  vseparator = gtk_vseparator_new();
  hbox_date = create_calendar_date();
  hbox_daily = create_calendar_daily();
  hbox_weekly = create_calendar_weekly();
  hbox_monthly = create_calendar_monthly();

  notebook_occurs = gtk_notebook_new();
  gtk_notebook_append_page( GTK_NOTEBOOK( notebook_occurs ), hbox_daily, NULL );
  gtk_notebook_append_page( GTK_NOTEBOOK( notebook_occurs ), hbox_weekly, NULL );
  gtk_notebook_append_page( GTK_NOTEBOOK( notebook_occurs ), hbox_monthly, NULL );
  gtk_notebook_set_show_tabs( GTK_NOTEBOOK( notebook_occurs ), FALSE );
  gtk_notebook_set_show_border( GTK_NOTEBOOK( notebook_occurs ), FALSE );

  gtk_box_pack_start( GTK_BOX( vbox_right ), notebook_occurs, TRUE, TRUE, 2 );
  gtk_box_pack_start( GTK_BOX( vbox_right ), hbox_date, FALSE, FALSE, 2 );

  gtk_box_pack_start( GTK_BOX( hbox_details ), vbox_occurs, FALSE, FALSE, 2 );
  gtk_box_pack_start( GTK_BOX( hbox_details ), vseparator, FALSE, FALSE, 2 );
  gtk_box_pack_start( GTK_BOX( hbox_details ), vbox_right, TRUE, TRUE, 2 );

  return hbox_details;
}

static void
create_calendar_frame( GtkWidget *frame )
{
  GtkWidget *separator;
  GtkWidget *separator2;

  GtkWidget *vbox_main;

  GtkWidget *hbox_event;
  GtkWidget *hbox_time;
  GtkWidget *hbox_details;
  GtkWidget *hbox_ops;
  GtkWidget *hbox_list;

  /* Get time from gkrellm and copy it to our struct so it can be modified */
  memcpy( &tm_input, gkrellm_get_current_time(), sizeof( tm_input ) );

  /* separators */
  separator = gtk_hseparator_new();
  separator2 = gtk_hseparator_new();

  /* Initialize boxes */
  vbox_main = gtk_vbox_new( FALSE, 0 );

  hbox_event = create_calendar_event();
  hbox_time = create_calendar_time();
  hbox_details = create_calendar_details();
  hbox_ops = create_calendar_ops();
  hbox_list = create_calendar_list();

  /* pack time */
  gtk_box_pack_end( GTK_BOX( hbox_event ), hbox_time, FALSE, FALSE, 2 );

  /* containers */
  gtk_container_add( GTK_CONTAINER( frame ), vbox_main );

  gtk_box_pack_start( GTK_BOX( vbox_main ), hbox_event, FALSE, FALSE, 2 );
  gtk_box_pack_start( GTK_BOX( vbox_main ), separator, FALSE, FALSE, 2 );
  gtk_box_pack_start( GTK_BOX( vbox_main ), hbox_details, FALSE, FALSE, 2 );
  gtk_box_pack_start( GTK_BOX( vbox_main ), separator2, FALSE, FALSE, 2 );
  gtk_box_pack_start( GTK_BOX( vbox_main ), hbox_ops, FALSE, FALSE, 2 );
  gtk_box_pack_start( GTK_BOX( vbox_main ), hbox_list, TRUE, TRUE, 2 );

  cb_reorder_date();
  cb_reset( frame, NULL );
  occurs_option = -1;
  cb_select_radio( frame, NULL );

  /* Populate list */
  if( !head_stored )
    reminder_load_stored();
  cb_populate();

  gtk_widget_show_all( frame );
}

static void
create_settings_frame( GtkWidget *tab )
{
  GtkWidget *label_remind_early;
  GtkWidget *label_remind_early_end;
  GtkWidget *label_alert;
  GtkWidget *label_time_format;
  GtkWidget *label_date_format;
  GtkWidget *label_notify;

  GtkWidget *vbox_main;

  GtkWidget *hbox_remind_early;
  GtkWidget *hbox_remind_old;
  GtkWidget *hbox_delete_old;
  GtkWidget *hbox_alert;
  GtkWidget *hbox_time_format;
  GtkWidget *hbox_date_format;
  GtkWidget *hbox_notify;

  GtkObject *adj_remind_early;

  vbox_main = gtk_vbox_new( TRUE, 2 );

  /* remind early */
  hbox_remind_early = gtk_hbox_new( FALSE, 2 );

  label_remind_early = gtk_label_new( "Remind me about events" );
  label_remind_early_end = gtk_label_new( "minutes early" );

  adj_remind_early = gtk_adjustment_new( config.remind_early, 0, 120, 1, 10, 0 );
  spin_remind_early = gtk_spin_button_new( GTK_ADJUSTMENT( adj_remind_early ), 0.0, 0 );
  gtk_spin_button_set_numeric( GTK_SPIN_BUTTON( spin_remind_early ), TRUE );

  gtk_box_pack_start( GTK_BOX( hbox_remind_early ), label_remind_early, FALSE, FALSE, 2 );
  gtk_box_pack_start( GTK_BOX( hbox_remind_early ), spin_remind_early, FALSE, FALSE, 2 );
  gtk_box_pack_start( GTK_BOX( hbox_remind_early ), label_remind_early_end, FALSE, FALSE, 2 );

  /* remind old */
  hbox_remind_old = gtk_hbox_new( FALSE, 2 );

  check_remind_old = gtk_check_button_new_with_label( "Remind of events that I may have missed today" );
  gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( check_remind_old ), config.remind_old );

  gtk_box_pack_start( GTK_BOX( hbox_remind_old ), check_remind_old, FALSE, FALSE, 2 );

  /* delete old */
  hbox_delete_old = gtk_hbox_new( FALSE, 2 );

  check_delete_old = gtk_check_button_new_with_label( "Automatically delete events that have expired" );
  gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( check_delete_old ), config.delete_old );

  gtk_box_pack_start( GTK_BOX( hbox_delete_old ), check_delete_old, FALSE, FALSE, 2 );

  /* AM/PM */
  hbox_time_format = gtk_hbox_new( FALSE, 2 );

  label_time_format = gtk_label_new( "Time format:" );
  radio_12hour = gtk_radio_button_new_with_label( NULL, "12-hour" );
  radio_24hour = gtk_radio_button_new_with_label(
		    gtk_radio_button_group( GTK_RADIO_BUTTON( radio_12hour ) ),
		    "24-hour" );

  if( config.ampm )
    gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( radio_12hour ), TRUE );
  else
    gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( radio_24hour ), TRUE );

  gtk_box_pack_start( GTK_BOX( hbox_time_format ), label_time_format, FALSE, FALSE, 2 );
  gtk_box_pack_start( GTK_BOX( hbox_time_format ), radio_12hour, TRUE, FALSE, 2 );
  gtk_box_pack_start( GTK_BOX( hbox_time_format ), radio_24hour, TRUE, FALSE, 2 );

  /* date format */
  hbox_date_format = gtk_hbox_new( FALSE, 2 );

  label_date_format = gtk_label_new( "Date format:" );
  radio_mdy = gtk_radio_button_new_with_label( NULL, "MM/DD/YYYY" );
  radio_dmy = gtk_radio_button_new_with_label(
		    gtk_radio_button_group( GTK_RADIO_BUTTON( radio_mdy ) ),
		    "DD/MM/YYYY" );

  if( config.mdy )
    gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( radio_mdy ), TRUE );
  else
    gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( radio_dmy ), TRUE );

  gtk_box_pack_start( GTK_BOX( hbox_date_format ), label_date_format, FALSE, FALSE, 2 );
  gtk_box_pack_start( GTK_BOX( hbox_date_format ), radio_mdy, TRUE, FALSE, 2 );
  gtk_box_pack_start( GTK_BOX( hbox_date_format ), radio_dmy, TRUE, FALSE, 2 );

  /* alert */
  hbox_alert = gtk_hbox_new( FALSE, 2 );

  label_alert = gtk_label_new( "Reminder method:" );

  check_alert_flash = gtk_check_button_new_with_label( "Flash icon" );
  check_alert_popup = gtk_check_button_new_with_label( "Popup reminder" );
  check_alert_execute = gtk_check_button_new_with_label( "Execute command" );

  gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( check_alert_flash ), config.alert & ALERT_FLASH );
  gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( check_alert_popup ), config.alert & ALERT_POPUP );
  gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( check_alert_execute ), config.alert & ALERT_EXECUTE );

  gtk_box_pack_start( GTK_BOX( hbox_alert ), label_alert, FALSE, FALSE, 2 );
  gtk_box_pack_start( GTK_BOX( hbox_alert ), check_alert_flash, TRUE, TRUE, 2 );
  gtk_box_pack_start( GTK_BOX( hbox_alert ), check_alert_popup, TRUE, TRUE, 2 );
  gtk_box_pack_start( GTK_BOX( hbox_alert ), check_alert_execute, TRUE, TRUE, 2 );

  /* notify */
  hbox_notify = gtk_hbox_new( FALSE, 2 );

  label_notify = gtk_label_new( "Notification (play sound) command:" );
  entry_notify = gtk_entry_new_with_max_length( 63 );
  if( config.notify )
    gtk_entry_set_text( GTK_ENTRY( entry_notify ), config.notify );

  gtk_box_pack_start( GTK_BOX( hbox_notify ), label_notify, FALSE, FALSE, 2 );
  gtk_box_pack_start( GTK_BOX( hbox_notify ), entry_notify, TRUE, TRUE, 2 );

  /* main */
  gtk_box_pack_start( GTK_BOX( vbox_main ), hbox_remind_early, FALSE, FALSE, 2 );
  gtk_box_pack_start( GTK_BOX( vbox_main ), hbox_remind_old, FALSE, FALSE, 2 );
  gtk_box_pack_start( GTK_BOX( vbox_main ), hbox_delete_old, FALSE, FALSE, 2 );
  gtk_box_pack_start( GTK_BOX( vbox_main ), hbox_time_format, FALSE, FALSE, 2 );
  gtk_box_pack_start( GTK_BOX( vbox_main ), hbox_date_format, FALSE, FALSE, 2 );
  gtk_box_pack_start( GTK_BOX( vbox_main ), hbox_alert, FALSE, FALSE, 2 );
  gtk_box_pack_start( GTK_BOX( vbox_main ), hbox_notify, FALSE, FALSE, 2 );

  gtk_widget_show_all( vbox_main );

  gtk_container_add( GTK_CONTAINER( tab ), vbox_main );
}

static void
create_help_frame( GtkWidget *tab )
{
  GtkWidget *vbox_main;

  GtkWidget *text_main;

  static gchar *str_info[] = {
    "<h>Calendar\n",
    "<i>Event: ", "Name of event. This will be displayed when you are reminded.\n\n",
    "<i>Daily: ", "This will remind you every <n> days starting with the start date. If the event\n",
    "does not occur on the end date, the actual ending date may be several days earlier\n",
    "than is indicated. ", "<b>Note: ", "Setting <n> to 1 will display the event every day in the\n",
    "Start/End range.\n\n",
    "<i>Weekly: ", "This will remind you of the event at the same time every week. You can\n",
    "select which days you would like to be reminded on with the check boxes and buttons.\n\n",
    "<i>Monthly: ", "This will remind you at the same time each month. Months can be skipped by\n",
    "setting the repeat field. Setting this to 12 will result in a yearly reminder.\n\n",
    "<i>Start/End/Time: ", "This selects the range of days on which the event may occur. The\n",
    "start and end dates are inclusive. Events will expire at the stroke of midnight on the\n",
    "day after end date. Time indicates what time of the day the event will appear.\n\n",
    "<i>Add: ", "Adds the current event to the list.\n",
    "<i>Remove: ", "Removes the currently selected event in the list.\n",
    "<i>Update: ", "Replaces the currently selected list item with the current event.\n",
    "<i>Today: ", "Resets the Start/End/Time to the current time and date.\n\n",
    "<h>Settings\n",
    "<i>Remind Early: ", "Reminders will appear <n> minutes before they are scheduled.\n",
    "<i>Remind Missed: ", "Reminders that happened before GKrellM was started will appear\n",
    "at startup.\n",
    "<i>Delete Expired: ", "Events that will never occur again (after end date) will automatically\n",
    "be deleted.\n",
    "<i>Time Format: ", "Select either 12 hour (with AM/PM) or 24-hour time display\n",
    "<i>Date Format: ", "The format to use when displaying dates.\n",
    "<i>Notification Command: ", "This command will be executed whenever a new event occurs\n\n",
    "<h>Reminders\n",
    "<i>Never: ", "Deletes the event from the calendar. It will never appear again.\n",
    "<i>Later: ", "Will display this reminder again in <n> minutes.\n",
    "<i>Dismiss: ", "Acknowledges the reminder. It will not be displayed again today.\n\n",
    "<h>Panel\n",
    "The first number indicates the number of events pending. The second number\n",
    "indicates the total number of events for today. The calendar will also flash when\n",
    "there are pending events.\n\n",
    "<i>Left mouse button: ", "Clicking on the numbers will display the first pending event.\n",
    "Clicking on the calendar will list all events scheduled for today.\n",
    "<i>Right mouse button: ", "Clicking anywhere within the panel will display the calendar."
  };

  vbox_main = gtk_vbox_new( TRUE, 2 );

  text_main = gkrellm_gtk_scrolled_text_view( vbox_main, NULL, GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC );
//  gkrellm_add_info_text( text_main, str_info, 59 );
  gkrellm_gtk_text_view_append_strings(text_main, str_info, 59);

  gtk_widget_show_all( vbox_main );

  gtk_container_add( GTK_CONTAINER( tab ), vbox_main );
}

static void
create_about_frame( GtkWidget *tab )
{
  GtkWidget *vbox_main;

  GtkWidget *label_info;

  gchar *str_info;

  vbox_main = gtk_vbox_new( TRUE, 2 );

  str_info = g_strdup_printf( "%s %s\n%s\n\n%s %s\n%s\n%s\n\n%s",
			      str_title, str_version,
			      str_date,
			      str_copyright, str_author,
			      str_email,
			      str_url,
			      str_gpl );

  label_info = gtk_label_new( str_info );
  gtk_box_pack_start( GTK_BOX( vbox_main ), label_info, TRUE, TRUE, 2 );

  g_free( str_info );

  gtk_widget_show_all( GTK_WIDGET( vbox_main ) );

  gtk_container_add( GTK_CONTAINER( tab ), vbox_main );
}

static void
display_config( GtkWidget *tab )
{
  GtkWidget *notebook;

  GtkWidget *label_calendar;
  GtkWidget *label_settings;
  GtkWidget *label_help;
  GtkWidget *label_about;

  GtkWidget *frame_calendar;
  GtkWidget *frame_settings;
  GtkWidget *frame_help;
  GtkWidget *frame_about;

  /* Delete any previously unsaved changes to the settings */
  reminder_free_id_list();
  if( head_temp )
    reminder_free_stored( &head_temp );

  notebook = gtk_notebook_new();
  gtk_notebook_set_tab_pos( GTK_NOTEBOOK( notebook ), GTK_POS_TOP );

  label_calendar = gtk_label_new( "Calendar" );
  frame_calendar = gtk_frame_new( NULL );
  gtk_container_border_width( GTK_CONTAINER( frame_calendar ), 3 );
  create_calendar_frame( frame_calendar );
  gtk_notebook_append_page( GTK_NOTEBOOK( notebook ), frame_calendar, label_calendar );

  label_settings = gtk_label_new( "Settings" );
  frame_settings = gtk_frame_new( NULL );
  gtk_container_border_width( GTK_CONTAINER( frame_settings ), 3 );
  create_settings_frame( frame_settings );
  gtk_notebook_append_page( GTK_NOTEBOOK( notebook ), frame_settings, label_settings );

  label_help = gtk_label_new( "Help" );
  frame_help = gtk_frame_new( NULL );
  gtk_container_border_width( GTK_CONTAINER( frame_help ), 3 );
  create_help_frame( frame_help );
  gtk_notebook_append_page( GTK_NOTEBOOK( notebook ), frame_help, label_help );

  label_about = gtk_label_new( "About" );
  frame_about = gtk_frame_new( NULL );
  gtk_container_border_width( GTK_CONTAINER( frame_about ), 3 );
  create_about_frame( frame_about );
  gtk_notebook_append_page( GTK_NOTEBOOK( notebook ), frame_about, label_about );

  gtk_widget_show_all( notebook );

  gtk_box_pack_start( GTK_BOX( tab ), notebook, TRUE, TRUE, 0 );
}

static void
update_config(void)
{
  struct id_list *dl_current;
  struct event_stored *es_tail;

  /* settings */
  config.remind_early_diff = config.remind_early;
  config.remind_early = gtk_spin_button_get_value_as_int( GTK_SPIN_BUTTON( spin_remind_early ) );
  config.remind_early_diff -= config.remind_early;
  config.remind_old = gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON( check_remind_old ) );
  config.delete_old = gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON( check_delete_old ) );
  config.alert = 0;
  config.alert |= gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON( check_alert_flash ) ) ? ALERT_FLASH : 0;
  config.alert |= gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON( check_alert_popup ) ) ? ALERT_POPUP : 0;
  config.alert |= gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON( check_alert_execute ) ) ? ALERT_EXECUTE : 0;

  config.ampm = gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON( radio_12hour ) );
  if( !config.ampm )
    {
      GTK_ADJUSTMENT( adj_time_hour )->lower = 0;
      GTK_ADJUSTMENT( adj_time_hour )->upper = 23;

      gtk_spin_button_update( GTK_SPIN_BUTTON( spin_time_hour ) );

      gtk_widget_set_sensitive( button_ampm, FALSE );
    }
  else
    {
      GTK_ADJUSTMENT( adj_time_hour )->lower = 1;
      GTK_ADJUSTMENT( adj_time_hour )->upper = 12;

      gtk_spin_button_update( GTK_SPIN_BUTTON( spin_time_hour ) );

      gtk_widget_set_sensitive( button_ampm, TRUE );
    }

  config.mdy = gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON( radio_mdy ) );
  cb_reorder_date();

  if( !strlen( gtk_entry_get_text( GTK_ENTRY( entry_notify ) ) ) )
    {
      g_free( config.notify );
      config.notify = g_strdup(str_null);
    }
  else if( !config.notify || strcmp( config.notify, gtk_entry_get_text( GTK_ENTRY( entry_notify ) ) ) )
    {
      if( config.notify )
	g_free( config.notify );

//      config.notify = malloc( strlen( gtk_entry_get_text( GTK_ENTRY( entry_notify ) ) ) + 1 );
//      if( !config.notify )
//	return;

//      strcpy( config.notify, gtk_entry_get_text( GTK_ENTRY( entry_notify ) ) );
      config.notify = g_strdup(gtk_entry_get_text( GTK_ENTRY( entry_notify ) ) );
    }

  /* Make sure that the list is loaded (fixes apply/ok bug) */
  if( !head_stored )
    reminder_load_stored();

  /* Remove anything deleted from the list */
  dl_current = head_delete;
  while( dl_current )
    {
      reminder_remove_event_stored( &head_stored, dl_current->id );
      reminder_remove_event_today( dl_current->id );

      dl_current = dl_current->next;
    }
  reminder_free_id_list();

  /* Add anything added to the list */
  if( head_temp )
    {
      if( head_stored )
	{
	  es_tail = head_stored;
	  while( es_tail->next )
	    es_tail = es_tail->next;

	  es_tail->next = head_temp;
	}
      else
	head_stored = head_temp;
    }
  head_temp = NULL;

  /* Save changes to disk and update today's list */
  reminder_build_today( TRUE );

  /* repopulate the list */
  cb_populate();
}

static void
default_config()
{
  config.remind_early = 15;
  config.remind_early_diff = 0;
  config.list_sort = 2;
  config.alert = ALERT_FLASH;
  config.remind_old = TRUE;
  config.delete_old = FALSE;
  config.ampm = TRUE;
  config.mdy = TRUE;

//  config.event_file = malloc( strlen( gkrellm_homedir() ) + 29 );
//  if( !config.event_file )
//    return;
  //sprintf( config.event_file, "%s/.gkrellm-reminder/event.dat", gkrellm_homedir() );
  config.event_file = g_strdup_printf("%s/.gkrellm-reminder/event.dat", gkrellm_homedir() );
}

static void
save_config( FILE *fp )
{
  fprintf( fp, "%s remind_early %d\n", CONFIG_NAME, config.remind_early );
  fprintf( fp, "%s list_sort %d\n", CONFIG_NAME, config.list_sort );
  fprintf( fp, "%s remind_old %d\n", CONFIG_NAME, config.remind_old );
  fprintf( fp, "%s delete_old %d\n", CONFIG_NAME, config.delete_old );
  fprintf( fp, "%s ampm %d\n", CONFIG_NAME, config.ampm );
  fprintf( fp, "%s mdy %d\n", CONFIG_NAME, config.mdy );
  fprintf( fp, "%s alert %d\n", CONFIG_NAME, config.alert );
  if (config.notify && strcmp(config.notify, str_null))
    fprintf( fp, "%s notify %s\n", CONFIG_NAME, config.notify ? config.notify : str_null );
}

static void
load_config( gchar *arg )
{
  gchar keyword[32], value[64];

  sscanf( arg, "%s %[^\n]", keyword, value );

  if( !strcmp( keyword, "remind_early" ) )
    config.remind_early = atoi( value );

  else if( !strcmp( keyword, "list_sort" ) )
    config.list_sort = atoi( value );

  else if( !strcmp( keyword, "remind_old" ) )
    config.remind_old = atoi( value );

  else if( !strcmp( keyword, "delete_old" ) )
    config.delete_old = atoi( value );

  else if( !strcmp( keyword, "ampm" ) )
    config.ampm = atoi( value );

  else if( !strcmp( keyword, "mdy" ) )
    config.mdy = atoi( value );

  else if( !strcmp( keyword, "alert" ) )
    config.alert = atoi( value );

  else if( !strcmp( keyword, "notify" ) )
    {
      if( config.notify )
	   g_free( config.notify );

      if( strcmp( value, str_null ) )
      {
//	config.notify = malloc( strlen( value ) + 1 );
//	if( !config.notify )
//	  return;

//	strcpy( config.notify, value );
          config.notify = g_strdup(value);
      }
    }
}

static void
cb_today_delete( GtkWidget *window, GdkEvent *event, gpointer data )
{
  gtk_widget_destroy( window_today );

  window_today = NULL;
}

static void
reminder_display_today( void )
{
  GtkWidget *vbox_main;

  GtkWidget *list_today;
  GtkWidget *scroll_today;

  GtkWidget *separator;
  GtkWidget *button_close;

  static gchar *list_titles[] = { "Time", "Event" };

  struct event_today *current;

  if( window_today )
    return;

  window_today = gtk_window_new( GTK_WINDOW_TOPLEVEL );
  gtk_window_set_policy( GTK_WINDOW( window_today ), TRUE, TRUE, FALSE );
  gtk_window_set_title( GTK_WINDOW( window_today ), str_title );
  gtk_widget_set_usize( window_today, 200, 200 );
  g_signal_connect( G_OBJECT( window_today ), "delete_event",
		      G_CALLBACK(cb_today_delete), NULL );

  vbox_main = gtk_vbox_new( FALSE, 5 );
  gtk_container_add( GTK_CONTAINER( window_today ), vbox_main );

  scroll_today = gtk_scrolled_window_new( NULL, NULL );
  list_today = gtk_clist_new_with_titles( 2, list_titles );

  gtk_clist_set_selection_mode( GTK_CLIST( list_today ), GTK_SELECTION_SINGLE );
  gtk_clist_column_titles_active( GTK_CLIST( list_today ) );

  current = head_today;
  while( current )
    {
      gchar *array[] = { NULL, NULL, };
      time_t time_current;

      array[0] = malloc( 9 );

      if( !array[0] )
	return;

      time_current = current->occurs;
      if( !strstr( current->name, str_delayed ) )
	time_current += config.remind_early * SECS_PER_MIN;

      if( config.ampm )
	strftime( array[0], 9, "%I:%M %p", localtime( &time_current ) );
      else
	strftime( array[0], 9, "%H:%M", localtime( &time_current ) );

      array[1] = current->name;

      gtk_clist_append( GTK_CLIST( list_today ), array );

      if( array[0] )
	free( array[0] );

      current = current->next;
    }

  gtk_clist_columns_autosize( GTK_CLIST( list_today ) );
  gtk_container_add( GTK_CONTAINER( scroll_today ), list_today );

  separator = gtk_hseparator_new();

  button_close = gtk_button_new_with_label( "Close" );
  g_signal_connect_swapped( G_OBJECT( button_close ), "clicked",
			     G_CALLBACK(cb_today_delete), GTK_OBJECT( window_today ) );

  gtk_box_pack_start( GTK_BOX( vbox_main ), scroll_today, TRUE, TRUE, 2 );
  gtk_box_pack_start( GTK_BOX( vbox_main ), separator, FALSE, FALSE, 2 );
  gtk_box_pack_end( GTK_BOX( vbox_main ), button_close, FALSE, FALSE, 2 );

  gtk_widget_show_all( window_today );
}

static void
reminder_text_button_enable( void )
{
  if( window_reminder )
    reminder_text_button->sensitive = FALSE;
  else
    reminder_text_button->sensitive = TRUE;
}

static void
reminder_window_never( GtkWidget *window, gpointer data )
{
  guint id = (guint) data;

  /* delete event from today */
  num_active--;
  num_today--;
  if( num_active )
    last_active = head_today->next;
  else
    last_active = NULL;
  reminder_remove_event_today( id );

  if( !head_stored )
    reminder_load_stored();
  reminder_remove_event_stored( &head_stored, id );
  reminder_save_stored();
  reminder_free_stored( &head_stored );

  gtk_widget_destroy( window_reminder );

  window_reminder = NULL;

  reminder_text_button_enable();

  if( num_active && config.alert & ALERT_POPUP )
    reminder_display_reminder();
}

static void
reminder_window_later( GtkWidget *window, gpointer data )
{
  guint id = (guint) data;
  struct event_stored *new, *old;

  /* delete event from today */
  num_active--;
  num_today--;
  if( num_active )
    last_active = head_today->next;
  else
    last_active = NULL;
  reminder_remove_event_today( id );

  if( event_active.last && config.delete_old )
    {
      if( !head_stored )
	reminder_load_stored();
      reminder_remove_event_stored( &head_stored, id );
    }

  new = malloc( sizeof( struct event_stored ) );
  if( new )
    {
      if( !strstr( event_active.name, str_delayed ) )
	{
//	  new->name = malloc( strlen( event_active.name ) + 1 + 10 );
//	  strcpy( new->name, str_delayed );
//	  strcpy( new->name + 10, event_active.name );
      new->name = g_strdup_printf("%10s%s", str_delayed, event_active.name);
	}
      else
	{
//	  new->name = malloc( strlen( event_active.name ) + 1 );
//	  strcpy( new->name, event_active.name );
      new->name = g_strdup(event_active.name);

	  if( !head_stored )
	    reminder_load_stored();
	  reminder_remove_event_stored( &head_stored, id );
	}

      new->id = event_active.id - 1000 * SECS_PER_DAY;
      new->last_displayed = 0;

      new->start = mktime( gkrellm_get_current_time() ) + config.remind_early * SECS_PER_MIN +
	gtk_spin_button_get_value_as_int( GTK_SPIN_BUTTON( spin_minutes ) ) * SECS_PER_MIN;

      new->start = new->start - new->start % SECS_PER_MIN;

      new->end = new->start + ( SECS_PER_DAY - ( new->start - TIMEZONE_DIFF ) % SECS_PER_DAY ) - 1;

      new->occurs = OPT_DAILY;
      new->days = 1;

      if( !head_stored )
	reminder_load_stored();

      old = reminder_find_event_stored( head_stored, id );
      if( old )
	old->last_displayed = mktime( gkrellm_get_current_time() );

      reminder_add_event_stored( &head_stored, new, NULL );
      reminder_build_today( TRUE );
    }

  gtk_widget_destroy( window_reminder );

  window_reminder = NULL;

  reminder_text_button_enable();

  if( num_active && config.alert & ALERT_POPUP )
    reminder_display_reminder();
}

static void
reminder_window_dismiss( GtkWidget *window, gpointer data )
{
  guint id = (guint) data;

  /* delete event from today */
  num_active--;
  num_today--;
  if( num_active )
    last_active = head_today->next;
  else
    last_active = NULL;
  reminder_remove_event_today( id );

  if( event_active.last && config.delete_old )
    {
      if( !head_stored )
	reminder_load_stored();
      reminder_remove_event_stored( &head_stored, id );
      reminder_save_stored();
    }
  else
    {
      struct event_stored *current;

      if( !head_stored )
	reminder_load_stored();
      current = reminder_find_event_stored( head_stored, id );
      current->last_displayed = mktime( gkrellm_get_current_time() );
      reminder_save_stored();
    }

  gtk_widget_destroy( window_reminder );

  window_reminder = NULL;

  reminder_text_button_enable();

  if( num_active && config.alert & ALERT_POPUP )
    reminder_display_reminder();
}

static guint
reminder_get_active()
{
  if( last_active )
    {
      memcpy( &event_active, head_today, sizeof( struct event_today ) );

//      event_active.name = malloc( strlen( head_today->name ) + 1 );
//      if( !event_active.name )
//	return 0;

//      strcpy( event_active.name, head_today->name );
      event_active.name = g_strdup(head_today->name);

      if( !strstr( event_active.name, str_delayed ) )
	event_active.occurs += config.remind_early * SECS_PER_MIN;

      return event_active.id;
    }
  else
    return 0;
}

static void
cb_reminder_delete( GtkWidget *widget, GdkEvent *event, gpointer user_data )
{
  gtk_widget_destroy( window_reminder );
  window_reminder = NULL;

  reminder_text_button_enable();
}

static void
reminder_display_reminder( void )
{
  guint id_active;

  GtkWidget *vbox_main;
  GtkWidget *hbox_later;
  GtkWidget *hbox_buttons;
  GtkWidget *label_time;
  GtkWidget *label_event;
  GtkWidget *label_remind;
  GtkWidget *label_minutes;
  GtkWidget *separator;
  GtkWidget *button_dismiss;
  GtkWidget *button_later;
  GtkWidget *button_never;

  GtkObject *adj_minutes;

  struct tm tm_active;
  gchar str_time[27];
  int length;

  if( window_reminder )
    {
      gtk_window_activate_focus( GTK_WINDOW( window_reminder ) );
      return;
    }

  id_active = reminder_get_active();

  if( !id_active )
    return;

  window_reminder = gtk_window_new( GTK_WINDOW_TOPLEVEL );
  gtk_window_set_policy( GTK_WINDOW( window_reminder ), TRUE, TRUE, FALSE );
  gtk_window_set_title( GTK_WINDOW( window_reminder ), str_title );
  g_signal_connect( G_OBJECT( window_reminder ), "delete-event",
		      G_CALLBACK( cb_reminder_delete ), NULL );

  vbox_main = gtk_vbox_new( FALSE, 5 );
  gtk_container_add( GTK_CONTAINER( window_reminder ), vbox_main );

  memcpy( &tm_active, localtime( &event_active.occurs ), sizeof( tm_active ) );
  if( config.ampm )
    length = strftime( str_time, 27, str_12hour, &tm_active );
  else
    length = strftime( str_time, 27, str_24hour, &tm_active );
  str_time[length] = ' ';
  strftime( str_time + length + 1, 27 - length - 2, config.mdy ? str_mdy : str_dmy, &tm_active );

  label_time = gtk_label_new( str_time );
  label_event = gtk_label_new( event_active.name );
  gtk_box_pack_start( GTK_BOX( vbox_main ), label_time, FALSE, FALSE, 2 );
  gtk_box_pack_start( GTK_BOX( vbox_main ), label_event, FALSE, FALSE, 2 );

  /* separator */
  separator = gtk_hseparator_new();
  gtk_box_pack_start( GTK_BOX( vbox_main ), separator, FALSE, FALSE, 4 );

  /* hbox_later */
  hbox_later = gtk_hbox_new( FALSE, 2 );
  gtk_box_pack_start( GTK_BOX( vbox_main ), hbox_later, FALSE, FALSE, 2 );

  label_remind = gtk_label_new( "Remind me again in" );
  label_minutes = gtk_label_new( "minutes" );
  adj_minutes = gtk_adjustment_new( 5, 1, 999, 1, 10, 0 );
  spin_minutes = gtk_spin_button_new( GTK_ADJUSTMENT( adj_minutes ), 0.0, 0 );
  gtk_spin_button_set_numeric( GTK_SPIN_BUTTON( spin_minutes ), TRUE );

  gtk_box_pack_start( GTK_BOX( hbox_later ), label_remind, FALSE, FALSE, 2 );
  gtk_box_pack_start( GTK_BOX( hbox_later ), spin_minutes, FALSE, FALSE, 2 );
  gtk_box_pack_start( GTK_BOX( hbox_later ), label_minutes, FALSE, FALSE, 2 );

  /* hbox_buttons */
  hbox_buttons = gtk_hbox_new( TRUE, 2 );
  gtk_box_pack_start( GTK_BOX( vbox_main ), hbox_buttons, FALSE, FALSE, 2 );

  button_never = gtk_button_new_with_label( " Never " );
  button_later = gtk_button_new_with_label( " Later " );
  button_dismiss = gtk_button_new_with_label( " Dismiss " );

  g_signal_connect( G_OBJECT( button_never ), "clicked",
		      G_CALLBACK(reminder_window_never),
		      GINT_TO_POINTER( head_today->id ) );
  g_signal_connect( G_OBJECT( button_later ), "clicked",
		      G_CALLBACK(reminder_window_later),
		      GINT_TO_POINTER( head_today->id ) );
  g_signal_connect( G_OBJECT( button_dismiss ), "clicked",
		      G_CALLBACK(reminder_window_dismiss),
		      GINT_TO_POINTER( head_today->id ) );

  gtk_box_pack_start( GTK_BOX( hbox_buttons ), button_never, FALSE, FALSE, 0 );
  gtk_box_pack_start( GTK_BOX( hbox_buttons ), button_later, FALSE, FALSE, 0 );
  gtk_box_pack_start( GTK_BOX( hbox_buttons ), button_dismiss, FALSE, FALSE, 0 );

  gtk_widget_show_all( window_reminder );

  reminder_text_button_enable();
}

static void
reminder_draw_panel_text( gint num_active, gint num_today )
{
  GkrellmStyle *style;
  GkrellmTextstyle ts, ts_old;
  gint new_value;
  gint x, w;
  gchar buf[16];

  new_value = ( num_active << 16 ) + num_today;

  if( new_value == reminder_text_decal->value )
    return;

  snprintf( buf, sizeof( buf ), "%d/%d", num_active, num_today );

  ts_old = ts = reminder_text_decal->text_style;

  w = gdk_string_width( ts.font, buf );

  if( w > reminder_text_decal->w )
    {
      ts.font = gkrellm_meter_alt_textstyle( style_id )->font;
      w = gdk_string_width( ts.font, buf );
    }

  style = gkrellm_meter_style( style_id );
  x = gkrellm_chart_width() * panel->label->position / GKRELLM_LABEL_MAX;
  x -= style->margin.left + w / 2;

  if ( panel->label->position >= 50 )
    x -= reminder_icon_decal->w / 2;
  if ( x > reminder_text_decal->w - w )
    x = reminder_text_decal->w - w;
  if ( x < 0 )
    x = 0;

  reminder_text_decal->text_style = ts;
  reminder_text_decal->x_off = x;
  gkrellm_draw_decal_text( panel, reminder_text_decal, buf, new_value );
  reminder_text_decal->text_style = ts_old;
}

static void
update_plugin(void)
{
  static gint frame = 0;

  if( pGK->day_tick )
    {
      if( !head_stored )
	reminder_load_stored();

      reminder_build_today( FALSE );
    }
  if( pGK->minute_tick )
    {
      time_t now;

      now = mktime( gkrellm_get_current_time() );

      reminder_check_new_active( head_today, last_active, now );
    }
  if( ( pGK->timer_ticks % 2 ) == 0 )
    {
      if( config.alert & ALERT_FLASH )
	{
	  if( num_active )
	    frame = frame ? 0 : 1;
	  else
	    frame = 0;
	}
      else
	frame = 0;

      gkrellm_draw_decal_pixmap( panel, DECAL( panel ), frame );
      reminder_draw_panel_text( num_active, num_today );
      gkrellm_draw_panel_layers( panel );
    }
}

static gint
panel_expose_event(GtkWidget *widget, GdkEventExpose *ev)
{
  gdk_draw_pixmap(widget->window,
		  widget->style->fg_gc[GTK_WIDGET_STATE (widget)],
		  panel->pixmap, ev->area.x, ev->area.y,
		  ev->area.x, ev->area.y,
		  ev->area.width, ev->area.height);
  return FALSE;
}

static gint
cb_panel_press( GtkWidget * widget, GdkEventButton * ev )
{
  if ( ev->button == 1 && ev->x >= reminder_icon_decal->x &&
       ev->x < reminder_icon_decal->x + reminder_icon_decal->w )
    reminder_display_today();
  else if( ev->button == 3 )
    gkrellm_open_config_window( reminder_mon );

  return TRUE;
}

static void
cb_reminder_button( GkrellmDecalbutton *button )
{
  if( num_active )
    reminder_display_reminder();
}

static void
create_plugin(GtkWidget *vbox, gint first_create)
{
  GkrellmStyle	*style;
  gint  x, w;
#if !defined(GKRELLM_HAVE_THEME_SCALE)
  static GdkPixmap *reminder_icon_pixmap;
  static GdkBitmap *reminder_icon_mask;
#endif

  if (first_create)
    panel = gkrellm_panel_new0();
  else
    gkrellm_destroy_decal_list( panel );

  style = gkrellm_meter_style(style_id);

  /* Load icon image */
  gkrellm_load_piximage( NULL, calendar_xpm, &reminder_icon_image, STYLE_NAME);

#if defined(GKRELLM_HAVE_THEME_SCALE)
  reminder_icon_decal = gkrellm_make_scaled_decal_pixmap(panel,
							reminder_icon_image,
							style, 2, -1, -1, 0, 0);
#else
  gkrellm_scale_piximage_to_pixmap(reminder_icon_image, &reminder_icon_pixmap,
      &reminder_icon_mask, 0, 0);
  reminder_icon_decal = gkrellm_create_decal_pixmap( panel,
						     reminder_icon_pixmap, reminder_icon_mask,
						     2, style, -1, -1 );
#endif

  /* Setup text decal for numbers */
  x = style->margin.left;
  if( style->label_position >= 50 )
    x += reminder_icon_decal->w;
  w = gkrellm_chart_width() - reminder_icon_decal->w - 2 * style->margin.left;

  panel->textstyle = gkrellm_meter_textstyle(style_id);

  reminder_text_decal = gkrellm_create_decal_text( panel,
						   "0",
						   panel->textstyle,
						   style, x, -1, w );
  if( reminder_icon_decal->h > reminder_text_decal->h )
    reminder_text_decal->y += (reminder_icon_decal->h - reminder_text_decal->h ) / 2;
  else
    reminder_icon_decal->y += ( reminder_text_decal->h - reminder_icon_decal->h ) / 2;

  reminder_text_button = gkrellm_put_decal_in_meter_button( panel,
							reminder_text_decal,
							cb_reminder_button, NULL, NULL );
  gkrellm_panel_configure( panel, NULL, style );
  gkrellm_panel_create( vbox, reminder_mon, panel);
      //, gkrellm_bg_meter_image( style_id ) );

  reminder_text_button_enable();

//  gkrellm_monitor_height_adjust(panel->h);

  if (first_create)
    {
      g_signal_connect(G_OBJECT (panel->drawing_area), "expose_event",
			 G_CALLBACK(panel_expose_event), NULL);
      g_signal_connect( G_OBJECT( panel->drawing_area ),
			  "button_press_event",
			  G_CALLBACK(cb_panel_press), NULL );

      reminder_load_stored();
      reminder_build_today( FALSE );
    }
}

static GkrellmMonitor	reminder_monitor	=
{
  "Reminder",   		/* Title for config clist.   */
  0,				/* Id,  0 if a plugin       */
  create_plugin,		/* The create function      */
  update_plugin,		/* The update function      */
  display_config,		/* The config tab create function   */
  update_config,		/* Apply the config function        */

  save_config,			/* Save user config			*/
  load_config,			/* Load user config			*/
  CONFIG_NAME,			/* config keyword			*/

  NULL,				/* Undefined 2	*/
  NULL,				/* Undefined 1	*/
  NULL,				/* private	*/

  MON_MAIL,			/* Insert plugin before this monitor	    */

  NULL,				/* Handle if a plugin, filled in by GKrellM */
  NULL				/* path if a plugin, filled in by GKrellM   */
};

#if defined(WIN32)
    __declspec(dllexport) GkrellmMonitor *
    gkrellm_init_plugin(win32_plugin_callbacks* calls)
#else
    GkrellmMonitor *
    gkrellm_init_plugin()
#endif
{
#if defined(WIN32)
    callbacks = calls;
#endif

    pGK = gkrellm_ticks();

    reminder_mon = &reminder_monitor;

  head_stored = NULL;
  head_today = NULL;
  head_temp = NULL;
  head_delete = NULL;
  last_active = NULL;

  str_null = g_strdup("");

  num_active = 0;
  num_today = 0;
  today_day = 0;

  window_reminder = NULL;
  window_today = NULL;

#if defined(__NetBSD__) || defined(__FreeBSD__) || defined(__OpenBSD__)
  bsd_timezone = gkrellm_get_current_time()->tm_gmtoff;
#endif

  default_config();

  style_id = gkrellm_add_meter_style(reminder_mon, STYLE_NAME);

  return reminder_mon;
}
