/******************************************************************************
  
  ISCAN_I.PXP - Image file scanner demonstration
  Written for Autodesk 3D Studio 3.0 by Tom Hudson, 1993
   
  (C) Copyright 1994 by Autodesk, Inc.

  This program is copyrighted by Autodesk, Inc. and is licensed to you under
  the following conditions.  You may not distribute or publish the source
  code of this program in any form.  You may incorporate this code in object
  form in derivative works provided such derivative works are (i.) are de-
  signed and intended to work solely with Autodesk, Inc. products, and (ii.)
  contain Autodesk's copyright notice "(C) Copyright 1994 by Autodesk, Inc."

  AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS.  AUTODESK SPE-
  CIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
  A PARTICULAR USE.  AUTODESK, INC.  DOES NOT WARRANT THAT THE OPERATION OF
  THE PROGRAM WILL BE UNINTERRUPTED OR ERROR FREE.

*******************************************************************************/

/* This program provides 3DS 3.0 with a visual image-file indexing
 * capability.  Shows all images in a single user-specified path or in
 * all map paths used by 3DS. */

#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include "pxp.h"
#include "dialog.h"
#include "keys.h"
#include "gif.h"

/* The following include file contains the dialog descriptions */

#include "iscan.3de"

#define _A_NORMAL 0

/* Index file header magic number */

#define INDEX_MAGIC 0xF5681115

#define FAIL(x) {fclose(stream); return(x);}

struct gif_header gif;
struct gif_image gim;

char gifsig[] = "GIF87a";
char *gifsigs[]= {"87a", "89a", NULL};


/* Program's overrides for default 'see' functions */

extern void see_imgbox(), see_string_erase(), see_thumb();

static SeeSub main_see[]=
{
   IMGBOX, see_imgbox, 
   IMAGECT, see_string_erase, 
   PATHFILE, see_string_erase, 
   DATE, see_string_erase, 
   TIME, see_string_erase, 
   SIZE, see_string_erase, 
   WIDTH, see_string_erase, 
   HEIGHT, see_string_erase, 
   ASPECT, see_string_erase, 
   GAMMA, see_string_erase, 
   COMMENT1, see_string_erase, 
   COMMENT2, see_string_erase, 
   COMMENT3, see_string_erase, 
   COMMENT4, see_string_erase, 
   IMGSL, see_thumb, 
   -1, FNULL
};

/* Program's overrides for default 'feel' functions */

extern void feel_exit(), feel_browse(), feel_buildix(), feel_dosearch(), 
feel_srchkey(), feel_report(), feel_viewimg(), 
feel_up(), feel_down(), feel_thumb(), feel_imgbox();

static FeelSub main_feel[]=
{
   EXIT, feel_exit, 
   BROWSE, feel_browse, 
   BUILDIX, feel_buildix, 
   SRCHKEY, feel_srchkey, 
   DOSEARCH, feel_dosearch, 
   REPORT, feel_report, 
   VIEWIMG, feel_viewimg, 
   IMGUP, feel_up, 
   IMGDN, feel_down, 
   IMGSL, feel_thumb, 
   IMGBOX, feel_imgbox, 
   -1, FNULL
};

#define PATH3D 0
#define PATH1 1
static int pathtype=PATH3D, allname=SRCHALL;
static char version[]="1.00";

static RadSub main_rad[]=
{
   PATHS3DS, feel_radio, &pathtype, PATH3D, 
   PATHSING, feel_radio, &pathtype, PATH1, 
   SRCHALL, feel_radio, &allname, SRCHALL, 
   SRCHNAME, feel_radio, &allname, SRCHNAME, 
   -1, FNULL, NULL, -1
};

/* Progress dialog */

static SeeSub prog_see[]=
{
   IXNAME, see_string_erase, 
   -1, FNULL
};

/* End of IPAS III Dialog */


/* Variable definitions */

static char *single_path=NULL;
static char *search_name=NULL;

/* ISCAN.IDX record structure */

typedef struct
{
   char name[13];
   short date;
   short time;
   long size;
   BitmapInfo info;
} IndexRecord;

/* In-memory file record structure */

typedef struct
{
   char name[13];		/* If empty, inactive file */
   void *path;			/* Pointer to PathList entry */
   short date;
   short time;
   long size;
   BitmapInfo info;
   void *stamp;		/* NULL if no stamp */
   void *next;
} FileList;
FileList *files=NULL;

/* Filename sort data structure */

typedef struct
{
   FileList *ptr;
} SortList;

SortList *sortfiles=NULL;

/* Number of image file records in memory */

int file_count=0;

/* Scrolling list index */

int scroll_pos=0;

/* BitmapInfo data structure 'empty' initializer */

static BitmapInfo no_info=
{"--------------------", 0, 0, 0.0, 0.0, 0, "", "", "", ""};

/* FileList data structure 'empty' initializer */

static FileList null_file=
{
   "", 0, 
   -1, -1, -1, 
{"--------------------", 0, 0, 0.0, 0.0, 0, "", "", "", ""}, 
NULL, NULL
};

/* File path data structure */

typedef struct
{
   char name[66];
   FileList *first;	/* Pointer to first file record */
   int count;			/* Number of file records */
   void *next;
} PathList;
PathList *paths=NULL;

/* Postage-stamp data structure */

typedef struct
{
   BXPColor palette[16];
   uchar image[1536];	/* 64 X 48 @ 2 pixels per byte */
} PalStamp;

/************************/

/* Standard IPAS dialog structures and stuff.  Note that since ISCAN does not
 * use standard IPAS dialogs, none of this is really used.  Because ISCAN does
 * all of its work in each session, no state info is saved, hence the state
 * structure is the minimum length. */

/* Dialog description */

DlgEntry cdialog[]=
{
   0, NULL
};

/* Version test value */

#define FILE_VERSION 0x0000

typedef struct
{
   ulong version;
} State;
/* function prototypes */
static void do_user_interaction();
static void maindlg_special(void);
static void clean_up();
static void free_path_list();
static void free_file_list();
static void init_search(int draw);
static void do_search(char *wild, char *search1, char *search2, char *search3);
static void display_found_files(int draw);
static void display_file_info(FileList *i, int draw);
static void generate_report(void);
static void add_report_line(char *text);
static void view_image(void);
static void display_stamps();
static void show_stamp_row(int rownum, int index);
static void sort_files(void);
static void update_slider();
static void format_date(char *out, int date);
static void format_time(char *out, int time);
static void keyboard_on();
static void keyboard_off();
static int kbabort(void);
static void purge_keyboard(void);

/* the "state" struct MUST start with a "ulong" which is the version#, to
   prevent using data from old versions of this program.  This verification is
   performed automatically. */

static State init_state = { FILE_VERSION };
static State state = { FILE_VERSION };

/*----------------------------------------------------------------*/

/* String to upper case */

char * strupr(char *s)
{
   char *t = s;
   while(*s)
   {
      *s = toupper(*s);
      s++;
   }
   return(t);
}

/* Case-insensitive string compare */

int stricmp(const char *s1, const char *s2)
{
   for( ; toupper(*s1) == toupper(*s2); s1++, s2++)
   {
      if(*s1==0)
	 return(0);
   }
   return(toupper(*s1)-toupper(*s2));
}

    
/* This function informs 3DS that ISCAN does NOT use the initial dialog
 * function provided by 3DS. */

int ClientUsesInitDialog(void)
{
   return(0);
}

