/*
 * $Source: /afs/gza.com/misc/xscreensaver/src/RCS/float.c,v $
 * $Author: jik $
 *
 * This file is part of xscreensaver.  It contains the code for the
 * floating icon.
 *
 * Author: Jonathan Kamens, MIT Project Athena and
 *                          MIT Student Information Processing Board
 *
 * Coyright (c) 1989 by Jonathan Kamens.  This code may be distributed
 * freely as long as this notice is kept intact in its entirety and
 * every effort is made to send all corrections and improvements to
 * the code back to the author.  Also, don't try to make any money off
 * of it or pretend that you wrote it.
 */

/*
 * The code in this file is, for the most part, really gross, and I'm
 * ashamed that I wrote it.  This comment will stay at the top of this
 * file (as a personal penance, I guess :-) until I've written enough
 * of it to no longer be ashamed of it ...
 */

#ifndef lint
     static char rcsid_float_c[] = "$Header: /afs/gza.com/misc/xscreensaver/src/RCS/float.c,v 1.14 1993/07/02 18:33:07 jik Exp $";
#endif

#define FLOAT

#include "xsaver.h"
#include <signal.h>
#include "globals.h"
#include "scaling.h"
#include "wordwrap.h"
#include "PromptBox.h"
#include "logoutButton.h"
#include "float.h"

extern long random();
extern char *widget_string(), *my_malloc();
extern Dimension widget_width();
extern char *time_string(), *elapsed_string(), *timeleft_string();
extern void unlock_command();
 /*
  * XXX why am I declaring these instead of including headers which
  * declare them?
  */
extern void XtMoveWidget(); 
extern void XawFormDoLayout();

/* Position and offset information */
static int x, y;
static int x_off = 0, y_off = 0;
static int max_float_x, max_float_y;
static int delay;

/* The pointer to the message lines */
char **message_lines = (char **) NULL;

/* The timer that gets turned off when the float gets deactivated */
static XtIntervalId float_timer;

/* the form widget itself */
static Widget float_widget = (Widget) NULL;

/* Widgets we'll be using */
static Widget icon_w, time_w, elapsed_w, timeout_w;
static Widget *message_ws;

/* A little bit of state for deciding when to recreate the widget. */
Dimension max_width = 0;
int max_chars = 0;




static void SetBounds()
{
     Dimension width, height, border;
     Arg arglist[3];
     
     XtSetArg(arglist[0], XtNwidth, &width);
     XtSetArg(arglist[1], XtNheight, &height);
     XtSetArg(arglist[2], XtNborderWidth, &border);
     XtGetValues(float_widget, arglist, 3);

     max_float_x = display_width - width - 2 * border;
     max_float_y = display_height - height - 2 * border;

     if (x) {
	  if (x > max_float_x)
	       x = max_float_x;
	  if (y > max_float_y)
	       y = max_float_y;
     }
}



static void SetPosition()
{
     x = random() % max_float_x;
     y = random() % max_float_y;
}


static void SetOffset()
{
     x_off = new_off();
     y_off = new_off();
}     


void StartupFloat()
{
     NewFloat();
     build_float();
     
     SetBounds();
     if (! x_off) {
	  SetPosition();
	  SetOffset();
	  delay = calc_delay(x_off, y_off, defs.velocity);
     }

     XtMoveWidget(float_widget, x, y);
     
     XtMapWidget(float_widget);
     ActivateFloat();
}




void ActivateFloat()
{
     float_timer = XtAppAddTimeOut(app_context, delay, MoveFloat, NULL);
}



void DeactivateFloat()
{
     if (float_timer) {
	  XtRemoveTimeOut(float_timer);
	  float_timer = (XtIntervalId) NULL;
     }
}




static void MoveFloat()
{
     int changed_off = 0;

     if (lock_flag && defs.timeout) {
	  if ((xtimes.current - xtimes.start) >= defs.timeout * 60)
	       do_timeout();
     }
     UpdateFloat();
     if (x + x_off > max_float_x) {
	  changed_off += maybe_new_off_opposite_sign(&x_off);
     }
     else if (x + x_off < 0) {
	  changed_off += maybe_new_off_opposite_sign(&x_off);
     }
     if (y + y_off > max_float_y) {
	  changed_off += maybe_new_off_opposite_sign(&y_off);
     }
     else if (y + y_off < 0) {
	  changed_off += maybe_new_off_opposite_sign(&y_off);
     }
     x += x_off; y += y_off;
     if (changed_off)
	  delay = calc_delay(x_off, y_off, defs.velocity);
     XtMoveWidget(float_widget, x, y);
     float_timer = XtAppAddTimeOut(app_context, delay, MoveFloat, NULL);
     moveLogoutButton();
}




