/* Copyright 1996 - 2000 by Abacus Research and
 * Development, Inc.  All rights reserved.
 */

#if !defined (OMIT_RCSID_STRINGS)
char ROMlib_rcsid_aboutbox[] =
	    "$Id: aboutbox.c 130 2006-05-10 17:21:35Z ctm $";
#endif

#include "rsys/common.h"

#if defined (SUPPORT_ABOUT_EXECUTOR_BOX)

#include "rsys/aboutbox.h"
#include "rsys/mman.h"
#include "rsys/vdriver.h"
#include "rsys/string.h"
#include "rsys/cquick.h"
#include "rsys/quick.h"
#include "rsys/ctl.h"
#include "rsys/version.h"
#include "rsys/licensetext.h"
#include "CQuickDraw.h"
#include "Gestalt.h"
#include "ToolboxEvent.h"
#include "TextEdit.h"
#include "FontMgr.h"
#include "OSUtil.h"
#include <ctype.h>
#include <rsys/file.h>
#include <rsys/osutil.h>
#include <SegmentLdr.h>
#include <rsys/segment.h>
#include "rsys/notmac.h"
#include "rsys/custom.h"
#include "rsys/gestalt.h"
#include "rsys/osevent.h"

#if !defined (TIME_OUT_DAYS)

#define days_left_func() (1)

#else

#include "rsys/cookie.h"

#if defined (LINUX)
#define COOKIE_FILE "/etc/opt/executor/.xp"
#endif


#if defined (LINUX) || defined (MSDOS)

#if defined (MSDOS)

#define COOKIE_FILE "excook.xp"

PRIVATE char *
viable_path (char vol, const char *path)
{
  char *retval;
  int path_len, retval_size;

  path_len = strlen (path);
  retval_size = sizeof vol + sizeof ':' + path_len + 1 + sizeof (COOKIE_FILE);
  retval = malloc (retval_size);
  if (retval)
    {
      struct stat sbuf;

      sprintf (retval, "%c:%s", vol, path);
      if (stat (retval, &sbuf) != 0 || !S_ISDIR (sbuf.st_mode))
	{
	  free (retval);
	  retval = NULL;
	}
      else
	{
	  if (path[1] != 0) /* test for non-root directory */
	    strcat (retval, "/");
	  strcat (retval, COOKIE_FILE);
	}
    }
  return retval;
}

#endif

PRIVATE char *
cookie_file_name (void)
{
  char *retval;

#if defined (LINUX)
  retval = strdup (COOKIE_FILE);
#elif defined (MSDOS)
  static char vols[] =
  {
    'C',
    0, /* to be filled in with ROMlib_start_drive*/
  };
  static char *dirs[] =
  {
    "/dos",
    "/windows",
    "/winnt",
    "/",
  };
  int i, j;

  vols[NELEM (vols)-1] = ROMlib_start_drive;
  retval = NULL;
  for (i = 0; !retval && i < (int) NELEM (vols); ++i)
    for (j = 0; !retval && j < (int) NELEM (dirs); ++j)
      retval = viable_path (vols[i], dirs[j]);

#else
#error need cookie file name
#endif
  return retval;
}

#define open_cookie_file ocf

PRIVATE
FILE *open_cookie_file (const char *mode)
{
  FILE *retval;
  char *file;

  file = cookie_file_name ();
  retval = file ? fopen (file, mode) : NULL;
  if (!retval)
    warning_trace_info ("%s", file ? file : "NULL");
  if (file)
    free (file);

  return retval;
}
#endif

PRIVATE boolean_t
retrieve_cookie (cookie_t *cookiep)
{
  boolean_t retval;

#if defined (LINUX) || defined (MSDOS)
  retval = FALSE;
  {
    FILE *fp;

    fp = open_cookie_file ("rb");
    if (fp)
      {
	retval = fread (cookiep, sizeof *cookiep, 1, fp) == 1;
	fclose (fp);
      }
  }
#elif defined (CYGWIN32)

  retval = win_retrieve_cookie (cookiep);

#else
#warning not implemented
#endif

  return retval;
}

PRIVATE long
today (void)
{
  time_t t;
  struct tm tm;
  int retval;

  time (&t);
  tm = *localtime (&t);
  retval = (long) tm.tm_year * 365 + tm.tm_yday;
  return retval;
}