/* Standard state setting function, not used in this program. */

void ClientSetStateVar(int id, void *ptr)
{
   OVL o;
   ulong *ul;
   char *s;
   
   ul=(ulong *)ptr;
   s=(char *)ptr;
   o.ul = *ul;
}

/* Standard state getting function, not used in this program. */

ulong ClientGetStateVar(int id)
{
   OVL o;
   return(o.ul);
}

/* This function normally returns the size of the various variables in the
 * state structure, but here it does nothing. */


int ClientVarSize(int id)
{
   return(1);
}

/* Standard state getter. */

char  *ClientGetState(int *size)
{
   *size = sizeof(State);
   return((char *)&state);
}

/* Standard state reset. */

void ClientResetState()
{	
   state = init_state;	
}

/* This function starts the whole thing.  If the user is running a version of
 * 3DS that is less than version 3.00, it bails out with an error message.
 * Otherwise, is performs a NO-OP function and continues into the main program
 * loop. */

void ClientStartup(EXPbuf *buf)
{
   int version=studio_version();
   
   if(version<300)
   {
      strcpy(buf->data.string, "ERROR: Requires 3DS version 3.0 or greater");
      buf->opcode=EXP_CONTINUE;
      buf->usercode=EXP_TERMINATE;
   }
   else
   {
      buf->opcode=EXP_NOP;
      buf->usercode=0x0200;	
   }
}

/* Here is the main function -- it gets the usercode 200 from the previous
 * function (with its NO-OP operation) and calls the user interaction
 * handler. */

void ClientUserCode(EXPbuf *buf)
{
   switch(buf->usercode)
   {
   case 0x0200:
      do_user_interaction();
      buf->opcode=EXP_TERMINATE;
      buf->usercode=EXP_TERMINATE;
      break;
      
   default:
      buf->opcode=buf->usercode=EXP_TERMINATE;
      buf->status=0;
      break;
   }
}

/* Standard termination function.  Does nothing. */

void ClientTerminate(void)
{ 
   /* free any data structures, etc. */
}

/* Standard built-in dialog entry pointer return.  Not used. */

DlgEntry *ClientDialog(int n)
{	
   return(&cdialog[n]); 
}

/* Because ISCAN can function in any 3DS module, it is considered to be a
 * 'universal' PXP.  Therefore, this function returns 1.  Normally, a PXP can
 * only operate in the Editor, but this call enables a PXP that does not
 * depend on the Editor code to run from any 3DS module. */

int ClientIsUniversal(void)
{
   return(1);
}

/* Here's where ISCAN saves the 3DS palette when it starts up.  When done, 
 * it sets it all back. */

BXPColor palsave[256];

/* The main ISCAN function.  This is the main code which handles the ISCAN
 * 'control panel' dialog. */


void do_user_interaction()
{

   /* Make sure they're running a large enough display */

   if(GI->sc_width<640 || GI->sc_height<480)
   {
      gfx_continu_line("ISCAN requires a display of at least 640x480 pixels");
      return;
   }

   /* If paletted and less than 256 colors, we bail out */

   if(GI->paletted && GI->colors<256)
   {
      gfx_continu_line("ISCAN requires a display with at least 256 colors");
      return;
   }

   /* Save the 3DS palette to a hold area */

   gfx_pal_get(0, 256, palsave);

   /* Prep the main control panel dialog for use */

   init_dialog(maindlg, mainedit, NULL);
   ready_dialog(maindlg, mainedit, main_see, main_feel, main_rad, NULL, NULL);

   /* The editables are defined backwards for TAB, this is a cheap fixup */

   flop_edit_tab_order(maindlg);

   /* Do any special initialization for the main dialog */

   maindlg_special();

   /* No matter what, the main dialog should be 640x480 pixels */

   maindlg[0].width=640;
   maindlg[0].height=480;

   /* Center the dialog on the screen */

   center_dialog(maindlg);

   /* Special alignment if screen is 640x480 */

   if(GI->sc_width==640)
      maindlg[0].sx=0;
   if(GI->sc_height==480)
      maindlg[0].sy=0;

   /* Erase the screen so our palette operations don't drive user nuts */

   gfx_clearscreen();

   /* Draw the main dialog */

   draw_dialog(maindlg);

   /* Call the dialog package to handle the user interaction with controls */

   do_dialog(maindlg, NAME);

   /* We're all done at this point, clear the screen before redrawing 3DS */

   gfx_clearscreen();

   /* Restore the 3DS palette */

   gfx_pal_set(0, 256, palsave);

   /* Redraw the 3DS display */

   gfx_redraw();

   /* Free up memory, etc. before exiting */

   clean_up();
}

/* User-defined dialog control functions */

/* This special setup function initializes the dialog to its startup state,
 * initializing the various editables, paths, etc.  This is only done once.
 */

void maindlg_special(void)
{
   char path[66], file[13];
   Editable *e;

   init_editable(&maindlg[NAME], "*.*");
   gfx_get_paths(GFX_IMG_PATH, 0, path, file);
   init_editable(&maindlg[USERPATH], path);
   init_editable(&maindlg[KEY1], "");
   init_editable(&maindlg[KEY2], "");
   init_editable(&maindlg[KEY3], "");
   e=(Editable *)maindlg[USERPATH].text;
   single_path=e->string;
   e=(Editable *)maindlg[NAME].text;
   search_name=e->string;
   strcpy(maindlg[VERSION].text, version);
   init_search(0);
}

/* Free any allocated memory */

void clean_up()
{
   if(sortfiles)
   {
      free(sortfiles);
      sortfiles=NULL;
   }
   free_path_list();
   free_file_list();
}

/* This function is called when the user clicks on the exit button or presses
 * the key equivalent (ESC).  The user is prompted as to whether or not the
 * program is to exit; if so, this function sets a flag that tells the dialog
 * package that the user interaction is complete. */

void feel_exit(Dialog *d, int mouse)
{
   int status;

   if(mouse)
      if(!(press_button(d)))
	 return;
   gfx_yes_no_line("Exit Image Scanner?", status);
   if(status)
      dialog_done=1;
}

/* This function displays the scrolling postage-stamp image box. */

void see_imgbox(Dialog *d)
{
   display_stamps();
}



/* split_fn */

void split_fn(char *path, char *file, char *pf)
{
   int ix, jx, bs_loc, fn_loc;

   if(strlen(pf)==0)
   {
      if(path)
	 *path=0;
      if(file)
	 *file=0;
      return;
   }
   bs_loc=strlen(pf);
   for(ix=bs_loc-1; ix>=0; --ix)
   {
      if(pf[ix]=='\\')
      {
	 bs_loc=ix;
	 fn_loc=ix+1;
	 goto do_split;
      }
      if(pf[ix]==':')
      {
	 bs_loc=ix+1;
	 fn_loc=ix+1;
	 goto do_split;
      }
   }
   bs_loc= -1;
   fn_loc=0;

 do_split:
   if(file)
      strcpy(file, &pf[fn_loc]);
   if(path)
   {
      if(bs_loc>0)
      {
	 for(jx=0; jx<bs_loc; ++jx)
	    path[jx]=pf[jx];
	 path[jx]=0;
      }
      else
	 path[0]=0;
   }
}


/* This function calls the 3DS file selector to request a path to use for
 * single-path image scanning.  Only the path is used (the filename portion
 * of the returned path/file string is discarded) -- the path is placed
 * into the USERPATH editable, the editable is redrawn, then the 'single
 * path' button is activated and the search parameters are initialized. */