void do_timeout()
{
#if defined(ATHENA) && defined(TIMEOUT_LOGOUT)
     extern char *getenv();
     char *xsession;
#endif
     
     unlock_command();
     if (defs.logout_command) {
	  system(defs.logout_command);
     }
#ifdef TIMEOUT_LOGOUT
#ifdef ATHENA
     else if ((xsession = getenv("XSESSION")) && atoi(xsession)) {
	  kill(atoi(xsession), SIGHUP);
     }
#endif /* ATHENA */
#ifndef ultrix
     else {
	  /* this signal doesn't work under Ultrix */
	  kill(-1, SIGHUP); /* 4.3-ism -- sends SIGHUP to all the user's */
	                    /* processes                                 */
     }
#endif /* ultrix */
#endif /* TIMEOUT_LOGOUT */
     /*
      * I used to have it exiting explicitly here.  The problem with
      * that is that we are not guaranteed to actually have logged out
      * the user at this point, and if we didn't, we don't want his
      * screen to unlock wih him still logged in.  Therefore, we
      * assume that if the user gets logged out, the X server will be
      * reset and xscreensaver will die.  We do, however, exit, if the
      * resource telling us to do so is set.
      */
     if (defs.timeout_exit) {
	  clean_up_and_die(0);
     }
}




#define max(a,b) ((a) > (b) ? (a) : (b))
     
static Boolean Redo(str, wid, width, chars)
char *str;
Widget wid;
Dimension *width;
int *chars;
{
     Arg arglist[1];
     
     if (str) {
	  XtSetArg(arglist[0], XtNlabel, str);
	  XtSetValues(wid, arglist, 1);
	  *width = max(*width, widget_width(wid));
	  *chars = max(*chars, strlen(widget_string(wid)));
	  return(True);
     }
     else {
	  *width = max(*width, widget_width(wid));
	  *chars = max(*chars, strlen(widget_string(wid)));
	  return(False);
     }
}

#undef max


#define SetWidth(a,b) if ((a) && (widget_width(b) != new_width))\
     XtSetValues(b, arglist, 1)

static void UpdateFloat()
{
     Arg arglist[2];
     Dimension new_width = 0, border;
     int float_chars = 0;

     Boolean new_time = 0, new_elapsed = 0, new_timeout = 0;
     
     UpdateClock();
     new_width = widget_width(icon_w);
     if (defs.d_time)
	  new_time = Redo(time_string(TIME_FORMAT, NoForce), time_w,
			  &new_width, &float_chars);
     if (defs.d_elapsed)
	  new_elapsed = Redo(elapsed_string(ELAPSED_FORMAT, NoForce),
			     elapsed_w, &new_width, &float_chars);
     if (defs.d_timeout && lock_flag && defs.timeout)
	  new_timeout = Redo(timeleft_string(TIMELEFT_FORMAT, NoForce),
			     timeout_w, &new_width, &float_chars);

     if (float_chars < DEFAULT_MESSAGE_WIDTH)
	  float_chars = DEFAULT_MESSAGE_WIDTH;
     if (new_time || new_elapsed || new_timeout) {
	  if (float_chars != max_chars) {
	       NewFloat();
	       build_float();
	       XtMoveWidget(float_widget, x, y);
	       XtMapWidget(float_widget);
	  }
	  else {
	       Widget *ptr;
	       
	       XawFormDoLayout(float_widget, False);

	       XtSetArg(arglist[0], XtNwidth, (XtArgVal) new_width);

	       SetWidth(1, icon_w);
	       SetWidth(defs.d_time, time_w);
	       SetWidth(defs.d_elapsed, elapsed_w);
	       SetWidth(defs.d_timeout && lock_flag && defs.timeout,
			timeout_w);

	       for (ptr = message_ws; *ptr; ptr++) {
		    if (widget_width(*ptr) != new_width)
			 XtSetValues(*ptr, arglist, 1);
	       }
	       
	       XawFormDoLayout(float_widget, True);

	       XtSetArg(arglist[0], XtNwidth, &new_width);
	       XtSetArg(arglist[1], XtNborderWidth, &border);
	       XtGetValues(float_widget, arglist, 2);
	       max_float_x = display_width - new_width - 2 * border;
	  }
     }
}     

#undef SetWidth

     
     


