#include <stdio.h>
#include <dirent.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/stat.h>

 
/* 




                 VV                   VV    EEEEEEEEEEEEEE   
                  VV                 VV     EE  
                   VV               VV      EE
                    VV             VV       EE
                     VV           VV        EE
                      VV         VV         EEEEEEEEEEEEEE
                       VV       VV          EE
                        VV     VV           EE
                         VV   VV            EE
                          VV VV             EE
                           VVV              EE
                            V               EEEEEEEEEEEEEE
                                                       
 
 
 
 
                       MODULE  : fileutil.c
 
                       VERSION : v3.5
 
                       REVISED : 17 Oct 2005
 
                       AUTHOR  : Michael A. Campbell
 
 
 
 
 
 


                          FUNCTIONAL DESCRIPTION
                          ----------------------
 
               This module contains a collection of file related 
 
               utilities and dialogs that are used by the VE editor.
 












*/

/* .page */

/*****************
 * INCLUDE FILES *
 *****************/

#include <string.h>
#include <curses.h>

#include "../def/common.def"
#include "../def/kbdif.def"
#include "../def/cmdix.def"

#define MAX_PATH_LEN  1024   /* Max length of a full pathname (dir + file)  */
#define MAX_FN_LEN    256    /* Max length of a file name wo directory part */

#define ST_NULL       0      /* Selection Type of Null                      */
#define ST_FILE       1      /* Selection Type of File                      */
#define ST_DIR        2      /* Selection Type of Directory                 */
#define ST_TEXT       3      /* Selection Type of manually entered text     */

#define DT_OFD        1      /* Dialog Type = Open File Dialog (OFD)        */
#define DT_GFD        2      /* Dialog Type = Goto File Dialog (GFD)        */

#include "../str/vestruct.str"

struct str_dir_entry {                 /* A directory entry                 */
   char name [ MAX_FN_LEN ];           /* Its name                          */
   BOOL is_directory;                  /* Whether it is a directory         */
   int  num_lines;                     /* Number of lines, if a file        */
   int  num_chars;                     /* Number of chars, if a file        */
   BOOL changed;                       /* Changed, if a file                */
};

struct str_entry_list {                /* An array of directory entries     */
   struct str_dir_entry entry [4096];  /* 4096 is arbitrary                 */
};

#include "../inc/vedbase.inc"
#include "../inc/termif.inc"
#include "../inc/kbdif.inc"

#include "../ext/veutils.ext"
#include "../ext/termif.ext"
#include "../ext/lnklst.ext"
#include "../ext/strprim.ext"
#include "../ext/editcmd.ext"
#include "../ext/findcmd.ext"
#include "../ext/fileutil.ext"

/* .page */

/*******************
 * LOCAL VARIABLES *
 *******************/

static int dialog_type;       /* The dialog type - either Open or Goto      */

/* Open/Goto File Dialog screen geometry */

static int cols_per_page;     /* Number of display columns per page         */
static int rows_per_page;     /* Number of display rows per page            */
static int entries_per_page;  /* Number of directory entries per page       */

/* Open/Goto File Dialog screen field positions, widths */

static int PAGE_NUM_X;        /* (x, y) for "Page Number" field             */
static int PAGE_NUM_Y;
static int PAGE_NUM_LEN;      /* Field width for Page Number field          */

static int CURR_FILE_X;       /* (x, y) for "current file" field            */
static int CURR_FILE_Y;
static int CURR_FILE_LEN;     /* Field width for "current file" field       */

static int CURR_DIR_X;        /* (x, y) for "current directory" field       */
static int CURR_DIR_Y;
static int CURR_DIR_LEN;      /* Field width for "current directory" field  */

static int SELECTION_X_BASE;  /* (x, y) for first directory entry on page   */
static int SELECTION_Y_BASE;
static int DIR_ENTRY_LEN;     /* Field width per directory entry            */

static int HEADERS_Y;         /* y coordinate for Lines, Chars, Changed hdr */
static int LINES_X;           /* x coordinate for Lines field               */
static int CHARS_X;           /* x coordinate for Chars field               */
static int CHANGED_X;         /* x coordinate for Changed field             */

/* .page */

/***************
 *  CONSTANTS  *
 ***************/

/* .page */

/********************
 * LOCAL PROCEDURES *
 ********************/

/* .page */

/*********************
 * PUBLIC PROCEDURES *
 *********************/

/* .page */

void analyze_pathname ( pathname, parent, child, include_slash )

/*****************************************************************************
 *                                                                           *
 * The function accepts a full pathname for a file or a directory and breaks *
 * it up into its parent directory and child portions.                       *
 *                                                                           *
 * The parent and child parts are returned in the parent and child           *
 * parameters. Either or both may be nil strings if this is appropriate.     *
 *                                                                           *
 *****************************************************************************/

char pathname [];    /* The pathname to break up into parent, child          */
char parent [];      /* The parent dir part of the pathname                  */
char child [];       /* The child part of the pathname (may be file or dir)  */
BOOL include_slash;  /* Include the trailing slash for the parent_dir?       */

begin
   register int len; /* Length of pathname                                   */
   register int i;   /* Loop counter                                         */
   register int brk; /* The index of the break between parent and child      */

   /* Initialize results */

   brk = 0;

   /* Check for the trivial case of a nil pathname, return if found */

   if ( (len = strlen(pathname)) == 0 )
   begin
      parent[0] = '\0';
      child [0] = '\0';
      return;
   end

   /* Scan backwards from end of pathname, looking for first '/'. This is   */
   /* break between the parent part of the name and child part. brk is      */
   /* incremented by one so that it is effectively the index of the first   */
   /* character of the child part of the pathname.                          */

   for ( i = len; i > 0; i-- )
      if ( pathname[i] == '/' )
      begin
         brk = i + 1;
         break;
      end

   /* Copy the child part to the child return argument. This picks up and   */
   /* transfers the '\0' at the end of the pathname, so that child is a     */
   /* properly terminated string.                                           */

   for ( i = brk; i <= len; i++ )
      child[i-brk] = pathname[i];

   if ( !include_slash && brk > 0 )
      brk -= 1;

   /* Copy the parent part to the parent return argument and add a string   */
   /* termination to it.                                                    */
   
   for ( i = 0; i < brk; i++ )
      parent[i] = pathname[i];
   parent[brk] = '\0';

end /* analyze_pathname */

/* .page */

void init_ofd_page_parms ()

/****************************************************************************
 *                                                                          *
 *  This function initializes the parameters that describe the File Select  *
 *  (fs) full screen page. These parameters are static global  variables of *
 *  this module. The layout of the complete page can be manipulated from    *
 *  this one function, which acts as a sort of "style sheet" for the page   *
 *  that is drawn.                                                          *
 *                                                                          *
 *  This routine sets the page for the Open File Dialog (ofd). Its peer,    *
 *  init_gfd_page_parms, sets the page for the Goto File Dialog.            *
 *                                                                          *
 ****************************************************************************/

begin
   register int used_cols;  /* Total columns used by dir/file entries       */

   dialog_type = DT_OFD;

   /* Set Page Number field position, width */

   PAGE_NUM_X       = physical_cols - 14;
   PAGE_NUM_Y       = 0;
   PAGE_NUM_LEN     = 10;

   /* Set Current File field position, width */

   CURR_FILE_X      = 14;
   CURR_FILE_Y      = physical_lines - 4;
   CURR_FILE_LEN    = physical_cols - 16;

   /* Set Current Directory field position, width */

   CURR_DIR_X       = 12;
   CURR_DIR_Y       = 3;               /* original was physical_lines - 5 */
   CURR_DIR_LEN     = physical_cols - 16;

   /* Set directory entry display base position, width */

   SELECTION_X_BASE = 2;   /* (2, 3) is first directory entry display     */
   SELECTION_Y_BASE = 5;
   DIR_ENTRY_LEN    = 30;  /* Field Width per directory entry             */

   /* Compute the available page geometry */

   cols_per_page    = (physical_cols - SELECTION_X_BASE)/ DIR_ENTRY_LEN;
   rows_per_page    = physical_lines - 10;
   entries_per_page = cols_per_page * rows_per_page;

   /* Finally, maximum the size of each column to the available page size */

   used_cols = SELECTION_X_BASE + cols_per_page * DIR_ENTRY_LEN;
   if ( used_cols < physical_cols )
      DIR_ENTRY_LEN += (physical_cols - used_cols) / cols_per_page;

end /* init_ofd_page_parms */

/* .page */

void init_gfd_page_parms ()