void feel_browse(Dialog *d, int mouse)
{  
   char exts[11][4]=
{"", "", "", "", "", "", "", "", "", "", ""};
   char path[66], selection[81];

   if(mouse)
      if(!(press_button(d)))
	 return;

   strcpy(path, single_path);
   gfx_file_selector("Select single path to search", path, "FILENAME.EXT", exts, 
		     selection);
   if(strlen(selection))
   {
      split_fn(path, NULL, selection);
      init_editable(&maindlg[USERPATH], path);
      draw_item(&maindlg[USERPATH]);
      feel_radio(&maindlg[PATHSING], 0);
      init_search(0);
   }
}

/* This function is called when the user clicks on the 'Build Index'
 * button on the main control panel.  It brings up the 3DS file selector, 
 * and when the user selects a path, builds an index file in that path. */

void feel_buildix(Dialog *d, int mouse)
{  
   char exts[11][4]= {"", "", "", "", "", "", "", "", "", "", ""};
   char path[66], selection[81];

   if(mouse)
      if(!(press_button(d)))
	 return;

   strcpy(path, single_path);
   gfx_file_selector("Select single path to build index", path, "FILENAME.EXT", exts, 
		     selection);
   if(strlen(selection))
   {
      split_fn(path, NULL, selection);
      build_index(path);
   }
}

int goodfiles;

/* This function builds an index file for a particular image path.  First, it
 * creates an ISCAN.IDX file in the path, then builds a list of the image
 * files in that path.  While it does this, it displays a gas gauge dialog
 * reflecting the progress of the operation.  When the directory scanning is
 * complete, it returns a status value to the caller. */

int build_index(char *ipath)
{
   int status, total, index, width;
   FNdata file;
   char search[85], idx_name[81];
   FILE *stream;
   long magic=INDEX_MAGIC;
   Dialog *d= &Progress[IXBAR];
   char exts[11][4]= {"IDX", "", "", "", "", "", "", "", "", "", ""};

   sprintf(idx_name, "%s\\iscan.idx", ipath);
   if((stream=fopen(idx_name, "wb"))==NULL)
   {
      gfx_continu_line("Can't create index file");
      return(0);
   }

   if(fwrite(&magic, 4, 1, stream)!=1)
      goto error;

   sprintf(search, "%s\\*.*", ipath);

   /* First, count the number of files */

   goodfiles=total=0;
   gfx_findfirst(search, _A_NORMAL, &file, status);
   while(status==0)
   {
      total++;
      gfx_findnext(&file, status);
   }

   /* Ready and draw our progress dialog */
   init_dialog(Progress, NULL, NULL);
   ready_dialog(Progress, NULL, prog_see, NULL, NULL, NULL, NULL);
   center_dialog(Progress);
   strcpy(Progress[IXNAME].text, "");
   save_under_dialog(Progress);
   draw_dialog(Progress);

   index=0;
   gfx_findfirst(search, _A_NORMAL, &file, status);
   while(status==0)
   {
      if(kbabort())
	 goto error;
      strcpy(Progress[IXNAME].text, file.name);
      draw_item_number(Progress, IXNAME);
      index++;
      width=(d->width-2)*index/total; 
      gfx_cblock(d->sx+1, d->sy+1, width, d->height-2, RED);
      if(add_index(stream, ipath, &file)<0)
      {
      error:
	 fclose(stream);
	 remove(idx_name);
	 gfx_put_hole();	/* Erase progress dialog */
	 return(0);
      }
      gfx_findnext(&file, status);
   }
   fclose(stream);
   gfx_put_hole();		/* Erase progress dialog */
   if(goodfiles==0)
   {
      remove(idx_name);
      gfx_continu_2line("No recognizable images in that directory.", "Index build cancelled.");
   }
   return(1);
}

/* split_fext */

void split_fext(char *fe, char *f, char *e)
{
   int ix;
   char ext[13], wkname[13];

   split_fn(NULL, wkname, fe);
   ix=0;
   while(wkname[ix]!='.' && wkname[ix]!=0)
      ++ix;
   if(wkname[ix]=='.')
   {
      strcpy(ext, &wkname[ix]);
      ext[4]=0;
   }
   else
      ext[0]=0;
   wkname[ix]=wkname[8]=0;
   if(f)
      strcpy(f, wkname);
   if(e)
      strcpy(e, ext);
}

/* get_gif_size */

static int get_gif_size(char *fullname, int *w, int *h)
{
   int ix, gif_colors;
   char c;
   FILE *stream;
	
   if((stream=fopen(fullname, "rb"))==NULL)
      return(0);

   if(fread(&gif, sizeof(gif), 1, stream)!=1)
      FAIL(-1);
   if(strncmp(gif.giftype, gifsig, 3)!=0)
      FAIL(-2);
	
   /* Check for valid GIF signatures */
	
   for(ix=0; gifsigs[ix]!=NULL; ++ix)
   {
      if(strncmp(gif.giftype+3, gifsigs[ix], 3)==0)
	 goto SIG_OK;
   }
   FAIL(-3);
	
 SIG_OK:
   gif_colors = (1<<((gif.colpix&PIXMASK)+1));
   if(gif.colpix&COLTAB)
      fseek(stream, gif_colors*3, SEEK_CUR);
   for (;;)   /* skip over extension blocks and other junk til get ', ' */
   {
      if(fread(&c, 1, 1, stream)!=1)
	 FAIL(-4);
      if (c == ', ')		break;
      if (c == ';')		/* semi-colon is end of piccie */
	 FAIL(-5);
      if (c == '!')		/* extension block */
      {
	 if(fread(&c, 1, 1, stream)!=1)
	    FAIL(-6);
	 for (;;)	 {
	    if(fread(&c, 1, 1, stream)!=1)
	       FAIL(-7);
	    if (c == 0)		/* zero 'count' means end of extension */
	       break;
	    fseek(stream, c, SEEK_CUR);
	 }
      }
   }
   if(fread(&gim, sizeof(gim), 1, stream)!=1)
      FAIL(-8);

   *w=gim.w;
   *h=gim.h;
   fclose(stream);
   return(1);
}


/* do_bitmap_info */

void do_bitmap_info(char *fullname, BitmapInfo *bi, int *status)
{
   int w, h, result;
   char fe[13], ext[5]; 

   gfx_bitmap_info(fullname, bi, *status);
   if(*status==1)
   {
      split_fn(NULL, fe, fullname);
      split_fext(fe, NULL, ext);
      if(stricmp(ext, ".gif")==0)
      {
	 result=get_gif_size(fullname, &w, &h);
	 if(result>0)
	 {
	    bi->width=w;
	    bi->height=h;
	 }
      }
   }
}

/* This function is the heart of the image index file build process.  It is
 * given a file handle pointing to the index file, a path and a filename to
 * consider.  It asks 3DS to return information for the bitmap specified by
 * the file path/name provided, and if 3DS recognizes it as a valid image,
 * this function loads the bitmap and creates a 16-color postage stamp image
 * (64x48 pixels), which it then writes out to the index file.
 *
 * Special note: 3DS 3.00 contains a minor bug which causes subsequent
 * gfx_resize_bitmap calls to fail if the user cancels a resize operation via
 * the ESC key.  For this reasom, ISCAN turns off the keyboard using the
 * keyboard_off() function before calling gfx_resize_bitmap, then turns the
 * keyboard on again and purges any keys. */