PRIVATE long
unify_version (int major, int minor)
{
  long retval;

  retval = major * 10000 + minor;
  return retval;
}

#define leave_cookie lc

PRIVATE boolean_t
leave_cookie (const cookie_t *cookiep)
{
  boolean_t retval;

#if defined (LINUX) || defined (MSDOS)
  retval = FALSE;
  {
    FILE *fp;

    fp = open_cookie_file ("wb");
    if (fp)
      {
	retval = (fwrite (cookiep, sizeof *cookiep, 1, fp) == 1 &&
		  fclose (fp) == 0);
      }
    if (!retval)
      warning_trace_info ("fp = %p", fp);
  }
#elif defined (CYGWIN32)
  retval = win_leave_cookie (cookiep);
    if (!retval)
      warning_trace_info (NULL_STRING);
#else
#error "don't know how to leave cookie"
#endif
  return retval;
}

#define COOKIE_RES_TYPE TICK(" xp ")
enum { COOKIE_RES_ID = 0 };

PRIVATE boolean_t
retrieve_system_cookie (cookie_t *cookiep)
{
  boolean_t retval;
  Handle h;
  INTEGER rn;

  rn = CW (CurMap);
  UseResFile (0);
  h = GetResource (COOKIE_RES_TYPE, COOKIE_RES_ID);
  if (!h)
    retval = FALSE;
  else
    {
      if (cookiep)
	*cookiep = *(cookie_t *) STARH (h);
      retval = TRUE;
    }
  UseResFile (rn);
  return retval;
}

#define leave_system_cookie lsc

PRIVATE boolean_t
leave_system_cookie (const cookie_t *cookiep)
{
  boolean_t retval, need_to_add;
  Handle h;
  INTEGER rn;

  retval = TRUE;
  rn = CW (CurMap);
  UseResFile (0);
  h = GetResource (COOKIE_RES_TYPE, COOKIE_RES_ID);
  if (h)
    need_to_add = FALSE;
  else
    {
      need_to_add = TRUE;
      h = NewHandleSys (sizeof *cookiep);
      if (!h)
	retval = FALSE;
    }
  if (retval)
    {
      *(cookie_t *)STARH (h) = *cookiep;
      if (need_to_add)
	AddResource (h, COOKIE_RES_TYPE, COOKIE_RES_ID, NULL);
      else
	ChangedResource (h);
      WriteResource (h);
    }
  UseResFile (rn);
  return retval;
}

PRIVATE boolean_t
leave_cookies (const cookie_t *cookiep)
{
  boolean_t retval;

  retval = leave_cookie (cookiep);
  if (retval)
    retval = leave_system_cookie (cookiep);
  return retval;
}

enum
{
  COOKIE_MAJOR_REVISION = 2,
  COOKIE_MINOR_REVISION = 12, /* 2.0v was 1 */
};

PRIVATE void
setup_cookie (cookie_t *cookiep)
{
  cookiep->version = unify_version (COOKIE_MAJOR_REVISION,
				    COOKIE_MINOR_REVISION);
  cookiep->day = today () + TIME_OUT_DAYS;
}

#define days_left_func dlf

#define days_left(expire_day)			\
({						\
  typeof (expire_day) _d;			\
  int retval;					\
						\
  _d = expire_day;				\
  retval = _d - today ();			\
  if (retval < 0 || retval > TIME_OUT_DAYS)	\
    retval = 0;					\
  retval;					\
})