/****************************************************************************
 *                                                                          *
 *  This function initializes the parameters that describe the File Select  *
 *  (fs) full screen page. These parameters are static global  variables of *
 *  this module. The layout of the complete page can be manipulated from    *
 *  this one function, which acts as a sort of "style sheet" for the page   *
 *  that is drawn.                                                          *
 *                                                                          *
 *  This routine sets the page for the Goto File Dialog (gfd). Its peer,    *
 *  init_ofd_page_parms, sets the page for the Open File Dialog.            *
 *                                                                          *
 ****************************************************************************/

begin
   dialog_type = DT_GFD;

   /* Set Page Number field position, width */

   PAGE_NUM_X       = physical_cols - 14;
   PAGE_NUM_Y       = 0;
   PAGE_NUM_LEN     = 10;

   /* Set Current File field position, width */

   CURR_FILE_X      = 14;
   CURR_FILE_Y      = physical_lines - 4;
   CURR_FILE_LEN    = physical_cols - 16;

   /* Set Current Directory field position, width */

   CURR_DIR_X       = 14;
   CURR_DIR_Y       = physical_lines - 5;
   CURR_DIR_LEN     = physical_cols - 16;

   /* Set directory entry display base position, width. The last value      */
   /* on this line (25 here) must match the last value of the LINES_X line  */

   SELECTION_X_BASE = 2;   /* (2, 3) is first directory entry display */
   SELECTION_Y_BASE = 5;
   DIR_ENTRY_LEN    = physical_cols - SELECTION_X_BASE - 25;

   /* Set the position of the LINES, CHARS and CHANGED fields */

   HEADERS_Y        = 3;
   LINES_X          = physical_cols - 25;
   CHARS_X          = physical_cols - 17;
   CHANGED_X        = physical_cols - 8;

   /* Compute the available page geometry */

   cols_per_page    = physical_cols / DIR_ENTRY_LEN;
   rows_per_page    = physical_lines - 12;
   entries_per_page = cols_per_page * rows_per_page;

end /* init_gfd_page_parms */

/* .page */

struct str_entry_list *load_ofd_entry_list ( directory, num_entries )

/****************************************************************************
 *                                                                          *
 *  This function creates a entry list segment containing all of the        *
 *  directory entries in the supplied directory, and returns a pointer to   *
 *  that list in the return argument. This list has been malloc'd, and must *
 *  be freed when it is done with. This function also returns the number of *
 *  entries in the list.                                                    *
 *                                                                          *
 *  This function returns a NULL pointer and a num_entries of 0 if any any  *
 *  error prevents the list from being created and populated.               *
 *                                                                          *
 ****************************************************************************/

char directory [];            /* The directory to present to the user       */
int  *num_entries;            /* The number of returned entries             */

begin
   register int i;            /* Loop counter                               */

   int stat_res;              /* Result of attempt to stat the file         */
   struct stat file_info;     /* File information from the stat function    */

   struct dirent **files;     /* String table of directory contents         */
   int count;                 /* Number of entires in the string table      */

   int num_dir_entries  = 0;  /* Number of directories in list              */
   int num_file_entries = 0;  /* Number of files in list                    */

   struct str_entry_list *dir_entry_list;   /* List of directory  entries   */
   struct str_entry_list *file_entry_list;  /* List of file entries         */
   struct str_dir_entry  *dir_entry;        /* The current entry if a dir   */
   struct str_dir_entry  *file_entry;       /* The current entry if a file  */

   /* Initialize the return results */

   *num_entries = 0;

   /* Attempt to change to the supplied directory */

   if ( chdir(directory) )
   begin
      write_ve_msg ( "Cannot enter selected directory" );
      return ( NULL );
   end

   /* Read the directory. Scandir creates a string table with each of the   */
   /* directory entries in a separate malloc'd segment. The table itself is */
   /* also a malloc'd segment. Each of the segments will be freed as used.  */

   count = scandir ( directory, &files, NULL, alphasort );

   /* Allocate memory segments for all of the entry names. Allocate room    */
   /* for one extra name at the end, which will be used for manually        */
   /* entered filenames. The dfs_entry_list is THE key data structure for   */
   /* this routine. The "dir_" and "file_" variants are used to build the   */
   /* master list                                                           */

   dir_entry_list  = ve_alloc ( ((count + 1) * sizeof (struct str_dir_entry)) );
   file_entry_list = ve_alloc ( ((count + 1) * sizeof (struct str_dir_entry)) );

   /* Now, scan the list of directory entries returned from scandir, and    */
   /* copy all of the directories to the dir_entry_list, and all of the     */
   /* file entries to the file_entry_list. These two lists will be          */
   /* concatentated afterwards.                                             */

   for ( i = 0; i < count; i++ )
   begin

      stat_res = stat ( files[i]->d_name, &file_info );

      if ( S_ISDIR(file_info.st_mode) )
      begin
         /* This is a directory, add it to the directory list */

         dir_entry = &dir_entry_list->entry[num_dir_entries];

         strcpy ( dir_entry->name, files[i]->d_name );
         dir_entry->is_directory = TRUE;

         num_dir_entries++;
      end
      else
      begin
         /* This is a file, add it to the file list */

         file_entry = &file_entry_list->entry[num_file_entries];

         strcpy ( file_entry->name, files[i]->d_name );
         file_entry->is_directory = FALSE;

         num_file_entries++;
      end

   end /* directory entry scan */

   /* Now concatenate the two lists so that the dir_entry_list is now the   */
   /* consolidated list, with directories first, followed by files.         */

   for ( i = 0; i < num_file_entries; i++ )
   begin
      dir_entry  = &dir_entry_list->entry[num_dir_entries + i];
      file_entry = &file_entry_list->entry[i];

      strcpy ( dir_entry->name, file_entry->name );
      dir_entry->is_directory = file_entry->is_directory;
   end

   *num_entries = num_dir_entries + num_file_entries;

   /* Now set the unused fields to nil values */

   for ( i = 0; i < *num_entries; i++ )
   begin
      dir_entry = &dir_entry_list->entry[i];

      dir_entry->num_lines = 0;
      dir_entry->num_chars = 0;
      dir_entry->changed   = FALSE;
   end

   /* Release the memory consumed by the scandir call and file_entry_list */

   for ( i = 0; i < count; i++ )
      free ( files[i] );
   free ( files );

   free ( file_entry_list );
   
   /* Set up return info and exit */


   return ( dir_entry_list );

end /* load_ofd_entry_list */

/* .page */

struct str_entry_list *load_gfd_entry_list ( num_entries )

/****************************************************************************
 *                                                                          *
 *  This function creates a entry list segment containing all of the        *
 *  filenames of all of the available files that VE has open at the time    *
 *  and returns that list as its return argument. This list has been        *
 *  malloc'd, and must be freed when it is done with. This function also    *
 *  returns the number of entries in the list.                              *
 *                                                                          *
 *  This function returns a NULL pointer and a num_entries of 0 if any any  *
 *  error prevents the list from being created and populated.               *
 *                                                                          *
 ****************************************************************************/

int  *num_entries;                    /* The number of returned entries     */

begin
   register int i;                    /* Loop counter                       */
   struct file_des_str *file_des;     /* Pointer to current file descriptor */

   struct str_entry_list *entry_list; /* List of directory  entries         */
   struct str_dir_entry  *curr_entry; /* The current entry being done       */
   int    checksum;                   /* Checksum of current entry          */

   /* Initialize the return results, check for pathalogical case of no open */
   /* files. If this occurs, return 0 and NULL                              */

   *num_entries = 0;
   if ( files_open == 0 )
      return ( NULL );

   /* Allocate a memory segment for all of the entry names.                 */

   entry_list = ve_alloc ( ((files_open + 1) * sizeof (struct str_dir_entry)) );

   /* Now, copy over the list of filenames from the open_file_list          */

   file_des = (struct file_des_str *) open_file_list.fwd_link;

   while ( file_des != NULL )
   begin
      curr_entry = &entry_list->entry[*num_entries];

      strcpy ( curr_entry->name, file_des->filename );
      curr_entry->is_directory = FALSE;

      /* Determine the lines, chars and modified status of the file. If     */
      /* is the current file, update the file descriptor first, so that we  */
      /* get the most up-to-date info.                                      */

      if ( file_des == curr_file_des )
         save_context ( curr_file_ix );

      file_info ( &file_des->file_list,   FALSE,    &curr_entry->num_chars, 
                  &curr_entry->num_lines, &checksum                         );

      curr_entry->changed = ( curr_entry->num_chars != file_des->file_list.num_chars ||
                              curr_entry->num_lines != file_des->file_list.num_lines ||
                              checksum              != file_des->file_list.checksum     );

      file_des = (struct file_des_str *) file_des->fwd_link;
      *num_entries += 1;
   end

   return ( entry_list );