static int maybe_new_off_opposite_sign(foo)
int *foo;
{
     int off = - *foo;
     int changed = 0;
     
     if (! (random() % 4)) {
	  off = random() % 5 + 1;
	  off = (*foo < 0 ? off : - off);
	  changed++;
     }
     *foo = off;
     
     return(changed);
}







static int new_off()
{
     int foo;
     
     foo = random() % 11 - 5;
     if (! foo)
	  foo = (random() % 1 ? 1 : -1);
     
     return(foo);
}



#define takemax(a) { int len; len = strlen(a);\
	             max_chars = (max_chars > len ? max_chars : len); }
		  

static void build_float()
{
     PromptLine lines[MAXPROMPT];
     int a, i = 0;
     Widget *return_widgets;
     static String float_name = "float";
     static String message_name = "message";
     
     max_chars = 0;
     if (! float_widget) {
	  /* The first one should pick up a bitmap from the defaults */
	  lines[i] = default_line;
	  lines[i].use_default = True;
	  lines[i].name = float_name;
	  lines[i].str = "floatIcon";					i++;
	  if (defs.d_time) {
	       lines[i] = default_line;
	       lines[i].name = float_name;
	       lines[i].str = time_string(TIME_FORMAT, Force);
	       takemax(lines[i].str);
	       i++;
	  }
	  if (defs.d_elapsed) {
	       lines[i] = default_line;
	       lines[i].name = float_name;
	       lines[i].str = elapsed_string(ELAPSED_FORMAT, Force);
	       takemax(lines[i].str);
	       i++;
	  }
     
	  if (defs.d_timeout && lock_flag && defs.timeout) {
	       lines[i] = default_line;
	       lines[i].name = float_name;
	       lines[i].str = timeleft_string(TIMELEFT_FORMAT, Force);
	       takemax(lines[i].str);
	       i++;
	  }

	  if (max_chars < DEFAULT_MESSAGE_WIDTH)
	       max_chars = DEFAULT_MESSAGE_WIDTH;

	  if (defs.lock_message) {
	       char **ptr;
	       
	       ptr = message_lines = word_wrap(defs.lock_message, max_chars,
					       MAXPROMPT - i);
	       if (*ptr) {
		    lines[i] = default_line;
		    lines[i].spread = 10;
		    lines[i].name = message_name;
		    lines[i].str = *ptr;
		    ptr++, i++;
	       }
	       
	       for (; *ptr; i++, ptr++) {
		    lines[i] = default_line;
		    lines[i].name = message_name;
		    lines[i].str = *ptr;
	       }
	  }
	  
	  return_widgets = (Widget *) my_malloc(sizeof(Widget) * (i + 1),
						"floating icon");
	  float_widget = PromptBox(root_widget, "float", lines, i,
				   return_widgets);
	  a = 0;
	  icon_w = return_widgets[a];				a++;
	  if (defs.d_time) {
	       time_w = return_widgets[a];			a++;
	  }
	  if (defs.d_elapsed) {
	       elapsed_w = return_widgets[a];			a++;
	  }
	  if (defs.d_timeout && lock_flag && defs.timeout) {
	       timeout_w = return_widgets[a];			a++;
	  }
	  message_ws = &return_widgets[a];
     }
}
     
#undef takemax







void change_message(new_val)
char *new_val;
{
     char *buf;

     if (! new_val)
	  return;

     XtFree(defs.lock_message);
     if (*new_val) {
	  buf = my_malloc(strlen(new_val) + 1, "lock message");
	  strcpy(buf, new_val);
	  defs.lock_message = buf;
	  max_chars = 0;
     }
     else {
	  defs.lock_message = 0;
     }
}





static void UpdateClock()
{
     struct timeval tim;
     gettimeofday(&tim, (struct timezone *) NULL);
     xtimes.current = tim.tv_sec;
}




void NewFloat()
{
     if (float_widget) {
#if 0
	  if (debug_file) {
	       fprintf(debug_file,
		       "About to unmap float widget 0x%x (window id 0x%x) in NewFloat.\n",
		       float_widget, XtWindow(float_widget));
	  }
	  XtUnmapWidget(float_widget);
#endif
	  if (debug_file) {
	       fprintf(debug_file,
		       "About to destroy float widget 0x%x (window id 0x%x) in NewFloat.\n",
		       float_widget, XtWindow(float_widget));
	  }
	  XtDestroyWidget(float_widget);
	  float_widget = (Widget) NULL;
     }
     if (message_lines) {
	  discard_wrap(message_lines);
	  message_lines = (char **) NULL;
     }
}