int add_index(FILE *stream, char *path, FNdata *file)
{
   int status;
   BitmapInfo bi;
   char fullname[81];
   static BXPColor *maptrue=NULL;
   IndexRecord ir;

   sprintf(fullname, "%s\\%s", path, file->name);
   bi.width=bi.height=0;
   do_bitmap_info(fullname, &bi, &status);

   if(status && bi.width!=0 && bi.height!=0)
   {
      strcpy(ir.name, file->name);
      ir.date=file->date;
      ir.time=file->time;
      ir.size=file->size;
      ir.info=bi;
      if((maptrue=malloc(bi.width*bi.height*3))!=NULL)
      {
	 gfx_load_bitmap(fullname, bi.width, bi.height, maptrue, status);
	 if(status==1)
	 {
	    uchar *pstamp, *pstamp2;
	    BXPColor palette[16];
	    if((pstamp=malloc(64*48*3))!=NULL)
	    {
	       int ix, iy;
	       keyboard_off();
	       purge_keyboard();
	       gfx_resize_bitmap(maptrue, bi.width, bi.height, pstamp, 
				 64, 48, status);
	       purge_keyboard();
	       keyboard_on();
	       if(fwrite(&ir, sizeof(IndexRecord), 1, stream)!=1)
	       {
	       fail:
		  free(maptrue);
		  free(pstamp);
		  return(-1);
	       }
	       gfx_calc_palette(pstamp, 64, 48, 16, palette);
	       gfx_24_to_8_bit(pstamp, 64, 48, 16, palette, pstamp, status);
	       if(fwrite(palette, 16*3, 1, stream)!=1)
		  goto fail;
	       /* Reorient paletted stamp */
	       pstamp2= &pstamp[64*48];
	       for(iy=0; iy<48; ++iy)
	       {
		  for(ix=0; ix<64; ++ix)
		     pstamp2[ix*48+iy]=pstamp[iy*64+ix];
	       }
	       /* Compress stamp to 2 pixels per byte */
	       for(ix=0, iy=0; ix<1536; ++ix, iy+=2)
		  pstamp2[ix]=(pstamp2[iy]<<4) | (pstamp2[iy+1]);
	       if(fwrite(pstamp2, 1536, 1, stream)!=1)
		  goto fail;
	       goodfiles++;
	       free(maptrue);
	       free(pstamp);
	       return(1);
	    }
	 }
	 else
	 {
	    sprintf(gp_buffer, "Failed to load [%s] (%dx%d), error %d", file->name, bi.width, bi.height, status);
	    gfx_debug_print(gp_buffer);
	 }
	 free(maptrue);
      }
   }
   return(0);
}

/* This function is called when the user clicks on the 'Keyword(s)' button.
 * The 'radio' field is inverted, causing the button state to toggle.*/

void feel_srchkey(Dialog *d, int mouse)
{
   if(mouse)
      if(!(press_button(d)))
	 return;
   d->radio=1-d->radio;
   see_button(d);
}

/* This function is called when the user clicks on the 'Do Search' button.
 * Ths button state is turned on, the program checks the control panel
 * settings for validity, then initializes and performs the search.  When
 * done, the button state is turned back off. */

void feel_dosearch(Dialog *d, int mouse)
{
   char *srchid;
   char *k1=NULL, *k2=NULL, *k3=NULL;
   Editable *e;

   if(mouse)
      if(!(press_button(d)))
	 return;

   d->radio=1;
   see_button(d);

   switch(allname)
   {
   case SRCHALL:
      srchid="*.*";
      break;
   case SRCHNAME:
      if(strlen(search_name)==0)
      {
	 gfx_continu_line("Please enter a filename wildcard");
	 goto done;
      }
      srchid=search_name;
      break;
   }
   if(maindlg[SRCHKEY].radio)
   {
      e=(Editable *)(maindlg[KEY1].text);
      if(strlen(e->string))
	 k1=(char *)(e->string);
      e=(Editable *)(maindlg[KEY2].text);
      if(strlen(e->string))
	 k2=(char *)(e->string);
      e=(Editable *)(maindlg[KEY3].text);
      if(strlen(e->string))
	 k3=(char *)(e->string);
      if(k1==NULL && k2==NULL && k3==NULL)
      {
	 gfx_continu_line("Please enter at least one keyword for search");
	 goto done;
      }
   }
   init_search(1);
   do_search(srchid, k1, k2, k3);

 done:
   d->radio=0;
   see_button(d);
}


/* This function initializes the search operation, and optionally redraws
 * the postage-stamp display. */

void init_search(int draw)
{
   scroll_pos=0;
   free_path_list();
   free_file_list();
   display_found_files(draw);
}

/* This function frees the linked list of paths used by the program. */

void free_path_list()
{
   PathList *p=paths, *np;

   while(p)
   {
      np=p->next;
      free(p);
      p=np;
   }
   paths=NULL;
}

/* This function frees the linked list of files currently in memory. */

void free_file_list()
{
   FileList *f=files, *nf;
   
   while(f)
   {
      nf=f->next;
      if(f->stamp)
	 free(f->stamp);
      free(f);
      f=nf;
   }
   files=NULL;
   file_count=0;
}

/* This function performs the search operation for each path under
 * consideration by the program. */

void do_search(char *wild, char *search1, char *search2, char *search3)
{
   int status; 
   char path[66];
   
   if(get_first_path(path)==0)
   {
   error:
      init_search(0);
      return;
   }
 loop:
   if(search_for_files(path, wild, search1, search2, search3)<=0)
      goto error;
   status=get_next_path(path);
   switch(status)
   {
   case 1:
      goto loop;
   case -1:
      goto error;
   }
   display_found_files(1);
}


/* This function displays all the files that reside in the in-memory file
 * linked list.  These files were added to the list by the search function. */

void display_found_files(int draw)
{
   
   /* First clear out individual file info display area */
   
   display_file_info(&null_file, draw);
   
   /* Now display number of found files */
   
   sprintf(maindlg[IMAGECT].text, "%d Images Found", file_count);
   if(draw)
      draw_item_number(maindlg, IMAGECT);
   
   /* Sort names here */
   
   sort_files();
   
   /* Show postage stamps/filenames in display box */
   
   if(draw)
   {
      display_stamps();
      update_slider();
   }
}

/* This function displays the information for a file in the file
 * information display box. */