end /* load_gfd_entry_list */

/* .page */

void draw_fs_page_num ( page_num, num_pages )

/****************************************************************************
 *                                                                          *
 *  This function draws the page number on to the fs display page.          *
 *                                                                          *
 ****************************************************************************/

int page_num;   /* The page number to display                               */
int num_pages;  /* The total number of pages                                */

begin
   char display_buf [20];  /* Buffer to build the page num field in         */
   char convert_buf [20];  /* Buffer for sprintf conversions                */

   /* Build the page number field */

   strcpy  ( display_buf, "Page "         );
   sprintf ( convert_buf, "%d", page_num  );
   strcat  ( display_buf, convert_buf     );
   strcat  ( display_buf, "/"             );
   sprintf ( convert_buf, "%d", num_pages );
   strcat  ( display_buf, convert_buf     );

   mv_writestr ( PAGE_NUM_X, PAGE_NUM_Y, FALSE, display_buf );

end /* draw_fs_page_num */

/* .page */

void draw_fs_page ( title, entry_list, num_entries, page_num, num_pages, selected_file_ix )

/****************************************************************************
 *                                                                          *
 *  This function draws a page for the file_select_dialog                   *
 *                                                                          *
 ****************************************************************************/

char   title [];                     /* Page title                          */
struct str_entry_list *entry_list;   /* Master list of file entries         */
int    num_entries;                  /* Number of entries in entry_list     */
int    page_num;                     /* The page number to display          */
int    num_pages;                    /* Number of display pages             */
int    selected_file_ix;             /* The index of the selected file      */

begin
   register int i; /* Loop counter                                          */

   int start_ix;   /* Index into fs_entry_list of first entry on page       */
   int end_ix;     /* Index into fs_entry_list of last entry on page        */
   int curr_col;   /* Current column being drawn                            */
   int curr_row;   /* Current row being drawn                               */
   int ex, ey;     /* Entry (x, y) coordinate pair                          */

   char *entry_buf;       /* Buffer for left truncated entry name            */
   char *cwd;            /* Buffer for getting current directory name       */
   char *display_buf;    /* Buffer for displaying directory or file names   */

   struct str_dir_entry  *curr_entry; /* The current entry being drawn      */

   /* Set up display buffers */

   cwd         = ve_alloc ( MAX_PATH_LEN );
   display_buf = ve_alloc ( MAX_PATH_LEN );
   entry_buf   = ve_alloc ( MAX_PATH_LEN );

   /* Clear from the line after the divider line to the end of the screen   */

   ti_clear_display ( 2 );

   /* Display the page_num field */

   draw_fs_page_num ( page_num + 1, num_pages );

   /* If this is the Goto File Dialog, put out column headers */

   if ( dialog_type == DT_GFD )
   begin
      mv_writestr_emphasis ( SELECTION_X_BASE, HEADERS_Y, FALSE, "Filename" );
      mv_writestr_emphasis ( LINES_X,          HEADERS_Y, FALSE, "Lines"    );
      mv_writestr_emphasis ( CHARS_X,          HEADERS_Y, FALSE, "Chars"    );
      mv_writestr_emphasis ( CHANGED_X,        HEADERS_Y, FALSE, "Changed"  );
   end

   start_ix = page_num * entries_per_page;
   end_ix   = start_ix + entries_per_page - 1;
   end_ix   = (end_ix > num_entries - 1) ? num_entries -1 : end_ix;

   curr_col = 0;
   curr_row = 0;

   for ( i = start_ix; i <= end_ix; i++ )
   begin
      /* Iterate over the entries on this page, putting them out in a multi */
      /* column display                                                     */

      curr_entry = &entry_list->entry[i];

      ex = SELECTION_X_BASE + (curr_col * DIR_ENTRY_LEN) + 1;
      ey = SELECTION_Y_BASE + curr_row;
      
      /* File names are put out in normal font and color, while directories */
      /* are put out with "emphasis", which in the Linux port is "with      */
      /* color, and underlined".                                            */

      truncate_str_left ( curr_entry->name, entry_buf, DIR_ENTRY_LEN - 2 );

      if ( curr_entry->is_directory )
         mv_writestr_emphasis ( ex, ey, FALSE, entry_buf );
      else
         mv_writestr ( ex, ey, FALSE, entry_buf );

      /* If this is the Goto File Dialog, print out the size and change     */
      /* status of the file                                                 */

      if ( dialog_type == DT_GFD )
      begin
         sprintf ( display_buf, "%d", curr_entry->num_lines );
         mv_writestr ( LINES_X, ey, FALSE, display_buf );

         sprintf ( display_buf, "%d", curr_entry->num_chars );
         mv_writestr ( CHARS_X, ey, FALSE, display_buf );

         if ( curr_entry->changed )
            mv_writestr ( CHANGED_X, ey, FALSE, "Yes" );
         else
            mv_writestr ( CHANGED_X, ey, FALSE, "No" );
      end

      curr_col = ++curr_col % cols_per_page;
      if ( curr_col == 0 ) 
         curr_row++;
   end

   /* Now draw the current directory field */

   if ( getcwd ( cwd, MAX_PATH_LEN) == NULL )
      strcpy ( cwd, "Directory pathname too large to acquire" );

   mv_writestr ( 2, CURR_DIR_Y, FALSE, "Curr Dir:" );
   truncate_str_left ( cwd, display_buf, CURR_DIR_LEN );
   mv_writestr ( CURR_DIR_X, CURR_DIR_Y, FALSE, display_buf );

   /* Now draw the current filename field */

   mv_writestr ( 2, CURR_FILE_Y, FALSE, "Curr File:" );
   truncate_str_left ( entry_list->entry[selected_file_ix].name, display_buf, CURR_FILE_LEN );
   mv_writestr ( CURR_FILE_X, CURR_FILE_Y, FALSE, display_buf );

   /* Write out the divider line and navigation menu */

   write_divider_line ( physical_lines - 2 );

   mv_writestr ( 0, physical_lines - 1, FALSE,
   "CurUp/CurDn/CurLeft/CurRight, PgUp/PgDn, ENTER (select entry), ESC-q (Quit)" );

   /* Clean up directory display buffers */

   free ( cwd         );
   free ( display_buf );
   free ( entry_buf   );

end /* draw_fs_page */

/* .page */

void update_fs_page ( title,    file_list,        num_entries,
                      page_num, selected_file_ix, select_x,     select_y )

/****************************************************************************
 *                                                                          *
 * This routine co-ordinates all display page updates for the file select   *
 * dialog. It accepts a selected_file_ix, determines from that the correct  *
 * page number to be displaying and the (row, column) of the selected       *
 * entry on that page.                                                      *
 *                                                                          *
 * It then ensures that the correct page is displayed, and moves the        *
 * visual selection marker to the correct (row, column). Finally, it        *
 * updates the "Curr File" field.                                           *
 *                                                                          *
 ****************************************************************************/

char   title [];                   /* The display page title to present     */
struct str_entry_list *file_list;  /* List of directory/file entries        */
int    num_entries;                /* Number of entries in file_list        */
int    *page_num;                  /* Currently displayed page              */
int    *selected_file_ix;          /* Index in file_list of selected_entry  */
int    *select_x;                  /* X (column) co-ordinate of selection   */
int    *select_y;                  /* Y (row) co-ordinate of selection      */

begin
   int new_page_num;               /* The page num with selected_file in it */
   int num_pages;                  /* Total number of display pages         */
   int selected_col;               /* Column of selected entry              */
   int selected_row;               /* Row of selected entry                 */
   int base_page_ix;               /* Base index of 1st entry of page       */
   int relative_ix;                /* selected_file_ix, relative to page    */
   
   /* Check the selected file index, and bring it into range if it is out   */
   /* of range                                                              */

   if ( *selected_file_ix < 0 )
      *selected_file_ix = num_entries - 1;

   if ( *selected_file_ix >= num_entries )
      *selected_file_ix = 0;

   /* Figure out the correct page number for the page containing the        */
   /* selected file                                                         */

   num_pages = num_entries / entries_per_page;
   if ( num_pages * entries_per_page < num_entries )
      num_pages++;

   new_page_num = *selected_file_ix / entries_per_page;
   if ( (new_page_num + 1)* entries_per_page < *selected_file_ix )
      new_page_num++;

   /* If the correct page isn't displayed, display it */

   if ( *page_num != new_page_num )
      draw_fs_page ( title,                      file_list,  num_entries, 
                     (*page_num = new_page_num), num_pages,  *selected_file_ix );

   /* Now figure out the row and column of the selected file on the page */

   base_page_ix = *page_num * entries_per_page;
   relative_ix  = *selected_file_ix - base_page_ix;

   selected_row = relative_ix / cols_per_page;
   selected_col = relative_ix - ( selected_row * cols_per_page );

   /* Now clear the previous selection marker from the page and write the   */
   /* new one                                                               */

   ti_move_cursor ( *select_x, *select_y );
   writechar ( ' ' );

   *select_x = SELECTION_X_BASE + selected_col * DIR_ENTRY_LEN;
   *select_y = SELECTION_Y_BASE + selected_row;

   ti_move_cursor ( *select_x, *select_y );
   writechar_emphasis ( '>' );

   /* Finally, update the Curr File field */

   ti_move_cursor  ( CURR_FILE_X, CURR_FILE_Y );
   ti_clear_to_eol ( CURR_FILE_Y );
   writestr ( file_list->entry[*selected_file_ix].name );