PRIVATE int
days_left_func (void)
{
  int retval;
  cookie_t cookie;

  if (retrieve_cookie (&cookie))
    {
      if (cookie.version < unify_version (COOKIE_MAJOR_REVISION,
					  COOKIE_MINOR_REVISION))
	{
	  setup_cookie (&cookie);
	  retval = leave_cookies (&cookie) ? days_left (cookie.day) : 0;
	  if (retval == 0)
	    warning_trace_info ("found old, couldn't leave new");
	}
      else
	{
	  cookie_t cookie2;

	  if (!retrieve_system_cookie (&cookie2))
	    {
	      if (leave_system_cookie (&cookie))
		retval = days_left (cookie.day);
	      else
		{
		  warning_trace_info ("couldn't leave sys");
		  retval = 0;
		}
	    }
	  else
	    {
	      if (cookie.day == cookie2.day)
		retval = days_left (cookie.day);
	      else
		{
		  retval = 0;
		  warning_trace_info ("mismatch");
		}
	    }
	}
    }
  else if (retrieve_system_cookie (&cookie))
    {
      if (cookie.version < unify_version (COOKIE_MAJOR_REVISION,
					  COOKIE_MINOR_REVISION))
	{
	  setup_cookie (&cookie);
	  retval = leave_cookies (&cookie) ? days_left (cookie.day) : 0;
	  if (retval == 0)
	    warning_trace_info ("found old, couldn't leave new");
	  else
	    warning_trace_info ("found old, left new");
	}
      else
	{
	  retval = 0;
	  warning_trace_info ("unexpected system");
	}
    }
  else 
    {
      setup_cookie (&cookie);
      if (leave_cookies (&cookie))
	retval = days_left (cookie.day);
      else
	{
	  retval = 0;
	  warning_trace_info ("couldn't leave");
	}
    }

  if (retval <= 0)
    {
      ROMlib_exit = TRUE;
      if (retval < 0)
	warning_trace_info ("%d", retval);
    }
  replace_physgestalt_selector (gestaltDemoExpiration, retval);
  return retval;
}

#endif


#define ABOUT_BOX_WIDTH  500
#define ABOUT_BOX_HEIGHT 300
#define BUTTON_WIDTH  85
#define BUTTON_HEIGHT 20
#define SCROLL_BAR_WIDTH 16
#define TE_MARGIN 4
#define TE_WIDTH  (ABOUT_BOX_WIDTH - 20)
#define TE_HEIGHT (ABOUT_BOX_HEIGHT - 106)
#define TE_LEFT   ((ABOUT_BOX_WIDTH - TE_WIDTH) / 2)
#define TE_RIGHT  (TE_LEFT + TE_WIDTH)
#define TE_TOP    66
#define TE_BOTTOM (TE_TOP + TE_HEIGHT)

#define DONE_BUTTON_NAME "Accept"

#define LICENSE_BUTTON_NAME "License"
#define TIPS_BUTTON_NAME "Tips"

#define COPYRIGHT_STRING_1 "Copyright \251 ARDI 1986-2006"
#define COPYRIGHT_STRING_2 "All rights reserved."

static struct { char *name, *text; ControlHandle ctl; } about_box_buttons[] = {
  { LICENSE_BUTTON_NAME, NULL /* generated on the fly from licensetext.c */,
    NULL
  },
  { "Maker",
    "ARDI\r"
    //    "Suite 4-101\r"
    //    "1650 University Blvd., NE\r"
    //    "Albuquerque, NM  87102\r\r"

    //    "+1 505 766 5153 FAX\r"
    //    "+1 505 766 9115 Phone (M-F 9am-5pm Mountain Time)\r\r"

    //    "E-Mail: questions@ardi.com\r"
    "World Wide Web: <http://www.ardi.com>\r"
    "FTP: <ftp://ftp.ardi.com/pub>\r",
    NULL
  },