void display_file_info(FileList *i, int draw)
{
   char buf[80];
   
   if(strlen(i->name)==0)
      strcpy(buf, "--------");
   else
   {
      PathList *p=i->path;
      sprintf(buf, "%s\\%s", p->name, i->name);
      buf[70]=0;
   }
   strcpy(maindlg[PATHFILE].text, buf);
   
   if(i->date == -1)
      strcpy(buf, "--/--/--");
   else
      format_date(buf, i->date);
   strcpy(maindlg[DATE].text, buf);
   if(i->time == -1)
      strcpy(buf, "--:--:--");
   else
      format_time(buf, i->time);
   strcpy(maindlg[TIME].text, buf);
   if(i->size<0)
      strcpy(buf, "--------");
   else
   {
      sprintf(buf, "%d", i->size);
      buf[8]=0;
   }
   strcpy(maindlg[SIZE].text, buf);
   if(i->info.width<=0)
      strcpy(buf, "----");
   else
   {
      sprintf(buf, "%d", i->info.width);
      buf[4]=0;
   }
   strcpy(maindlg[WIDTH].text, buf);
   if(i->info.height<=0)
      strcpy(buf, "----");
   else
   {
      sprintf(buf, "%d", i->info.height);
      buf[4]=0;
   }
   strcpy(maindlg[HEIGHT].text, buf);
   if(i->info.aspect<=0)
      strcpy(buf, "Undef");
   else
   {
      sprintf(buf, "%.2f", i->info.aspect);
      buf[6]=0;
   }
   strcpy(maindlg[ASPECT].text, buf);
   if(i->info.gamma<=0)
      strcpy(buf, "Undef");
   else
   {
      sprintf(buf, "%.2f", i->info.gamma);
      buf[6]=0;
   }
   strcpy(maindlg[GAMMA].text, buf);
   strcpy(maindlg[COMMENT1].text, i->info.desc1);
   strcpy(maindlg[COMMENT2].text, i->info.desc2);
   strcpy(maindlg[COMMENT3].text, i->info.desc3);
   strcpy(maindlg[COMMENT4].text, i->info.desc4);
   if(draw)
   {
      draw_item_number(maindlg, PATHFILE);
      draw_item_number(maindlg, DATE);
      draw_item_number(maindlg, TIME);
      draw_item_number(maindlg, SIZE);
      draw_item_number(maindlg, WIDTH);
      draw_item_number(maindlg, HEIGHT);
      draw_item_number(maindlg, ASPECT);
      draw_item_number(maindlg, GAMMA);
      draw_item_number(maindlg, COMMENT1);
      draw_item_number(maindlg, COMMENT2);
      draw_item_number(maindlg, COMMENT3);
      draw_item_number(maindlg, COMMENT4);
   }
}

/* This function is a generic 'see' function which draws a string after
 * erasing the string's bounding box.  This prevents leftover fragments
 * of longer strings from remaining on the screen. */

void see_string_erase(Dialog *d)
{
   gfx_cblock(d->sx, d->sy, d->width, d->height, MDGRAY);
   see_string(d);
}

/* This function is called when the user clicks on the 'Report' button.  If
 * there are currently files in the in-memory linked list, it calls the
 * generate_report function. */

void feel_report(Dialog *d, int mouse)
{
   if(mouse)
      if(!(press_button(d)))
	 return;
   if(file_count==0)
   {
      gfx_continu_line("No images found");
   }
   else
      generate_report();
}

/* The following structure and fields are necessary for using the 3DS text
 * editor, which is used to generate a report of all image files in the
 * in-memory file list. */

typedef struct
{
   char *text;
   int length;
   int bufsize;
   int cursor;
} Document;

static Document doc1=
{NULL, 0, 0, 0};
static TextEd DocIO=
{(char _far *)NULL, 0, 0, 0, 0, (void _far *)NULL, 0, (void _far *)NULL};

/* This function initializes the text editor document to an empty state, 
 * which consists of a CR/LF combination.  */

void init_document(Document *d)
{
   d->text=malloc(100);
   strcpy(d->text, "\r\n");
   d->length=2;
   d->bufsize=100;
   d->cursor=0;
}

static void edit_document(Document *d);

/* This function creates a text editor document containing information on
 * all image files in the in-memory linked list.  It then saves the screen
 * palette (on paletted displays), calls the text editor interface
 * function, edit_document, and when it is done, restores the display by
 * reloading the palette and redrawing the main control panel dialog. */

void generate_report(void)
{
   int ix;
   SortList *s=sortfiles;
   BXPColor oldpal[256];
   
   init_document(&doc1);
   doc1.cursor=0;
   strcpy(doc1.text, "Image Scan File Report:\r\n\r\n");
   doc1.length=strlen(doc1.text);
   
   for(ix=0; ix<file_count; ++ix)
   {
      FileList *f=s[ix].ptr;
      PathList *p=f->path;
      BitmapInfo *i= &f->info;
      char work[81];
      
      sprintf(gp_buffer, "%s\\%s", p->name, f->name);
      add_report_line(gp_buffer);
      sprintf(gp_buffer, "File size:%d bytes  Date:", f->size);
      format_date(work, f->date);
      strcat(gp_buffer, work);
      strcat(gp_buffer, "  Time:");
      format_time(work, f->time);
      strcat(gp_buffer, work);
      add_report_line(gp_buffer);
      sprintf(gp_buffer, "Width:%d  Height:%d  Aspect:", i->width, i->height);
      if(i->aspect<=0)
	 strcat(gp_buffer, "Undefined");
      else
      {
	 sprintf(work, "%.2f", i->aspect);
	 strcat(gp_buffer, work);
      }
      strcat(gp_buffer, "  Gamma:");
      if(i->gamma<=0)
	 strcat(gp_buffer, "Undefined");
      else
      {
	 sprintf(work, "%.2f", i->gamma);
	 strcat(gp_buffer, work);
      }
      add_report_line(gp_buffer);
      if(strlen(i->desc1) || strlen(i->desc2) || strlen(i->desc3) || strlen(i->desc4))
	 add_report_line("Comments:");
      if(strlen(i->desc1))
	 add_report_line(i->desc1);
      if(strlen(i->desc2))
	 add_report_line(i->desc2);
      if(strlen(i->desc3))
	 add_report_line(i->desc3);
      if(strlen(i->desc4))
	 add_report_line(i->desc4);
      add_report_line("");
   }
   add_report_line("End of report");
   if(GI->paletted)
   {
      gfx_pal_get(0, 256, oldpal);
      gfx_clearscreen();
      gfx_pal_set(0, 256, palsave);
   }
   edit_document(&doc1);
   free(doc1.text);
   gfx_clearscreen();
   if(GI->paletted)
      gfx_pal_set(0, 256, oldpal);
   draw_dialog(maindlg);
}

/* This function provides the interface between this program and the 3DS
 * text editor.  It loads up the text document information, starts up the
 * editor, then handles the dialog between the editor and this program.
 * Because this program does not utilize text editor callbacks, any
 * nonstandard replies by the editor are considered to be invalid. */

void edit_document(Document *d)
{
   int result, start_code;
   TextEd _far *docout;
   char *newtext;
   
   start_code=TEX_STARTUP;
   
   DocIO.text=(char _far *)d->text;
   DocIO.length=d->length;
   DocIO.bufsize=d->bufsize;
   DocIO.cursor=d->cursor;
   DocIO.dropct=0;
   DocIO.toolct=0;
   
   gfx_text_editor(&DocIO, docout, start_code, result);
   switch(result)
   {
   case TEX_FAILED:
      gfx_continu_line("Text editor failed");
      save_under_dialog(maindlg);
      draw_dialog(maindlg);
      return;
   case TEX_CLOSED:
      if((newtext=malloc(docout->bufsize))==NULL)
      {
	 gfx_continu_line("No RAM for revised document");
      }
      else
      {
	 if(d->text)
	    free(d->text);
	 d->text=newtext;
	 far_to_near(d->text, docout->text, docout->length);
	 d->length=docout->length;
	 d->bufsize=docout->bufsize;
	 d->cursor=docout->cursor;
      }
      break;
   default:
      gfx_continu_line("Error, got editor callback");
      break;
   }
}

/* This function adds lines to the text editor document, automatically
 * appending CR/LF pairs to the end of each line, and dynamically
 * expanding the memory allocated to the document in 10, 000-byte
 * increments as needed. */