end /* update_fs_page */

/* .page */

int file_select_dialog ( title, file_list, num_entries, 
                         selected_entry,   entry_size,  selected_ix )

/****************************************************************************
 *                                                                          *
 *  This function presents the user with a full screen list of all of the   *
 *  entries in the supplied file_list, and allows the user to interactively *
 *  select an entry.                                                        *
 *                                                                          *
 *  The user may select a file from the full screen list, or may manually   *
 *  type an entry name in instead.                                          *
 *                                                                          *
 *  This routine returns as its value the type of the selected entry, one   *
 *  of ST_DIR if a dir, ST_FILE if a file, or ST_NULL if the user cancels   *
 *  out without selecting or entering a file name.                          *
 *                                                                          *
 *  This routine also returns the name of the selected entry, or a nil      *
 *  string if the user cancels out without selecting an entry or entering a *
 *  filename. It also returns the index in the supplied list of the         *
 *  selected file, or -1. -1 will be returned if the user cancels out       *
 *  without selecting a filename.                                           *
 *                                                                          *
 ****************************************************************************/

char   title [];                   /* The display page title to present     */
struct str_entry_list *file_list;  /* List of directory/file entries        */
int    num_entries;                /* Number of entries in file_list        */
char   selected_entry [];          /* The entry the user has selected       */
int    entry_size;                 /* Maximum size of the selected_entry    */
int    *selected_ix;               /* Index in file_list of selected_entry  */

begin
   register int i;         /* Loop counter                                  */
   register int key;       /* User input                                    */
   int page_num;           /* Page num for curr page [1..num_pages]         */

   int selected_file_ix;   /* The index into dfs_entry_list of selection    */
   int selection_x;        /* The x coordinate of selected entry            */
   int selection_y;        /* The y coordinate of selected entry            */
   int selection_type;     /* The users selection type (file, dir, null)    */

   int le_ix;              /* line_edit position index                      */
   int le_x, le_y;         /* line_edit (x, y) co-ordinates                 */

   int    stat_res;        /* Result of attempt to stat the file            */
   struct stat file_info;  /* File information from the stat function       */
   char   *line_edit_buf;  /* Pointer to line_edit's buffer                 */

   MEVENT mouse_event;     /* A received mouse event                        */
   int m_x, m_y;           /* Determined mouse co-ordinates                 */
   int base_file_ix;       /* Base selected_file_ix, this page              */

   /* Initialize return result */

   selected_entry[0] = '\0';
   if ( num_entries == 0 )
      return ( ST_NULL );

   /* Set up parms for the initially selected entry on the page    */

   page_num         = -1;                /* Forces first page draw */
   selected_file_ix = *selected_ix;
   selection_x      = SELECTION_X_BASE;
   selection_y      = SELECTION_Y_BASE;
   selection_type   = ST_FILE;

   line_edit_buf = (char *) &file_list->entry[num_entries].name;

   /* Draw the initial page */

   update_fs_page ( title,     file_list,          num_entries,
                    &page_num, &selected_file_ix,  &selection_x, &selection_y);
   do
   begin
      /* Master command loop. Read a keystroke, and act on that keystroke.  */

      if ( selected_file_ix != num_entries )
         ti_move_cursor ( selection_x, selection_y );

      ve_mode = last_ve_mode; /* Kick VE out of command mode, if it is in   */
      key     = read_char ();

      if ( ve_mode == M_COMMAND )
      begin
         /* A command key has been pressed. */

         if ( key == commands[half_up_ix] )
         begin
            /* The user has pressed CTL-CurUp or SHFT-CurUp - they wish to  */
            /* select the parent directory. Load the filename and set up    */
            /* to exit.                                                     */

            strcpy ( line_edit_buf, ".." );
            selected_file_ix = num_entries;

            key = ENTER;

         end /* half_up_ix */

         else if ( key == commands[ext_pgup_ix] )
         begin
            /* PgUp has been pressed. Move to the previous page of          */
            /* directory entries, or wrap down to the last page if we are   */
            /* on the first page. If there IS only one page, do nothing     */

            selected_file_ix -= entries_per_page;

            update_fs_page ( title,     file_list,         num_entries,
                             &page_num, &selected_file_ix, &selection_x, &selection_y);

         end /* pgup */

         else if ( key == commands[ext_pgdn_ix] )
         begin
            /* PgDn has been pressed. Move to the next page of directory    */
            /* entries, or wrap up to the first page if we are on the last  */
            /* page. If there IS only one page, do nothing                  */

            selected_file_ix += entries_per_page;

            update_fs_page ( title,     file_list,         num_entries,
                             &page_num, &selected_file_ix, &selection_x, &selection_y);

         end /* pgdn */

         else if ( key == commands[cursor_up_ix] )
         begin
            /* Cursor Up or CTL-u has been entered - move up to previous    */
            /* row, or up to the previous page if we are at the top of the  */
            /* page                                                         */

            selected_file_ix -= cols_per_page;

            update_fs_page ( title,     file_list,         num_entries,
                             &page_num, &selected_file_ix, &selection_x, &selection_y);

         end /* Cursor Up */

         else if ( key == commands[cursor_down_ix] )
         begin
            /* Cursor Dn or CTL-d has been entered - move down to the next  */
            /* row, or to the next page if we are at the bottom of the      */
            /* page                                                         */

            selected_file_ix += cols_per_page;

            update_fs_page ( title,     file_list,         num_entries,
                             &page_num, &selected_file_ix, &selection_x, &selection_y);

         end /* Cursor Dn */

         else if ( key == commands[cursor_left_ix] )
         begin
            /* Cursor Left or CTL-l has been pressed. Move one column to    */
            /* left if possible, or wrap up to the previous row             */

            if ( selected_file_ix == num_entries )
            begin
               /* Special case - the user is typing in the line edit buffer */
               /* Pass the cursor left key to the line editor               */

               line_edit ( line_edit_buf, CURR_FILE_LEN, key, &le_ix, &le_x, &le_y );
            end

            else
            begin
               /* We are not at the leftmost column - just move one left    */

               selected_file_ix -= 1;

               update_fs_page ( title,     file_list,         num_entries,
                                &page_num, &selected_file_ix, &selection_x, &selection_y);
            end

         end /* Cursor Left or CTL-l has been pressed */

         else if ( key == commands[cursor_right_ix] )
         begin
            /* Cursor Right or CTL-r has been pressed. Move one column to   */
            /* right if possible, or wrap forward to the next row           */

            if ( selected_file_ix == num_entries )
            begin
               /* Special case - the user is typing in the line edit buffer */
               /* Pass the cursor right key to the line editor              */

               line_edit ( line_edit_buf, CURR_FILE_LEN, key, &le_ix, &le_x, &le_y );
            end

            else
            begin
               /* We are not at the rightmost column - just move one right  */

               selected_file_ix += 1;

               update_fs_page ( title,     file_list,         num_entries,
                                &page_num, &selected_file_ix, &selection_x, &selection_y);
            end

         end /* Cursor Right or CTL-r has been pressed */

         else if ( (key == commands[ext_home_ix ] || key == commands[ext_end_ix ] ||
                    key == commands[del_char_ix ] || key == commands[rubout_ix  ] ||
                    key == commands[del_right_ix] || key == commands[del_left_ix]    ) &&
                   (selected_file_ix              == num_entries                     ) )
         begin
            /* A line edit control key has been entered - pass it to the   */
            /* line editor                                                 */

            line_edit ( line_edit_buf, CURR_FILE_LEN, key, &le_ix, &le_x, &le_y );

         end /* line edit control key */

         else if ( key == commands[mouse_ix] )
         begin
            /* A mouse click has been received. Resolve it to the nearest   */
            /* displayed entry. Then set the selected_file_ix to that one   */

            getmouse ( &mouse_event );

            if ( mouse_event.bstate & BUTTON1_CLICKED        ||
                 mouse_event.bstate & BUTTON2_CLICKED        ||
                 mouse_event.bstate & BUTTON3_CLICKED        ||
                 mouse_event.bstate & BUTTON1_DOUBLE_CLICKED )
            begin
               m_x = mouse_event.x;
               m_y = mouse_event.y;

               /* Check for clicks beyond the last field on the row, and    */
               /* and reign them in if found                                */

               if ( m_x > cols_per_page * DIR_ENTRY_LEN )
                  m_x = cols_per_page * DIR_ENTRY_LEN - 1;

               m_x = m_x / DIR_ENTRY_LEN;
               m_y = (m_y < SELECTION_Y_BASE) ? 0 : m_y - SELECTION_Y_BASE;

               base_file_ix = page_num * entries_per_page;

               selected_file_ix = base_file_ix + m_y * cols_per_page + m_x;

               update_fs_page ( title,     file_list,         num_entries,
                                &page_num, &selected_file_ix, &selection_x, &selection_y);

               if ( mouse_event.bstate & BUTTON1_DOUBLE_CLICKED )
               begin
                  /* The user has doubled clicked an entry. Treat this as   */
                  /* if they had typed ENTER.                               */

                  key = ENTER;

               end /* Doubled clicked */

            end

         end /* mouse click */

         else if ( key == commands[quit_ix] )
         begin
            /* ESC-q or F12 has been entered. The user wishes to back out   */

            selected_entry[0] = '\0';
            *selected_ix      = -1;
            selection_type    = ST_NULL;

            key = ENTER; /* Will cause us to exit */

         end /* ESC-q */

      end /* A command key has been pressed */

      else if ( key >= 0x20 && key <= 0x7e )
      begin
         /* A text character has been entered. Pass it to line_edit for     */
         /* processing.                                                     */

         if ( selected_file_ix != num_entries )
         begin
            /* This is either the first character typed, or the user has    */
            /* moved the cursor since typing last. Either way, set up for   */
            /* line editing.                                                */

            strcpy ( file_list->entry[num_entries     ].name,
                     file_list->entry[selected_file_ix].name );
            selected_file_ix = num_entries;

            /* Clear the "current file" display field */

            le_ix   = 0;
            le_x    = CURR_FILE_X;
            le_y    = CURR_FILE_Y;
            ve_mode = M_COMMAND;

            line_edit ( line_edit_buf, CURR_FILE_LEN, commands[del_right_ix], &le_ix, &le_x, &le_y );

            ve_mode = last_ve_mode;
         end

         /* Pass the character to the line editor, which will echo          */

         line_edit ( line_edit_buf, CURR_FILE_LEN, key, &le_ix, &le_x, &le_y );

      end /* a text character has been entered */

      else if ( key != ENTER )

         /* Unsupported selection - whine at the user via BEL */

         writechar ( BEL );

   end
   while ( key != ENTER );

   /* ENTER has been pressed. Set up the return results by copying    */
   /* selected file into the selected_entry return parm, copying      */
   /* selected_file_ix to the *selected_ix parm, and finally,         */
   /* determining the type of the selected file.                      */

   if ( selection_type != ST_NULL )
   begin
      strcpy ( selected_entry, file_list->entry[selected_file_ix].name );
      *selected_ix = (selected_file_ix == num_entries) ? 0 : selected_file_ix;

      stat_res = stat ( selected_entry, &file_info );

      /* If the file doesn't exist, it is a manually typed entry - set its  */
      /* type to ST_FILE. If it does exist, use the stat results to set the */
      /* type.                                                              */

      if ( stat_res != 0 )
         selection_type = ST_FILE;

      else if ( S_ISDIR(file_info.st_mode) )
         selection_type = ST_DIR;
         
      else
         selection_type = ST_FILE;

   end /* Selection Type != ST_NULL */       

   return ( selection_type );