  { "Credits",
"Bill Goldman \321 Browser, Testing\r"
"Mat Hostetter \321 Syn68k, Low Level Graphics, DOS port, more...\r"

#if defined (MSDOS)
"Joel Hunter \321 Low Level DOS Sound\r"
#endif

#if defined (CYGWIN32)
"Sam Lantinga \321 Win32 port\r"
#endif

"Patrick LoPresti \321 High Level Sound, Low Level Linux Sound\r"
"Cliff Matthews \321 this credit list (and most things not listed)\r"
"Cotton Seed \321 High Level Graphics, Apple Events, more...\r"

#if defined (MSDOS) || defined (CYGWIN32)
"Lauri Pesonen \321 Low Level Win32 CD-ROM access (Executor 2.1)\r"
#endif

#if defined (MSDOS)
"Samuel Vincent \321 Low Level DOS Serial Port Support\r"
#endif

"and all the engineers and testers who helped us build version 1.x\r"
"\r"
"Windows Appearance:\r"
"\r"
"The windows appearance option uses \"Jim's CDEFs\" copyright "
"Jim Stout and the "
"\"Infinity Windoid\" copyright Troy Gaul.\r"
"\r"
"Primary Pre-Beta Testers:\r"
"\r"
"Jon Abbott \321 Testing, Icon Design\r"
"Ziv Arazi \321 Testing\r"
"Edmund Ronald \321 Advice, Testing\r"
"K. Harrison Liang \321 Testing\r"
"Hugh Mclenaghan \321 Testing\r"
"Emilio Moreno \321 Testing, Spanish Translation + Keyboard, Icon Design\r"
"Ernst Oud \321 Documentation, Testing\r"
"\r"
"Some of the best development tools are Free and written by:\r"
"\r"
"The Free Software Foundation \321 the gcc compiler, more ...\r"
"Cygnus Support \321 the gdb debugger, the gnats bug tracking software\r"
"Linus Torvalds et al. \321 Linux, our favorite OS"
"\r"
#if defined (MSDOS)
"Executor/DOS was ported via DJGPP, DJ Delorie's port of gcc.\r"
"DJGPP's primary authors are:\r"
"\r"
"DJ Delorie \321 Ringleader\r"
"William Metzenthen \321 80387 emulator\r"
"Charles Sandmann \321 cwsdpmi, more...\r"
"Morten Welinder \321 misc.\r"
"Eli Zaretskii \321 Some DOS-related library functions\r"
"\rThis product includes software developed by "
"the University of California, Berkeley and its contributors\r"
#endif
  , NULL
 },
  {
    TIPS_BUTTON_NAME,
    "Don't delete tips.txt.  If you do, that will be your only tip.\r"
    , NULL
  },
  { DONE_BUTTON_NAME, "Internal error!", NULL }
};



static WindowPtr about_box;
static ControlHandle about_scrollbar;
static TEHandle about_te;
static ProcPtr scroll_bar_callback;

enum { THROTTLE_TICKS = 4 };

PRIVATE boolean_t
enough_time_has_passed (void)
{
  static ULONGINT old_ticks;
  ULONGINT new_ticks;
  boolean_t retval;

  new_ticks = TickCount ();
  retval = new_ticks - old_ticks >= THROTTLE_TICKS;
  if (retval)
    old_ticks = new_ticks;
  return retval;
}

/* Menu name for the about box. */
StringPtr about_box_menu_name_pstr = (StringPtr) "\022\000About Executor...";

static void
help_scroll (ControlHandle c, INTEGER part)
{
  if (enough_time_has_passed ())
    {
      int page_size, old_value, new_value, delta;
      int line_height;

      old_value = GetCtlValue (c);
      line_height = TE_LINE_HEIGHT (about_te);
      page_size = RECT_HEIGHT (&CTL_RECT (c)) / line_height;

      switch (part)
	{
	case inUpButton:
	  SetCtlValue (c, old_value - 1);
	  break;
	case inDownButton:
	  SetCtlValue (c, old_value + 1);
	  break;
	case inPageUp:
	  SetCtlValue (c, old_value - page_size);
	  break;
	case inPageDown:
	  SetCtlValue (c, old_value + page_size);
	  break;
	default:
	  break;
	}

      new_value = GetCtlValue (about_scrollbar);
      delta = new_value - old_value;
      if (delta != 0)
	TEScroll (0, -delta * line_height, about_te);
    }
}


static syn68k_addr_t
scroll_stub (syn68k_addr_t junk, void *junk2)
{
  syn68k_addr_t retaddr;
  uint32 ctl;
  INTEGER part;

  retaddr = POPADDR ();
  part = POPUW ();
  ctl = (uint32) SYN68K_TO_US (POPUL ());
  help_scroll ((ControlHandle) ctl, part);
  return retaddr;
}

static int
find_button (const char *button_name)
{
  int i;

  for (i = 0; i < (int) NELEM (about_box_buttons); i++)
    if (!strcmp (about_box_buttons[i].name, button_name))
      break;
  gui_assert (i < (int) NELEM (about_box_buttons));
  return i;
}

/* Returns the index of the button with name LICENSE_BUTTON_NAME. */
static int
find_license_button (void)
{
  int retval;

  retval = find_button (LICENSE_BUTTON_NAME);
  return retval;
}

#if defined (TIME_OUT_DAYS)
#define DAYS_LEFT_MESSAGE "%d DAYS LEFT\r\r"
#define   EXPIRED_MESSAGE "This demo has EXPIRED.\r\r"
#endif