void add_report_line(char *text)
{
   char *new;
   if((doc1.length+strlen(text)+2)>doc1.bufsize)
   {
      new=realloc(doc1.text, doc1.bufsize+10000);
      if(new==NULL)
	 return;
      doc1.text=new;
      doc1.bufsize+=10000;
   }
   strcpy(&doc1.text[doc1.length], text);
   doc1.length+=strlen(text);
   strcpy(&doc1.text[doc1.length], "\r\n");
   doc1.length+=2;
}

/* This function is called when the user clicks on the 'View Image'
 * button.  It, in tuen, calls the view_image function to actually
 * display the image. */

void feel_viewimg(Dialog *d, int mouse)
{
   if(mouse)
      if(!(press_button(d)))
	 return;
   view_image();
}

/* If an image file currently has its information displayed in the image
 * information box, this function loads the image, sizes it to fit the
 * display, calculates an appropriate color palette if 3DS is using a
 * paletted display, then displays the image.  When the user presses a key
 * or clicks the mouse, the control panel is redisplayed. */

void view_image(void)
{
   int status, neww, newh, offx, offy;
   BitmapInfo bi;
   static BXPColor *maptrue=NULL;
   char fullname[81];
   
   strcpy(fullname, maindlg[PATHFILE].text);
   if(fullname[0]=='-')
   {
      gfx_continu_line("Please select an image to view");
      return;
   }  
   bi.width=bi.height=0;
   do_bitmap_info(fullname, &bi, &status);
   if(status && bi.width!=0 && bi.height!=0)
   {
      if((maptrue=malloc(bi.width*bi.height*3))!=NULL)
      {
	 gfx_load_bitmap(fullname, bi.width, bi.height, maptrue, status);
	 if(status==1)
	 {
	    BXPColor oldpal[256];
	    if(bi.width>GI->sc_width || bi.height>GI->sc_height)
	    {
	       float xdim, ydim;
	       
	       gfx_stand_by("Image too large -- Fitting to display size");
	       xdim=(float)bi.width/(float)GI->sc_width;
	       ydim=(float)bi.height/(float)GI->sc_height;
	       if(xdim>ydim)
	       {
		  neww=GI->sc_width;
		  newh=(int)((float)bi.height/xdim);
		  if(newh>GI->sc_height)
		     newh=GI->sc_height;
	       }
	       else
	       {
		  newh=GI->sc_height;
		  neww=(int)((float)bi.width/ydim);
		  if(neww>GI->sc_width)
		     neww=GI->sc_width;
	       }
	       keyboard_off();
	       purge_keyboard();
	       gfx_resize_bitmap(maptrue, bi.width, bi.height, maptrue, 
				 neww, newh, status);
	       purge_keyboard();
	       keyboard_on();
	       gfx_put_hole();
	    }
	    else
	    {
	       neww=bi.width;
	       newh=bi.height;
	    }
	    offx=(GI->sc_width-neww)/2;
	    offy=(GI->sc_height-newh)/2;
	    if(GI->paletted)
	    {
	       BXPColor palette[256];
	       uchar *palimage;
	       int y; 
	       
	       if((palimage=malloc(neww*newh*2))==NULL)
	       {
		  gfx_continu_line("No RAM for 256-color version");
		  goto view_fail;
	       }
	       gfx_stand_by("Computing colors");
	       gfx_calc_palette(maptrue, neww, newh, 256, palette);
	       gfx_24_to_8_bit(maptrue, neww, newh, 256, palette, palimage, status);
	       gfx_put_hole();
	       gfx_clearscreen();			
	       gfx_pal_get(0, 256, oldpal);
	       gfx_pal_set(0, 256, palette);
	       for(y=0; y<newh; y++)
		  gfx_c_blitput(&palimage[y*neww], offx, offy+y, neww, 1);
	       free(palimage);
	    }
	    else
	    {
	       int y;
	       
	       gfx_clearscreen();			
	       for(y=0; y<newh; y++)
		  gfx_tc_blitput(&maptrue[y*neww], offx, offy+y, neww, 1);
	    }
	    gfx_wait_click();
	    gfx_clearscreen();
	    if(GI->paletted)
	       gfx_pal_set(0, 256, oldpal);
	    draw_dialog(maindlg);
	 }
      }
   view_fail:
      free(maptrue);
   }
   else
      gfx_continu_line("Can't locate that image");
}

static int path_number=0;

/* This function gets the first path to be used in the image search.  If
 * the search is a single path, it returns the single path's string.  If
 * the search is using all the 3DS image and map paths, it returns the 3DS
 * image path. */

int get_first_path(char *path)
{
   char file[13];
   Editable *e;
   switch(pathtype)
   {
   case PATH1:
      e=(Editable *)maindlg[USERPATH].text;
      strcpy(path, e->string);
      return(1);
   case PATH3D:
      gfx_get_paths(GFX_IMG_PATH, 0, path, file);
      if(strlen(path)==0)
	 return(0);
      path_number=0;
      return(1);
   }
   return(0);
}

/* This function is called to get the next path for the current search
 * operation.  For single-path searches, it returns no path, since only
 * one is used, and it was returned by the get_first_path function.  If
 * the search is using all 3DS image and map paths, it returns the
 * appropriate map path. */

int get_next_path(char *path)
{
   char file[13];
   switch(pathtype)
   {
   case PATH1:
      return(0);	/* Only one path used */
   case PATH3D:
      if(path_number>255)
	 return(0);
      gfx_get_paths(GFX_MAP_PATH, path_number, path, file);
      if(strlen(path)==0)
	 return(0);
      path_number++;
      return(1);
   }
   return(-1);
}

/* This is a list of file extensions that are known to not be valid image
 * file types.  By using this list to quickly discard non-image files, the
 * ISCAN program can speed up search operations. */

static char *non_images[]=
{
   ".exe", ".ixp", ".pxp", ".axp", ".sxp", ".kxp", ".bxp", 
   ".txt", ".doc", ".idx", ".com", ".sys", ".set", ".bat", 
   ".bak", ".dat", ".col", ".loc", ".stb", ".inf", ".prm", 
   ".3ds", ".lft", ".shp", ".c", ".obj", 
   NULL
};

/* This function searches the given path for all valid image files matching
 * the search criteria.  It also adds the path string to the linked list of
 * paths, so that the path may be referenced later.  If an index file is
 * present in the path, it is loaded so that search operations may be
 * speeded up -- Files which are present in the index file (matched by
 * name, date and time stamp) need not be scanned in detail, as all their
 * information is present in the index file.  Valid image files without
 * corresponding index file records must be accessed via the IPAS III
 * gfx_bitmap_info function, and these files don't have postage-stamp
 * images. */