end /* file_select_dialog */

/* .page */

void open_file_dialog ( title, selected_file, selected_file_size, redraw )

/****************************************************************************
 *                                                                          *
 *  This is the VE Open File Dialog. It presents a full screen menu that    *
 *  allows users to interactively navigate their file system, and visually  *
 *  select a file that they wish to open.                                   *
 *                                                                          *
 *  The user may use this dialog to select a full filename, or may simply   *
 *  use it to navigate to the intended directory and then manually tupe in  *
 *  a filename of interest.                                                 *
 *                                                                          *
 *  This dialog is used by the [Edit, File] command, the [Quit, Write]      *
 *  command and the Open Files menu.                                        *
 *                                                                          *
 *  This dialog returns the pathname of the selected file, or a nil string  *
 *  if the user cancels out of the dialog without selecting a file.         *
 *                                                                          *
 ****************************************************************************/

char title [];             /* The display page title to present             */
char selected_file [];     /* The file the user selects via the dialog      */
int  selected_file_size;   /* The maximum size of the selected file var     */
BOOL redraw;               /* Redraw the current file when done?            */

begin
   char *start_dir;        /* The directory we started from                 */
   char *start_parent;     /* The parent directory of start_dir             */
   char *selected_dir;     /* Current directory user is in                  */
   char *selected_parent;  /* The parent directory of selected_dir          */
   char *child;            /* The child portion of selected_dir             */
   char *selected_entry;   /* The selected directory entry                  */

   BOOL subtending_dir;    /* Does selected_dir subtend from start_dir?     */
   int  substring_ix = 0;  /* Index of selected dir in start_dir pathname   */

   int  selection_type;    /* Type of directory entry selected              */
   int  selected_ix = 0;   /* Index of selected entry in the file_list      */

   struct str_entry_list *entry_list;  /* List of directory  entries        */
   int    num_entries;                 /* Number of entries in entry_list   */

   /* Initialize result variables */

   selected_file[0] = '\0';

    /* Since we are working with lots of directory pathnames, and these are */
    /* large, we allocate them rather than declare them.                    */

    start_dir = ve_alloc ( MAX_PATH_LEN );

    /* Establish our initial working directory = current directory, and     */
    /* save it off as start_dir                                             */

    if ( getcwd(start_dir, MAX_PATH_LEN) == NULL )
    {
       write_ve_msg ( "Failed to get current pathname" );
       free ( start_dir );
       return;
    }

    /* Finish allocating working file name buffers */

    start_parent    = ve_alloc ( MAX_PATH_LEN );
    selected_dir    = ve_alloc ( MAX_PATH_LEN );
    selected_parent = ve_alloc ( MAX_PATH_LEN );
    child           = ve_alloc ( MAX_PATH_LEN );
    selected_entry  = ve_alloc ( MAX_PATH_LEN );

    /* Load up the initial selected directory */

    strcpy ( selected_dir, start_dir );
    selection_type = ST_DIR;

   /* Set up the display page parameters - apply virtual "style sheet" for  */
   /* the page that will be displayed                                       */

   create_full_screen_info_page ( title );

   init_ofd_page_parms ();

    /* Loop until the user either selects a file or cancels out             */

    do
    begin
       /* Now load up the directory entry list that we will be working with */

       entry_list = load_ofd_entry_list ( selected_dir, &num_entries );

       if ( entry_list == NULL )
       begin
          /* Failed to load the directory list - an error message has been  */
          /* produced so just drop out                                      */

          selection_type = ST_NULL;
          break;
       end

       /* Present user with a full screen list of available entries in the  */
       /* selected directory, and allow user to interactively pick one of   */
       /* of them, which may be either a file or another directory.         */

       selection_type = file_select_dialog ( title,          entry_list, num_entries,
                                             selected_entry, MAX_FN_LEN, &selected_ix );
       if ( selection_type == ST_DIR )
       begin
          /* The user has selected a directory. If it is "." or "..",       */
          /* expand these out to their full pathnames. If it is an          */
          /* absolute pathname (starts with "/"), just use it directly.     */

          if ( selected_entry[0] == '/' )
             strcpy ( selected_dir, selected_entry );

          else if ( strcmp("..", selected_entry) == 0 )
          begin
             /* If the user has selected "..", break the currently selected  */
             /* directory into its parent and child directories, overwriting */
             /* the selected directory with its parent.                      */

             analyze_pathname ( selected_dir, selected_dir, selected_entry, FALSE );

             if ( strlen(selected_dir) == 0 )
             begin
                /* Special case - if the resulting parent directory is null, */
                /* we have reach the root. Set the parent dir to '/'.        */

                strcpy ( selected_dir, "/" );
             end

          end



          else if ( strcmp(".", selected_entry) != 0 )
          begin
             /* If "." selected, the user has elected to stay in the same   */
             /* directory - no action needed. Else, we have a new directory */
             /* selected. If it doesn't have a parent, it is relative to    */
             /* the current directory and we can append it. Else, we use it */
             /* directly.                                                   */

             analyze_pathname ( selected_entry, selected_parent, child, FALSE );

             if ( strlen(selected_parent) != 0 ) 
             begin
                /* The selected entry has a parent directory, so it is not  */
                /* relative to the current directory. Use it directly.      */

                strcpy ( selected_dir, selected_entry );
             end
             else
             begin
                /* The entry is relative to the current dir, so append   */

                strcat ( selected_dir, "/"            );
                strcat ( selected_dir, selected_entry );
             end
          end



       end /* Selected entry is a directory */

       free ( entry_list  );

    end
    while ( selection_type == ST_DIR );

    /* User has either selected a file or canceled out. Process results.    */
    /* First, check for either cancelled out or for the condition where the */
    /* pathname of the selected dir + selected entry won't fit into the     */
    /* available space. If either case is true, set the result to null.     */

    if ( selection_type == ST_NULL                                                 ||
         (strlen(selected_dir) + strlen(selected_entry) + 1) >= selected_file_size )

       selected_file[0] = '\0';

    else
    begin
       /* The user has selected a directory and a file. Convert the         */
       /* directory to its absolute pathname form, to facilitate potential  */
       /* pathname simplification, which follows.                           */
       
       chdir  ( selected_dir );
       getcwd ( selected_dir, MAX_PATH_LEN);

       /* Examine the start_dir and the selected dir, breaking them into    */
       /* parent and child components. Also determine if the selected dir   */
       /* is a subtending directory of the parent directory                 */

       analyze_pathname ( start_dir,    start_parent,    child, FALSE );
       analyze_pathname ( selected_dir, selected_parent, child, FALSE );

       subtending_dir = substring_of (start_dir,         selected_dir,
                                      strlen(start_dir), strlen(selected_dir),
                                      &substring_ix                            );

       /* Now, check to see if the selected directory is the original       */
       /* starting directory. If so, only return the filename portion.      */

       if ( strcmp(start_dir, selected_dir) == 0 )
           strcpy ( selected_file, selected_entry );

       /* Next, check if the selected directory is the parent directory. If */
       /* so, construct a relative pathname - more intuitive to user        */

       else if ( strcmp(start_parent, selected_parent) == 0 )
       begin
          /* The the selected directory and the start directory share a     */
          /* common parent. Create a relative pathname for it that will be  */
          /* a more intuitive to the user than the full pathname            */

          strcpy ( selected_file, "../"          );
          strcat ( selected_file, child          );
          strcat ( selected_file, "/"            );
          strcat ( selected_file, selected_entry );
       end

       /* Next check of the selected dir is a subtending directory of the   */
       /* parent dir. If so, eliminate the parent dir part of the filename  */
       /* making the result relative to the start_dir.                      */

       else if ( subtending_dir && substring_ix == 0 )
       begin
          strcpy ( selected_file, &selected_dir[strlen(start_dir)+1] );
          strcat ( selected_file, "/" );
          strcat ( selected_file, selected_entry );
       end

       else
       begin
          /* No other conditions apply, so construct the selected filename  */
          /* by appending the selected dir and the selected entry           */

          strcpy ( selected_file, selected_dir   );
          strcat ( selected_file, "/"            );
          strcat ( selected_file, selected_entry );
       end

    end /* The user has selected a file */

    /* Change directory back to the starting directory and free the         */
    /* allocated memory segments.                                           */

    chdir ( start_dir );

    free ( start_dir       );
    free ( start_parent    );
    free ( selected_dir    );
    free ( selected_parent );
    free ( child           );
    free ( selected_entry  );

    /* Finally, update the display page as needed, and we are done          */

    ti_enable_horizontal_scrolling ();

    if ( selection_type == ST_NULL || redraw )
       if ( curr_file_ix != -1 )
          view_cmd ();