static void
create_license_text (int days_left)
{
  int b;

  b = find_license_button ();
  if (about_box_buttons[b].text == NULL)
    {
      long new_size;
      char *license_text, *p, *q, *licensep;

      new_size = 0;

#if defined (TIME_OUT_DAYS)
#if TIME_OUT_DAYS > 999
#error string overflow
#endif
      new_size += MAX (sizeof DAYS_LEFT_MESSAGE, sizeof EXPIRED_MESSAGE);
#endif

      /* Compute the length of the license string. */
      for (licensep = ROMlib_licensep ? (char *) ROMlib_licensep->chars : "";
	   *licensep;)
	{
	  int title_len;
	  int body_len;

	  title_len = strlen (licensep);
	  licensep += title_len + 1;
	  body_len = strlen (licensep);
	  licensep += body_len + 1;
	  new_size += title_len + body_len + 5;
	}

      /* Allocate and construct the license string. */
      license_text = (char *) NewPtrSys (new_size + 1);
      license_text[0] = '\0';

      p = license_text;

#if defined (TIME_OUT_DAYS)
      if (days_left > 0)
	p += sprintf (p, DAYS_LEFT_MESSAGE, days_left);
      else
	p += sprintf (p, EXPIRED_MESSAGE);
#endif

      for (licensep = ROMlib_licensep ? (char *) ROMlib_licensep->chars : "";
	   *licensep;)
	{
	  const char *titlep;
	  const char *bodyp;

	  titlep = licensep;
	  licensep += strlen (licensep) + 1;
	  bodyp = licensep;
	  licensep += strlen (licensep) + 1;
	  p += sprintf (p, "%s\r\r%s\r\r\r", titlep, bodyp);
	}

      /* Nuke any trailing whitespace, and end with exactly one linefeed */
      for (q = p - 1; q >= license_text && isspace (*q); q--)
	*q = '\0';
      if (q >= license_text)
	strcpy (q + 1, "\r");

      about_box_buttons[b].text = license_text;
    }
}

/*
 * get an upper bound on the number of tips that may be in the buffer
 */

static int
approximate_tips (char *p)
{
  int retval;

  retval = 0;
  while (*p)
    {
      ++retval;
      while (*p && *p != '\n')
	++p;
      while (*p && *p == '\n')
	++p;
    }
  return retval;
}

typedef struct
{
  int tip_offset;
  int tip_length;
}
tip_t;

static int
find_tips (tip_t *tips, const char *p)
{
  const char *orig_p;
  int retval;

  retval = 0;
  
  orig_p = p;
  while (*p)
    {
      /* suck up any excess leading \n */
      while (*p && *p == '\n')
	++p;

      if (*p)
	{
	  tips->tip_offset = p - orig_p;
	  while (*p && (*p != '\n' || (p[1] && p[1] != '\n')))
	    ++p;
	  tips->tip_length = p - orig_p - tips->tip_offset;
	  ++retval;
	  ++tips;
	}	
    }

  return retval;
}

static void
add_to_str (char **pp, char *ip, int n)
{
  int i;

  for (i = 0; i < n; ++i)
    (*pp)[i] = ip[i] == '\n' ? ' ' : ip[i];
  *pp += n;
}

static char *
parse_and_randomize_tips (char *buf)
{
  int n_tips, chars_needed;
  tip_t *tips;
  boolean_t seen_tip;
  char *retval;
  char *p;
  int i;

  n_tips = approximate_tips (buf);
  tips = alloca (n_tips * sizeof *tips);
  n_tips = find_tips (tips, buf);

  chars_needed = 0;
  for (i = 0; i < n_tips; ++i)
    chars_needed += tips[i].tip_length;
  chars_needed += 2 * (n_tips - 1) + 1;

  retval = malloc (chars_needed);
  seen_tip = FALSE;
  p = retval;
  while (n_tips)
    {
      if (seen_tip)
	add_to_str (&p, "\r\r", 2);
      else
	seen_tip = TRUE;
      i = rand () % n_tips;
      add_to_str (&p, buf+tips[i].tip_offset, tips[i].tip_length);
      tips[i] = tips[n_tips-1];
      --n_tips;
    }
  *p = 0;
  return retval;
}

static char *orig_text;