int search_for_files(char *path, char *wild, char *s1, char *s2, char *s3)
{
   PathList *p;
   FileList *f, *pf, *nf; 
   char search[81], ext[5];
   int ix, status, count;
   FNdata file;
   FILE *stream;
   long magic;
   IndexRecord ir;
   
   sprintf(gp_buffer, "Searching path [%s]", path);
   gfx_stand_by(gp_buffer);
   
   /* Add path to path list */
   
   if((p=malloc(sizeof(PathList)))==NULL)
   {
      gfx_put_hole();
      return(0);
   }
   p->next=paths;
   paths=p;
   strcpy(p->name, path);
   p->first=NULL;
   p->count=0;
   
   /* Add all files from path to file list */
   
   f=NULL;
   sprintf(search, "%s\\%s", path, wild);
   gfx_findfirst(search, _A_NORMAL, &file, status);
   while(status==0)
   {
      if((f=malloc(sizeof(FileList)))==NULL)
      {
      fail:
	 gfx_put_hole();
	 return(0);
      }
      p->count++;
      strcpy(f->name, file.name);
      f->path=p;
      f->date=file.date;
      f->time=file.time;
      f->size=file.size;
      f->info=no_info;
      f->stamp=NULL;
      f->next=files;
      files=f;
      file_count++;
      gfx_findnext(&file, status);
   }
   
   /* Save the pointer to the last added (first for this path) */
   p->first=f;
   
   /* Look for ISCAN.IDX file in path.  If it's there, load it	*/
   /* and match its records to those files in memory				*/
   
   if(p->first==NULL)	/* No files to worry about */
      goto done;
   
   sprintf(search, "%s\\iscan.idx", path);
   if((stream=fopen(search, "rb"))!=NULL)
   {
      if(fread(&magic, 4, 1, stream)!=1)
      {
      error:
	 gfx_put_hole();
	 gfx_continu_line("Error reading index file");
	 goto ix_done;
      }
      if(magic!=INDEX_MAGIC)
      {
      badfile:
	 gfx_put_hole();
	 gfx_continu_2line("Invalid index file in path:", path);
	 goto ix_done;
      }
   match_loop:
      if(kbabort())
      {
	 gfx_put_hole();
	 return(0);
      }
      if(fread(&ir, sizeof(IndexRecord), 1, stream)!=1)
      {
      ix_done:
	 fclose(stream);
	 goto final_cleanup;
      }
      f=p->first;
      count=p->count;
      while(count)
      {
	 if(stricmp(ir.name, f->name)==0 && ir.date==f->date && ir.time==f->time)
	 {
	    f->info=ir.info;
	    if((f->stamp=malloc(sizeof(PalStamp)))==NULL)
	    {
	       static int warned=0;
	       if(warned==0)
	       {
		  gfx_continu_line("Out of memory for postage-stamp")
		     warned=1;
	       }
	       fseek(stream, sizeof(PalStamp), SEEK_CUR);
	    }
	    else
	    {
	       if(fread(f->stamp, sizeof(PalStamp), 1, stream)!=1)
		  goto badfile;
	    }
	    goto match_loop;
	 }
	 f=f->next;
	 count--;
      }
      
      /* No match, must seek past postage-stamp */
      fseek(stream, sizeof(PalStamp), SEEK_CUR);
      
      goto match_loop;
   }
   
   /* Now, those files which don't have index records must be scanned */
   
 final_cleanup:
   f=p->first;
   pf=NULL;
   count=p->count;
   while(count)
   {
      int keystat, key;
      gfx_key_hit(keystat);
      if(keystat)
      {
	 gfx_get_key(key);
	 if(key==ESC)
	 {
	    gfx_put_hole();
	    return(0);
	 }
      }
      if(f->info.width==0)
      {
	 /* Ignore known filetypes that aren't images */
	 split_fext(f->name, NULL, ext);
	 ix= -1;
	 while(non_images[++ix]!=NULL)
	 {
	    if(stricmp(non_images[ix], ext)==0)
	       goto delete_non_image;
	 }
	 sprintf(search, "%s\\%s", path, f->name);
	 f->info.width=f->info.height=0;
	 do_bitmap_info(search, &f->info, &status);
	 
	 /* If didn't get info, it's not an image -- delete it */
	 
	 if(status==0 || f->info.width==0 || f->info.height==0)
	 {
	 delete_non_image:
	    if(pf)
	       pf->next=f->next;
	    else
	    {
	       p->first=f->next;
	       if(files==f)
		  files=f->next;
	    }
	    file_count--;
	    p->count--;
	    nf=f->next;
	    free(f);
	    f=nf;
	    goto next;
	 }
      }
      
      if(match_keywords(f, s1, s2, s3)==0)
	 goto delete_non_image;
      
      pf=f;
      f=f->next;
      
   next:
      count--;
   }
   
 done:
   gfx_put_hole();
   return(1);
}

/* This function performs keyword matching on a given file in the file
 * linked list. */

int match_keywords(FileList *f, char *s1, char *s2, char *s3)
{
   int a, b, c;
   
   a=match_1_keyword(f, s1);
   b=match_1_keyword(f, s2);
   c=match_1_keyword(f, s3);
   
   return((a && b && c) ? 1:0);
}

/* This function checks all four description fields for keyword matches. */

int match_1_keyword(FileList *f, char *s)
{
   if(s==NULL)
      return(1);
   if(strlen(s)==0)
      return(1);
   if(parse_and_match(s, f->info.desc1))
      return(1);
   if(parse_and_match(s, f->info.desc2))
      return(1);
   if(parse_and_match(s, f->info.desc3))
      return(1);
   if(parse_and_match(s, f->info.desc4))
      return(1);
   return(0);
}

/* This function checks a single string for keyword matches. */

int parse_and_match(char *s, char *desc)
{
   int len=strlen(desc), matlen=strlen(s);
   char *wp=desc, *wp2, word[81], keyword[81];
   
   if(len==0 || matlen==0)
      return(0);
   
   strcpy(keyword, s);
   strupr(keyword);
   
 word_loop:
   strcpy(word, "");
   wp2=word;
   while(*wp==' ')
      wp++;
   if(*wp==0)
      return(0);
   /* At start of a word */
   while(*wp!=' ' && *wp!=0)
   {
      *wp2++ = *wp;
      *wp2 = 0;
      wp++;
   }
   strupr(word);
   if(strncmp(keyword, word, matlen)==0)
      return(1);
   goto word_loop;
}

/* This function displays the postage stamp/filename scrolling box. */

void display_stamps()
{
   FileList *f=files;
   
   draw_inbox(&maindlg[IMGBOX], BLACK);
   
   if(file_count==0)
      return;
   
   show_stamp_row(0, scroll_pos);
   show_stamp_row(1, scroll_pos+6);
}

/* This function displays a single row of postage-stamp images with their
 * matching filename.  If a file has no postage-stamp image, the image is
 * shown as an X'd out box. */

void show_stamp_row(int rownum, int index)
{
   int ix, stamp=0, x, y, w, h;
   FileList *fw;
   Dialog *box= &maindlg[IMGBOX];
   
 loop:
   if(index>=file_count)
      return;
   fw=sortfiles[index].ptr;
   x=box->sx+1+100*stamp+17;
   y=box->sy+1+72*rownum+6;
   w=66;
   h=50;
   if(fw->stamp)
   {
      gfx_frame(x, y, w, h, MDGRAY);
      if(GI->paletted)
      {
	 PalStamp *s=fw->stamp;
	 uchar wkimage[64*48], *is=s->image, *id=wkimage;
	 uchar basecolor=16+(6*rownum+stamp)*16;
	 gfx_pal_set(basecolor, 16, s->palette);
	 for(ix=0; ix<1536; ++ix)
	 {
	    *(id++)= (*is >> 4)+basecolor;
	    *(id++)= (*(is++) & 15)+basecolor;
	 }
	 gfx_c_blitput(wkimage, x+1, y+1, 64, 48);
      }
      else
      {
	 PalStamp *s=fw->stamp;
	 uchar wkimage[64*48], *is=s->image, *id=wkimage;
	 uchar basecolor=16+(6*rownum+stamp)*16;
	 gfx_pal_set(basecolor, 16, s->palette);
	 for(ix=0; ix<1536; ++ix)
	 {
	    *(id++)= (*is >> 4)+basecolor;
	    *(id++)= (*(is++) & 15)+basecolor;
	 }
	 gfx_c_blitput(wkimage, x+1, y+1, 64, 48);
      }
   }
   else
   {
      gfx_frame(x, y, w, h, MDGRAY);
      gfx_line(x, y, x+w-1, y+h-1, MDGRAY);
      gfx_line(x, y+h-1, x+w-1, y, MDGRAY);
   }
   gfx_8x16_text(fw->name, box->sx+1+100*stamp+50-(strlen(fw->name)*4), 
		 box->sy+1+74*rownum+58, WHITE, BLACK);
   index++;
   stamp++;
   if(stamp<6)
      goto loop;
}