end /* open_file_dialog */

/* .page */

int goto_file_dialog ( title, selected_file, selected_file_size, redraw )

/****************************************************************************
 *                                                                          *
 *  This is the VE Goto File Dialog. It presents a full screen menu that    *
 *  allows users to interactively select among the set of open files and    *
 *  visually select the file that they wish to switch to.                   *
 *                                                                          *
 *  The user may use this dialog to select a full filename, or may simply   *
 *  use it to navigate to the intended directory and then manually tupe in  *
 *  a filename of interest.                                                 *
 *                                                                          *
 *  This dialog is used by the [Edit, Menu] command, the GotoFile command,  *
 *  and the [Quit, Discard] command.                                        *
 *                                                                          *
 *  This dialog returns the index of the selected file in the open files    *
 *  list, and the pathname of the selected file. This dialog always returns *
 *  a valid index and filename - the user does not have the option to not   *
 *  select one. If they try, the current index at the time of the call is   *
 *  returned. The ONLY exception to this is if this function is calles      *
 *  while VE has no files open. This condition should never occur. If it is *
 *  encountered, this function returns -1 and a nil filename.               *
 *                                                                          *
 ****************************************************************************/

char title [];             /* The display page title to present             */
char selected_file [];     /* The file the user selects via the dialog      */
int  selected_file_size;   /* The maximum size of the selected file var     */
BOOL redraw;               /* Redraw the current file when done?            */

begin
   struct file_des_str *file_des;     /* Pointer to current file descriptor */
   int    selection_type;             /* Type of directory entry selected   */
   int    return_ix;                  /* File index to return               */
   int    selected_ix;                /* Index of selected entry            */

   struct str_entry_list *entry_list; /* List of available files            */
   int    num_entries;                /* Number of entries in entry_list    */

   if ( files_open == 0 )
   begin
      selected_file[0] = '\0';
      return ( -1 );
   end

   /* Initialize result variables to select the current file, or the first  */
   /* file on the open file list if the current file is -1                  */

   if ( curr_file_ix >= 0 )
   begin
      /* Current file exists - use it */

      return_ix = curr_file_ix;
      strcpy ( selected_file, curr_filename );
   end

   else
   begin
      /* No file currently selected - likely just discarded. Arbitrarily    */
      /* select the first available file.                                   */

      return_ix = 0;
      file_des  = (struct file_des_str *) open_file_list.fwd_link;
      strcpy ( selected_file, file_des->filename );
   end

   selection_type = ST_NULL;
   selected_ix    = return_ix;

   /* Set up the display page parameters - apply virtual "style sheet" for  */
   /* the page that will be displayed                                       */

   create_full_screen_info_page ( title );
   init_gfd_page_parms ();

   /* Now load up the available files list that we will be working with */

   entry_list = load_gfd_entry_list ( &num_entries );

   if ( entry_list != NULL )
   begin
      selection_type = file_select_dialog ( title,         entry_list, num_entries,
                                            selected_file, MAX_FN_LEN, &selected_ix );
      free ( entry_list  );
   end

   /* OK, user has made a selection. Analyze it and use as appropriate */

   if ( selection_type != ST_NULL )
   begin
      if ( selected_ix == 0 )
      begin
         /* The user may have manually typed in a file name - both the      */
         /* first file in the open file list and a manually typed file name */
         /* return an index of 0. Attempt to update the index to point to   */
         /* the intended file.                                              */

         selected_ix = get_file ( selected_file );

         if ( selected_ix == -1 )
         begin
            selected_ix = return_ix;

            write_ve_msg ( err_msg[E_OPEN_ERROR] );
            writechar ( BEL );
         end
      end

      return_ix = selected_ix;

   end /* User has selected a file */

   /* Finally, update the display page as needed, and we are done          */

   ti_enable_horizontal_scrolling ();

   if ( selection_type == ST_NULL || redraw )
      if ( curr_file_ix != -1 )
         view_cmd ();

   return ( return_ix );

end /* goto_file_dialog */

/* .page */

FILE *write_open ( pathname, allow_overwrite, wstatus )