static void
create_tips_text (void)
{
  char *filename;
  FILE *fp;
  int b;

  b = find_button (TIPS_BUTTON_NAME);
  if (!orig_text)
    {
      srand (Ticks); /* NOTE: we don't care that rand/srand is not a
			   particular good way to generate random numbers */
      orig_text = about_box_buttons[b].text;
    }
  if (about_box_buttons[b].text == orig_text)
    {
#if !defined (LINUX)
      filename = copystr ("+/tips.txt");
#else
      filename = copystr ("/opt/executor/tips.txt");
#endif
      fp = fopen (filename, "r");
      free (filename);
      if (fp)
	{
	  char tempbuf[32768];
	  size_t nread;
	  
	  nread = fread (tempbuf, 1, sizeof tempbuf -1, fp);
	  tempbuf[nread] = 0;
	  if (nread > 0)
	    about_box_buttons[b].text = parse_and_randomize_tips (tempbuf);
	  fclose (fp);
	}
    }
}

static void
dispose_license_text (void)
{
  int b;

  b = find_license_button ();
  if (about_box_buttons[b].text != NULL)
    {
      DisposPtr ((Ptr) about_box_buttons[b].text);
      about_box_buttons[b].text = NULL;
    }
}

static void
dispose_tips_text (void)
{
  int b;

  b = find_button (TIPS_BUTTON_NAME);
  if (about_box_buttons[b].text != orig_text)
    {
      free (about_box_buttons[b].text);
      about_box_buttons[b].text = orig_text;
    }
}


static void
create_about_box (int days_left)
{
  static Rect scroll_bar_bounds = {
    CWC (TE_TOP),
    CWC (TE_RIGHT - SCROLL_BAR_WIDTH),
    CWC (TE_BOTTOM),
    CWC (TE_RIGHT)
  };
  static Rect te_bounds = {
    CWC (TE_TOP + 1),
    CWC (TE_LEFT + TE_MARGIN),
    CWC (TE_BOTTOM - 1),
    CWC (TE_RIGHT - TE_MARGIN - SCROLL_BAR_WIDTH)
  };
  Rect about_box_bounds;
  int b;

  create_license_text (days_left);
  create_tips_text ();

  SetRect (&about_box_bounds,
	   (vdriver_width - ABOUT_BOX_WIDTH) / 2U,
	   (vdriver_height - ABOUT_BOX_HEIGHT) / 3U + 15,
	   (vdriver_width + ABOUT_BOX_WIDTH) / 2U,
	   (vdriver_height + 2 * ABOUT_BOX_HEIGHT) / 3U + 15);
  
  /* Create the window. */
  about_box = (WindowPtr) NewCWindow (NULL, &about_box_bounds,
				      (StringPtr) "\016About Executor",
				      FALSE, dBoxProc, (CWindowPtr) -1,
				      TRUE,	/* go away flag */
				      -5	/* unused */);

  THEPORT_SAVE_EXCURSION
    (about_box,
     {
       /* Create the push buttons. */
       for (b = 0; b < (int) NELEM (about_box_buttons); b++)
	 {
	   Str255 str;
	   Rect r;

	   /* Set up the rectangle enclosing each button. */
	   r.top    = CWC (ABOUT_BOX_HEIGHT - 30);
	   r.bottom = CWC (ABOUT_BOX_HEIGHT - 30 + BUTTON_HEIGHT);
	   r.left   = CW ((b * ABOUT_BOX_WIDTH / NELEM (about_box_buttons))
			  + (ABOUT_BOX_WIDTH / NELEM (about_box_buttons)
			     - BUTTON_WIDTH) / 2);
	   r.right  = CW (CW (r.left) + BUTTON_WIDTH);

	   str255_from_c_string (str, about_box_buttons[b].name);
	   about_box_buttons[b].ctl = NewControl (about_box, &r, str, TRUE, 0,
						  0, 1, pushButProc, b);
	 }
       
       about_scrollbar = NewControl (about_box, &scroll_bar_bounds, NULL, TRUE,
				     0, 0, 100, scrollBarProc, -1);
       about_te = TENew (&te_bounds, &te_bounds);
       TESetJust (teFlushLeft, about_te);
     });
}