/* This function handles clicks on the UP file display scroll button  */

void feel_up(Dialog *d, int mouse)
{
   if(scroll_pos)
   {
      scroll_pos-=6;
      if(scroll_pos<0)
	 scroll_pos=0;
      update_slider();
      display_stamps();
   }
}

/* This function handles clicks on the DOWN file display scroll button  */

void feel_down(Dialog *d, int mouse)
{
   int next=scroll_pos+6;
   if(next<(file_count-6))
   {
      scroll_pos=next;
      update_slider();
      display_stamps();
   }
}

/* This function handles clicks in the file display box -- It determines
 * which image was clicked on, and displays its information in the file
 * information display box. */

void feel_imgbox(Dialog *d)
{
   int xslot=(GC->mouse_x-d->sx-1)/100;
   int yslot=(GC->mouse_y-d->sy-1)/74;
   int index=scroll_pos+yslot*6+xslot;
   if(index<file_count)
   {
      display_file_info(sortfiles[index].ptr, 1);
   }
}

/* This function performs an alphabetic sort on the files in the linked 
 * list of image files. */

void sort_files(void)
{
   int ix;
   FileList *f=files;
   
   if(sortfiles)
   {
      free(sortfiles);
      sortfiles=NULL;
   }
   if(file_count)
   {
      unsigned int srtcnt, count, srt1, srt2, srt3, srt4;
      SortList *s1, *s2;
      
      sortfiles=malloc(sizeof(SortList)*file_count);
      for(ix=0; ix<file_count; ++ix)
      {
	 sortfiles[ix].ptr=f;
	 f=f->next;
      }
      
      count=file_count;
      if(count<2)
	 return;
      
      srtcnt=count+1;
      
   nxtsc:
      srtcnt/=2;
      if(srtcnt==0)
	 return;
      srt1=1;
      srt2=count-srtcnt;
      
   srtsw1:
      srt3=srt1;
      
   srtsw2:
      srt4=srt3+srtcnt;
      
      s1= &sortfiles[srt3-1];
      s2= &sortfiles[srt4-1];
      if(stricmp(s1->ptr->name, s2->ptr->name)>0)
      {
	 FileList *temp=s1->ptr;
	 s1->ptr=s2->ptr;
	 s2->ptr=temp;
	 if(srtcnt<srt3)
	 {
	    srt3-=srtcnt;
	    goto srtsw2;
	 }
      }
      srt1++;
      if(srt1<=srt2)
	 goto srtsw1;
      goto nxtsc;
   }
}

static int thumb_h, thumb_y, leftover, rows, row, rowmax;

/* This function displays the file display scroller 'thumb' box. */

void see_thumb(Dialog *d)
{
   rows=(file_count+5)/6;
   row=scroll_pos/6;
   rowmax=rows-2;
   if(rowmax<0)
      rowmax=0;
   draw_inbox(d, CYAN);
   if(rows<2)
      rows=2;
   thumb_h=(d->height-2)*2/rows;
   if(thumb_h<8)
      thumb_h=8;
   leftover=(d->height-2)-thumb_h;
   if(leftover==0)
      thumb_y=d->sy+1;
   else
      thumb_y=d->sy+1+leftover*row/(rows-2);
   gfx_cblock(d->sx+1, thumb_y, d->width-2, thumb_h, GRAY);
   dialog_frame(d->sx+1, thumb_y, d->sx+d->width-2, thumb_y+thumb_h-1, WHITE, BLACK);
}

/* This function is in control when the user clicks in the file display
 * scroller, and stays in control until the mouse is released. */

static void feelthumb(Dialog *d, void (*update)())
{
   (*update)(d);
   for (;;)
   {
      (*update)(d);
      gfx_wait_input();
      if (!(GC->mouse_button & 1))
	 break;
   }
}


/* This function is called to determine the file display slider's current
 * location. */

static int slide_where(Dialog *d)
{
   int height;
   int y;
   
   y = GC->mouse_y - d->sy - 1;
   height = d->height-2;
   if (y < 0)
      return(0);
   if (y >= height)
      return(rowmax);
   return(uscaleby(y, rowmax + 1, height));
}

/* When the file display scroller is moved, this function handles the
 * display update. */

static void do_slider_move(Dialog *d)
{
   int new;
   
   new = slide_where(d);
   if (new != row)
   {
      row = new;
      scroll_pos=row*6;
      update_slider();
      display_stamps();
   }
}

/* When the user clicks in the file display scroller control, this
 * function starts the interaction sub-functions. */

void feel_thumb(Dialog *d, int mouse)
{
   feelthumb(d, do_slider_move);
}

/* This function updates the on-screen position of the file display
 * scroller. */

void update_slider()
{
   see_thumb(&maindlg[IMGSL]);
}

/* This function formats a date field into an 8-character MM/DD/YY string. */

void format_date(char *out, int date)
{
   sprintf(out, "%02d/%02d/%02d", (date>>5)&15, date & 31, ((date>>9)+1980)%100);
}

/* This function formats a time field into an 8-character HH:MM:SS string. */

void format_time(char *out, int time)
{
   sprintf(out, "%02d:%02d:%02d", (time>>11) & 31, (time>>5) & 63, time & 31);
}

/* This function turns the system keyboard on and purges the input stream
 * so that any keys pressed during the OFF period are discarded. */

#ifdef __WATCOMC__
#define INP inp
#define OUTP outp
#else
#define INP _inp
#define OUTP _outp
#endif

void keyboard_on()
{
   int keyboard = INP(0x64);
   int command;
   command=(keyboard & 8)?0x64:0x60;
   
   OUTP(command, 0xae);	/* Keyboard on */
   gfx_debug_print("Keyboard ON");
   purge_keyboard();
}

/* This function turns off the system keyboard so that keypresses cannot
 * affect 3DS operations. */


void keyboard_off()
{
   int keyboard=INP(0x64);
   int command;
   command=(keyboard & 8)?0x64:0x60;
   OUTP(command, 0xad);	/* Keyboard off */
   gfx_debug_print("Keyboard OFF");
}

/* This function checks the keyboard and mouse for activity which will
 * cancel pending operations. */

int kbabort(void)
{
   int status;
   
   gfx_key_hit(status);
   if(status)
   {
      purge_keyboard();
      return(1);
   }
   else
   {
      gfx_poll_mouse();
      if(GC->mouse_button)
      {
	 purge_keyboard();
	 return(1);
      }
   }
   return(0);
}

/* This function removes any keys from the keyboard buffer and discards
 * them. */

void purge_keyboard(void)
{
   int key, status;
   
 loop:
   gfx_key_hit(status)
      if(status)
      {
	 gfx_get_key(key);
	 goto loop;
      }
}