/*****************************************************************************
 *                                                                           *
 *  Open the supplied file for writing.  Before doing this, which truncates  *
 *  the file, check for its existence, and if it already exists, prompt the  *
 *  user to be sure that truncation is the desired result.                   *
 *                                                                           *
 *****************************************************************************/

 char *pathname;          /* Pathname to open for writing                    */
 BOOL allow_overwrite;    /* Allow overwrite if file exists already          */
 int  *wstatus;           /* Status of the write                             */

 begin
    FILE *temp_conn;        /* File descriptor for file being opened         */
    int  open_for_write;    /* Are we opening for write or not?              */
    int  stat_res;          /* Result of attempt to stat the file            */
    struct stat file_info;  /* File information from the stat function       */

    char temp_file[MAX_PATH_LEN];    /* Temporary filename                   */

    open_for_write = TRUE;
    *wstatus       = E_OK;

    stat_res = stat ( pathname, &file_info );

    if ( stat_res != -1 && !allow_overwrite ) begin

         /* The file already exists: prompt the user */

         write_status_line ( "File already exists: Overwrite ? " );

         if ( toupper( read_char()) != 'Y' ) begin
               open_for_write = FALSE;
               *wstatus       = E_NO_FILE;
         end

         restore_cursor ();
    end

    if ( open_for_write ) begin

       /* If we are going to write this file out, open a temporary file of   */
       /* name "pathname~". and do the write to it. Later, when the write is */
       /* complete, the original file will be deleted and pathname~ will     */
       /* be renamed to pathname. This protects against a bug in the write   */
       /* code causing a crash which completely eliminates the file you were */
       /* trying to save (fopen for write truncates the file to zero length) */

       strcpy ( temp_file, pathname );
       temp_file[strlen(pathname)-1] = '~';

       temp_conn = fopen ( temp_file, "wb" );

       /* If the file already existed, we need to override the default mode  */
       /* and permission bits assigned to the new temp file by fopen so that */
       /* they match those of the original file (because the temp file will  */
       /* ultimately be renamed, becoming the updated file). So, close the   */
       /* temporary file, update it with the mode from the stat call, and    */
       /* re-open. Grotty but effective.                                     */

       if ( stat_res != -1 && temp_conn != NULL ) begin
          fclose ( temp_conn );
          chmod  ( temp_file, file_info.st_mode );
          temp_conn = fopen ( temp_file, "wb" );
       end

       if ( temp_conn == NULL ) then
          *wstatus = E_OPEN_ERROR;
    end
    else
       temp_conn = NULL;

    return ( temp_conn );
    
end /* write_open */

/* .page */

void write_close ( write_conn, write_fn )

/****************************************************************************
 *                                                                          *
 * This function closes a file that has been written and was opened by the  *
 * above write_open function. It takes care of the fact that the write      *
 * actually occured to a temporary file whose name was the same as the      *
 * requested file, with ~ as 1st char. So, this function closes the write   *
 * conn, deletes the original file, and renames the "~" version to the      *
 * original name.                                                           *
 *                                                                          *
 ****************************************************************************/

FILE *write_conn;          /* The write connection to close                 */
char write_fn [];          /* The filename associated with the connection   */

begin
    char temp_file [MAX_PATH_LEN];    /* Temporary filename, write_fn.ve    */

    fclose ( write_conn ); /* Close the file now that it has been written   */

    /* Write has completed to temporary file "~write_fn". Now delete the    */
    /* original file and rename ~write_fn to just write_fn                  */

    remove ( write_fn );              /* Delete the original file           */

    strcpy ( temp_file, write_fn );
    temp_file[strlen(write_fn)-1] = '~';

    rename ( temp_file, write_fn );   /* Rename the temporary file          */

end /* write_close */

/* .page */

int write_file ( write_fn, allow_overwrite, ff, start_line, end_line, 
                 num_chars, num_lines, checksum )

/****************************************************************************
 *                                                                          *
 *   This function writes out the contents of the file whose filename       *
 *   and file block is supplied. It loads the number of chars, the number   *
 *   of lines, the checksum vars whose pointers are also given.             *
 *                                                                          *
 *   This function returns its overall success result, one of E_OK or       *
 *   E_OPEN_ERROR.                                                          *
 *                                                                          *
 *   This function is called from update_cmd (quitcmd.c) and block_save_as  *
 *   (blockcmd.c) and is the only code in VE that writes out text files to  *
 *   disk after editing.                                                    *
 *                                                                          *
 ****************************************************************************/

char *write_fn;              /* Filename to write to                        */
BOOL allow_overwrite;        /* Overwrite if exists?                        */
char ff;                     /* The file format                             */
struct node *start_line;     /* First line to write                         */
struct node *end_line;       /* Last line to write                          */
int *num_chars;              /* Output: num chars in file                   */
int *num_lines;              /* Output: num lines in file                   */
int *checksum;               /* Output: checksum of file                    */

begin
    FILE *write_conn;        /* Connection to file to write                 */
    int result;              /* Result of write operation                   */
    int eol_size = 1;        /* End of Line size: LF (linux) or CR,LF (DOS) */
    register int i = 0;      /* Just an index                               */
    register int len;        /* Length to copy                              */
    char eol_str [4];        /* CR, LF or just LF                           */
    struct node *cnode;      /* Current line                                */
    struct node *enode;      /* End of write line                           */
    struct node *wnode;      /* Line to be written                          */

    /* Open the temp file write_fn.ve */

    write_conn = write_open ( write_fn, allow_overwrite, &result );

    if ( write_conn == NULL ) then
       return ( E_OPEN_ERROR );

    if ( ff == 'd' ) begin
       eol_size = 2;
       eol_str[i++] = CR;
    end
    eol_str[i++] = LF;
    eol_str[i]   = '\0';

    *num_chars = *num_lines = *checksum = 0;
    cnode      = start_line;
    enode      = end_line->fwd_link;

    /* Starting at the head, write each line into the file until the end of */
    /* the RAM image is reached.                                            */

    while ( cnode != enode && cnode != EOL ) begin

       /* Make sure we have room for the line and eol in temp.text. If not, */
       /* truncate the line.                                                */

       if ( cnode->length >= MAX_LINE_SIZE - 2 ) then
          cnode->length = MAX_LINE_SIZE - 2;

       wnode       = cnode;
       *num_chars += cnode->length + eol_size;
       *num_lines += 1;

       *checksum  += sumw ( cnode->text, cnode->length/sizeof(int) );
       for ( len = cnode->length % sizeof(int), i = 1; i <= len; i++ )
          *checksum += (int) cnode->text[cnode->length-i];

       if (options.output_tab_translation) begin

           /* If output tab translation is on, copy the current line to the */
           /* temporary buffer, do an inline pack of it, and write it out.  */
           /* The temporary buffer is used to ensure that output tab        */
           /* translation effects only the written version of the file, not */
           /* the in-RAM version of it.                                     */

           movb ( &cnode->length, &temp.length, cnode->length+sizeof(int) );
           temp.length = pack (temp.text, temp.length, options.tab_stop);
           wnode = &temp;
       end

       wnode->text[wnode->length] = '\0';    /* Add the line end characters     */
       strcat ( wnode->text, eol_str );

       fwrite ( wnode->text, wnode->length+eol_size, 1, write_conn );

       cnode = cnode->fwd_link;
    end

    write_close ( write_conn, write_fn );

    return ( E_OK );

end /* write_file */

/* .page */

int read_file ( read_fn, file_block, num_chars, num_lines, checksum, ff )

/****************************************************************************
 *                                                                          *
 *   This function reads in the contents of the file whose filename is      *
 *   supplied, and adds it to the internal file structure whose file block  *
 *   pointer is supplied. It loads the number of chars, the number of lines *
 *   the checksum and file format vars whose pointers are also supplied.    *
 *                                                                          *
 *   This function returns its overall success result, as one of E_OK,      *
 *   E_OPEN_ERROR (file does not exist), E_BAD_FILE (not a text file) or    *
 *   E_MEM (ran out of memory).                                             *
 *                                                                          *
 *   This function is called from edit_file (editcmd.c) and block_read      *
 *   (block_cmd.c) and is the only code in VE that reads in text files for  *
 *   editing usage.                                                         *
 *                                                                          *
 ****************************************************************************/

char *read_fn;                /* File to read from                          */
struct file_ctl *file_block;  /* File control block to add file to          */
int *num_chars;               /* Output: num chars in file                  */
int *num_lines;               /* Output: num lines in file                  */
int *checksum;                /* Output: checksum of file                   */
char *ff;                     /* Output: the file format (DOS or Unix)      */

begin
    FILE *read_conn;      /* Connection to file to read                     */
    register int i;       /* Loop counter                                   */
    register unsigned cc; /* The current character                          */
    int eol_size;         /* End of Line size: LF (linux) or CR,LF (DOS)    */
    int line_size;        /* Length of line as read from file               */
    int count;            /* Adjusted line size without line end chars      */
    int result;           /* The result of this function                    */
    int *src, *dst;       /* Source and destination for copies              */
    int len;              /* The length to copy                             */