/* Closes about box and frees up memory taken by it. */
static void
dispose_about_box (void)
{
  C_DisposeWindow (about_box);
  about_box       = NULL;
  about_scrollbar = NULL;
  dispose_license_text ();
  dispose_tips_text ();
}


/* Sets the text currently being displayed. */
static void
set_text (char *text)
{
  TESetText ((Ptr) text, strlen (text), about_te);
  SetCtlMax (about_scrollbar,
	     MAX (0, (TE_N_LINES (about_te)
		      - ((TE_HEIGHT - 2) / TE_LINE_HEIGHT (about_te)))));
  SetCtlValue (about_scrollbar, 0);
  TE_DEST_RECT (about_te) = TE_VIEW_RECT (about_te);
  InvalRect (&TE_VIEW_RECT (about_te));
}


/* Specifies which button is currently pressed. */
static void
set_current_button (int button)
{
  int i;
  set_text (about_box_buttons[button].text);
  for (i = 0; i < (int) NELEM (about_box_buttons); i++)
    HiliteControl (about_box_buttons[i].ctl, (i == button) ? 255 : 0);
}


static void
draw_mem_string (char *s, int y)
{
  MoveTo ((ABOUT_BOX_WIDTH - 9 - TextWidth ((Ptr) s, 0, strlen (s))), y);
  DrawText_c_string (s);
}


static void
draw_status_info (boolean_t executor_p)
{
  char total_ram_string[128];
  char applzone_ram_string[128];
  char syszone_ram_string[128];
  boolean_t gestalt_success_p;
  LONGINT total_ram;
  const char *ram_tag, *system_ram_tag, *application_ram_tag;

  if (executor_p)
    {
      ram_tag = "Emulated RAM: ";
      system_ram_tag = "System RAM free: ";
      application_ram_tag = "Application RAM free: ";
    }
  else
    {
      ram_tag = "";
      system_ram_tag = "";
      application_ram_tag = "";
    }

  /* Compute a string for total RAM. */
#define MB (1024 * 1024U)
  gestalt_success_p = (C_GestaltTablesOnly (gestaltLogicalRAMSize, &total_ram)
		       == noErr);
  total_ram = CL (total_ram);
  if (gestalt_success_p)
    sprintf (total_ram_string, "%s%u.%02u MB", ram_tag,
	     total_ram / MB, (total_ram % MB) * 100 / MB);
  else
    sprintf (total_ram_string, "%s??? MB", ram_tag);

  /* Compute a string for ApplZone RAM. */
  ZONE_SAVE_EXCURSION
    (ApplZone,
     {
       sprintf (applzone_ram_string, "%s%lu KB / %u KB", application_ram_tag,
		FreeMem () / 1024UL, ROMlib_applzone_size / 1024U);
     });

  /* Compute a string for SysZone RAM. */
  sprintf (syszone_ram_string, "%s%lu KB / %u KB", system_ram_tag,
	   FreeMemSys () / 1024UL, ROMlib_syszone_size / 1024U);

  draw_mem_string (total_ram_string,    20);
  draw_mem_string (syszone_ram_string,  36);
  draw_mem_string (applzone_ram_string, 52);
}