int line_num = 0;

    result = E_OK;      /* Assume success to start                          */
    *ff = 'u';          /* Assume unix formatted file to start              */
    *num_chars = *num_lines = *checksum = 0;

    read_conn = fopen ( read_fn, "r" );
    if ( read_conn == NULL ) then
       return ( E_OPEN_ERROR );

    /* Read up to MAX_LINE_SIZE + 1 bytes in an attempt to read a line. Note */
    /* that the "+ 1" is needed, as fgets always adds a trailing "\0". Hence */
    /* specifying "MAX_LINE_SIZE + 1" ensures that fgets can read up to      */
    /* MAX_LINE_SIZE bytes of real data.                                     */

    while ( fgets(temp.text, MAX_LINE_SIZE+1, read_conn) != NULL && 
            result == E_OK                                     ) begin

        /* Determine the length of this line and scan it at the same    */
        /* time for non text characters.                                */

line_num++;

        count = line_size = MAX_LINE_SIZE+1;

        for ( i = 0; i < MAX_LINE_SIZE+1; i++ ) begin

           cc = (unsigned) temp.text[i] & 0xff;

           /* Determine the line size                                       */

           if ( cc == '\0' ) begin
              count = line_size = i;
              break;
           end

           /* Check for invalid characters - done inline for speed          */

           if ( ( cc != 0x09               ) &&           /* Tab            */
                ( cc != 0x0a               ) &&           /* LF             */
                ( cc != 0x0c               ) &&           /* FF             */
                ( cc != 0x0d               ) &&           /* CR             */
                ( cc <  0x20 || cc >  0x7e ) &&           /* Basic chars    */
                ( cc <  0x80 || cc >  0xfe )     ) then   /* Extended chars */
              result = E_BAD_FILE;

if ( result == E_BAD_FILE )
begin
   sprintf ( temp.text, "%s %d %s %x", "Bad char at line ", line_num, "value is ", cc );
   write_status_line ( temp.text );
   wrefresh ( ve_win );
   getch ();
   break;
end 

        end /* for */


        /* Strip any trailing line end characters, typically CR, LF         */
        
        if ( temp.text[count-1] == LF ) then
            count--;
        if ( count > 0 ) then
           if ( temp.text[count-1] == CR ) then
               count--;

        if ( count + 2 == line_size ) then    /* Removed 2 line-end: CR,LF  */
            *ff = 'd';                        /* Set file format to DOS     */

        temp.length = count;

        /* If tabs are enabled and input tabs translation is enabled        */
        /* unpack the input line: expand tabs to spaces.                    */

        if (options.input_tab_translation) then
            temp.length = unpack (&temp.text, count, options.tab_stop);

        /* Because we will copy using words, allocate the required size     */
        /* plus one extra word, to allow copy to slosh over end of line     */

        cnode = (struct node *) ve_alloc (temp.length + DES_SIZE + sizeof(int));

        /* Else copy the line into the node and add the node to the list   */
        /* and increment the lines and chars counters, accounting for      */
        /* the line end chars as well.                                     */

        eol_size   = ( *ff == 'u' ) ? 1 : 2;
        *num_chars += temp.length + eol_size;
        *num_lines += 1;

        /* Copy the line to the node using the native word size of the     */
        /* machine (assumed to be sizeof(int) for greatest efficiency when */
        /* dealing with large files. Since we are passing over the data    */
        /* anyway, compute the checksum in line, rather than the usual     */
        /* sumw() call.                                                    */

        len = ( temp.length / sizeof(int) ) + 2; /* 1 for .length, + 1     */

        for ( src = &temp.length, dst = &cnode->length, i = 0; i < len; i++ )
        begin
           dst[i] = src[i];
           *checksum += src[i];
        end

        /* Eliminate the ".length" word and the last word from the checksum */
        /* and pick up the trailing bytes not handled by the word aligned   */
        /* summing. Not a true checksum at this point, but it is a unique   */
        /* mathmatical transform on the contents of the line.               */

        *checksum = *checksum - src[0] - src[len-1];

        for ( len = cnode->length % sizeof(int), i = 1; i <= len; i++ )
           *checksum += (int) cnode->text[cnode->length-i];

        send_node ( file_block, cnode );

        if ( (*num_lines % 524288) == 0) begin

           /* For VERY large files, every 1/2 million lines put out an on   */
           /* screen indicator that VE is busily loading the file           */

           sprintf ( temp.text, "%s %d", "Reading file, now at line", *num_lines );
           mv_writestr ( 0, 0, FALSE, ve_masthead );
           mv_writestr ( 10, lines/2-2, FALSE, temp.text );
           ti_force_screen_update ();
        end

    end  /* while */

    if ( *num_chars == 0 ) begin

       /* An empty file! Establish a first line for VE to work with */

       cnode = (struct node *) ve_alloc (80 + DES_SIZE);
       *num_chars    = 1;
       *num_lines    = 1;
       cnode->length = 0;
       send_node ( file_block, cnode );
    end

    fclose ( read_conn );

    return ( result );

end /* read_file */

/* .page */

void file_info ( file_des, incl_des, num_bytes, num_lines, checksum )

/****************************************************************************
 *                                                                          *
 *   Determine the size of a file by counting up the size of all of its     *
 *   lines.                                                                 *
 *                                                                          *
 ****************************************************************************/

 struct file_ctl *file_des;  /* Pointer to file control block.              */
 int    incl_des;            /* Include size of descriptor: yes/no          */
 int    *num_bytes;          /* Pointer to number of bytes integer          */
 int    *num_lines;          /* Pointer to number of lines integer          */
 int    *checksum;           /* The checksum of the file's data             */

 begin
    register int eol_size;   /* End of Line size: LF (linux) or CR,LF (DOS) */
    register int i, len;     /* Checksum variables                          */

    cnode = file_des->head;

    *num_bytes = *num_lines = *checksum = 0;
    eol_size   = ( file_des->file_format == 'u' ) ? 1 : 2;

    while ( cnode != EOL ) begin

        *num_lines += 1;

        *num_bytes += cnode->length + eol_size;
        if ( incl_des ) then
             *num_bytes += DES_SIZE;

        /* Compute a "checksum" - proper sum for all word aligned data, then */
        /* throw in any non word aligned bytes left over. Not a true         */
        /*  checksum at this point, but is a unique tranform on the line     */

        *checksum += sumw ( cnode->text, cnode->length/sizeof(int) );

        for ( len = cnode->length % sizeof(int), i = 1; i <= len; i++ )
           *checksum += (int) cnode->text[cnode->length-i];

        cnode = cnode->fwd_link;

    end

end /* file_info */

/* .page */

BOOL curr_file_has_changed ()

/****************************************************************************
 *                                                                          *
 *   Determine if the current file has changed since the last read or write *
 *   by comparing its total chars, lines and checksum. Return TRUE if it    *
 *   has changed, or FALSE if it has not.                                   *
 *                                                                          *
 ****************************************************************************/

begin
   int  num_chars, num_lines, checksum;

   file_info ( &curr_file, FALSE, &num_chars, &num_lines, &checksum );

   return ( num_chars != curr_file.num_chars ||
            num_lines != curr_file.num_lines ||
            checksum  != curr_file.checksum     );

end /* curr_file_has_changed */

/* .page */

BOOL unsaved_changes ( num_changed_files )

/****************************************************************************
 *                                                                          *
 *  This function scans all the presently open files to determine if there  *
 *  are any with outstand changes that have not been saved. It returns      *
 *  TRUE if there are any, or FALSE if there are not.                       *
 *                                                                          *
 *  This function is called from [Quit, Absolute].                          *
 *                                                                          *
 ****************************************************************************/

int *num_changed_files;               /* Number of files with changes       */

begin
   struct file_des_str *file_des;     /* Pointer to current file descriptor */
   BOOL file_changed;                 /* Has current file changed?          */
   BOOL changes;                      /* Have ANY files changed?            */

   int num_lines;                     /* Number of lines, current file      */
   int num_chars;                     /* Number of chars, current file      */
   int checksum;                      /* Checksum, current file             */

   changes            = FALSE;
   *num_changed_files = 0;

   /* Now, examine each file and determine if changes made                  */

   file_des = (struct file_des_str *) open_file_list.fwd_link;

   while ( file_des != NULL )
   begin
      file_info ( &file_des->file_list, FALSE, &num_chars, &num_lines, &checksum );

      file_changed = ( num_chars != file_des->file_list.num_chars ||
                       num_lines != file_des->file_list.num_lines ||
                       checksum  != file_des->file_list.checksum     );

      if ( file_changed )
      begin
         *num_changed_files += 1;
         changes            =  TRUE;
      end

      file_des = (struct file_des_str *) file_des->fwd_link;
   end

   return ( changes );

end /* unsaved_changes */