static void
event_loop (boolean_t executor_p)
{
  static Rect frame_rect = {
    CWC (TE_TOP),
    CWC (TE_LEFT),
    CWC (TE_BOTTOM),
    CWC (TE_RIGHT - SCROLL_BAR_WIDTH + 1)
  };
  EventRecord evt;
  boolean_t done_p;
  int which_text;
  Rect te_dest_rect;
  int old_scroll_bar_value;

  /* Make sure our drawing mode is sane. */
  ForeColor (blackColor);
  BackColor (whiteColor);
  PenNormal ();

  te_dest_rect = TE_DEST_RECT (about_te);

  set_current_button (0);
  which_text = 0;
  InvalRect (&about_box->portRect);

  old_scroll_bar_value = GetCtlValue (about_scrollbar);

  for (done_p = FALSE; !done_p; )
    {
      GetNextEvent ((  mDownMask | mUpMask
		     | keyDownMask | keyUpMask | autoKeyMask
		     | updateMask | activMask), &evt);
      
      TEIdle (about_te);
      
      switch (CW (evt.what))
	{
	case updateEvt:
	  BeginUpdate (about_box);
	  PenNormal ();
	  ForeColor (blackColor);
	  BackColor (whiteColor);
	  EraseRect (&about_box->portRect);
	  TextFont (helvetica);
	  TextSize (24);
	  MoveTo (TE_LEFT, 30);
	  if (executor_p)
	    DrawText_c_string ((char *) ROMlib_executor_full_name);
	  else
	    DrawText_c_string ((char *) "Carbonless Copies Runtime System");
	  TextSize (12);
	  MoveTo (TE_LEFT, 49);
	  DrawText_c_string (COPYRIGHT_STRING_1);
	  MoveTo (TE_LEFT, 62);
	  DrawText_c_string (COPYRIGHT_STRING_2);
	  draw_status_info (executor_p);
	  FrameRect (&frame_rect);
	  TEUpdate (&te_dest_rect, about_te);
	  DrawControls (about_box);
	  EndUpdate (about_box);
	  break;

	case keyDown:
	case autoKey:
	  {
	    char ch;
	    
	    ch = CL (evt.message) & 0xFF;
	    switch (ch)
	      {
	      case '\r':
	      case NUMPAD_ENTER:
		done_p = TRUE;
		break;
	      default:
		TEKey (ch, about_te);
		break;
	      }
	  }
	  break;
	  
#define _FindControl(arg0, arg1, arg2)		\
  ({						\
    int16 retval;				\
    HIDDEN_ControlHandle bogo_c;		\
						\
    retval = FindControl (arg0, arg1, &bogo_c);	\
    *(arg2) = MR (bogo_c.p);			\
						\
    retval;					\
  })

	case mouseDown:
	  {
	    Point local_pt;
	    boolean_t control_p;
	    ControlHandle c;
	    
	    local_pt = evt.where;
	    GlobalToLocal (&local_pt);
	    local_pt = SWAP_POINT (local_pt);
	    
	    control_p = _FindControl (local_pt, about_box, &c);
	    if (!control_p)
	      SysBeep (1);
	    else
	      {
		if (c == about_scrollbar)
		  {
		    int new_val, delta;
		    INTEGER part;

		    part = TestControl (c, local_pt);

		    if (TrackControl (c, local_pt,
				      (part == inThumb
				       ? (ProcPtr) -1
				       : scroll_bar_callback))
			== inThumb)
		      {
			new_val = GetCtlValue (about_scrollbar);
			delta = new_val - old_scroll_bar_value;
			if (delta != 0)
			  {
			    TEScroll (0, -delta * TE_LINE_HEIGHT (about_te),
				      about_te);
			  }
		      }

		    old_scroll_bar_value = GetCtlValue (about_scrollbar);
		  }
		else if (TrackControl (c, local_pt, (ProcPtr) -1)
			 == inButton)
		  {
		    int new_text;

		    new_text = CTL_REF_CON (c);
		    if (new_text != which_text)
		      {
			if (!strcmp (about_box_buttons[new_text].name,
				     DONE_BUTTON_NAME))
			  done_p = TRUE;
			else
			  {
			    set_current_button (new_text);
			    which_text = new_text;
			  }
		      }
		  }
	      }
	  }
	  break;

	case activateEvt:
	case mouseUp:
	case keyUp:
	  break;
	}
    }
}


void
do_about_box (void)
{
  static boolean_t busy_p = FALSE;

  if (!busy_p)
    {
      int days_left;

      busy_p = TRUE;  /* Only allow one about box at a time. */

      if (scroll_bar_callback == 0)
	scroll_bar_callback = (ProcPtr) SYN68K_TO_US(callback_install (scroll_stub, NULL));
      
      days_left = days_left_func ();
      ZONE_SAVE_EXCURSION
	(SysZone,
	 {
	   create_about_box (days_left);
       
	   THEPORT_SAVE_EXCURSION
	     (about_box,
	      {
		C_ShowWindow (about_box);
		event_loop (strncasecmp (ROMlib_appname, EXECUTOR_NAME,
					 sizeof EXECUTOR_NAME - 1) == 0);
	      });

	   TEDispose (about_te);
	   about_te = NULL;
	   dispose_about_box ();
	 });

      busy_p = FALSE;
      if (days_left <= 0)
	C_ExitToShell ();
    }
}

#endif /* SUPPORT_ABOUT_EXECUTOR_BOX */
