#include <stdio.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  : quitcmd.c
 
                       VERSION : v3.5
 
                       REVISED : 31 Oct 2005
 
                       AUTHOR  : Michael A. Campbell
 
 
 
 
 
 


                          FUNCTIONAL DESCRIPTION
                          ----------------------
 
               This module contains the quit commands for VE.  For
 
               v1.0 onward, QUIT is a menu driven command, allowing
 
               a variety of options, and supporting the multi-file
 
               nature of VE.








*/

/* .page */

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

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

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

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

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

#include "../ext/termif.ext"
#include "../ext/kbdif.ext"
#include "../ext/veutils.ext"
#include "../ext/fileutil.ext"
#include "../ext/lnklst.ext"
#include "../ext/stacks.ext"
#include "../ext/columncmd.ext"
#include "../ext/undo.ext"

extern void exit ();

/********************
 * LOCAL VARS       *
 ********************/

static char *replace_prompt [] = {
            " - Select/Type Filename To Edit ",
            " - Could not open. Again? (ESC-q to Quit) " };

/* .page */

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

void discard_curr_file ( file_head, include_descriptor )

/****************************************************************************
 *                                                                          *
 *   Release the current file, and then delete its descriptor.              *
 *                                                                          *
 ****************************************************************************/

struct node *file_head;
BOOL include_descriptor;

begin
    int actual;

    line_buf.length = 0;    /* Instead of update_curr_line()                */

    cnode = file_head;

    /*  Iterate through each line of the file, releasing it. */

    if ( cnode != EOL) begin

        while (cnode != EOL) begin

            nnode = cnode->fwd_link;
            free (cnode);

            cnode = nnode;
        end

        setb (0, &curr_file,     sizeof (curr_file));
        setb (0, &curr_screen,   sizeof (curr_screen));
        setb (0, &curr_filename, sizeof(curr_filename) );
        curr_screen.cy = 2;        
    end

    if ( include_descriptor ) begin
       file_des = (struct file_des_str *) traverse_list ( &open_file_list, 
                                          open_file_list.fwd_link, '+',
                                          &actual, curr_file_ix, &status );

       file_des = (struct file_des_str *) delete ( &open_file_list, file_des, 
                                                   &status );
       free ( file_des );
    end

    files_open -= 1;

    file_des = curr_file_des = NULL;
    curr_file_ix = -1;

end /* discard_curr_file */

/* .page */

void discard_all_files ()

/****************************************************************************
 *                                                                          *
 *   This function discards all open files, freeing all associated memory.  *
 *   It simply parks itself at the front of the current file list, and      *
 *   deletes the first file on the list. This deletion moves the next file  *
 *   to the head of list, and the process repeats until the list is empty.  *
 *                                                                          *
 ****************************************************************************/

 begin
    struct file_des_str *des;
    struct file_ctl     *file_des, *column_block;
    struct node         *cnode;

    /* Pick up the head of the list of file descriptors. */

    curr_file_ix = 0;               /* Always working on the first file     */

    des = (struct file_des_str *) open_file_list.head;

    /* Iterate through each descriptor, summing up all of the bytes used    */
    /* by lines in that descriptor.                                         */

    while ( des != EOL && files_open > 0 ) begin

        /* Pick up the head of this file, and feed it to the individual file */
        /* discarder. The check for files_open > 0 is just a failsafe.       */

        file_des = &(des->file_list);
        discard_curr_file ( file_des->head, TRUE );

        /* discard_curr_file has deleted the current file's lines and its   */
        /* descriptor. A new descriptor has been linked in to               */
        /* open_file_list.head. Pick it up and carry on.                    */

        des = (struct file_des_str *) open_file_list.head;
    end

    /* Have now done all of the nodes and descriptors of the open files.    */
    /* Do the line and column block buffers as well.                        */

    if ( line_block.fwd_link != EOL ) then 
       discard_curr_file ( line_block.head, FALSE );

    if ( line_del_block.fwd_link != EOL ) then 
       discard_curr_file ( line_del_block.head, FALSE );

    column_block = get_column_block_ptr ();
    if ( column_block->fwd_link != EOL ) then 
       discard_curr_file ( column_block->head, FALSE );
  
    /* Finally, free all memory held on the undo queue */

    flush_undo_queue ();

end /* discard_all_files */

/* .page */

BOOL confirm_discard ()

/******************************************************************************
 *                                                                            *
 *   The user has asked to discard the current file. Check to see if the file *
 *   has changed before doing so. If it has, prompt the user with this fact   *
 *   and seek confirmation before actually discarding the file. This function *
 *   and seek confirmation before actually discarding the file. This function *
 *   returns TRUE if the file is truly to be discarded, and FALSE if the user *
 *   has second thoughts after being told the file has changed.               *
 *                                                                            *
 ******************************************************************************/

begin
   BOOL discard_file = TRUE;
   char key;

   if ( curr_file_has_changed() ) begin

       /*  File has been modified since it was read in or last saved.  Ask   */
       /*  for confirmation before dumping it.                               */

       discard_file = FALSE;

       write_status_line ( "File has been modified: Are you sure ? (y or n) : " );
       key = toupper ( read_char () );

       if ( key == 'Y' ) then
           discard_file = TRUE;

       else if ( key != 'N' ) then
           writechar ( BEL );

       if ( !discard_file ) then
          write_ve_msg ( "File has been retained" );

   end /* file has changed */

   return ( discard_file );

end /* confirm_discard */

/* .page */

void quit_ve (exit_code)

/******************************************************************************
 *                                                                            *
 *   Leave the screen in a nice way by moving the cursor to the bottom of the *
 *   screen before returning to OS                                            *
 *                                                                            *
 ******************************************************************************/

int exit_code;

begin
    /* Release all allocated memory                                          */

    discard_all_files ();
    release_reserve_memory ();

    /* Clear the last two lines of the display                               */

    ti_clear_display ( lines - 2 );
    ti_force_screen_update ();

    /* Exit ncurses and we are done!                                         */

    ti_reset_char_mode ();

    /* Ciao!                                                                 */

    exit (exit_code);   /* Return to OS */

end /* quit_ve */

/* .page */

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

void update_cmd ()

/*****************************************************************************
 *                                                                           *
 *   Update the currently editing file by writing the RAM image back into    *
 *   the file. This function updates the current file's num_chars, num_lines *
 *   and checksum fields, so that the file is once again considered          *
 *   unchanged until the user's next change to it.                           *
 *                                                                           *
 *****************************************************************************/

begin
    struct node *cnode;
    int eol_size;         /* Size of End of Line: LF (linux) or CR,LF (DOS) */
    int result;           /* Result of write attempt                        */

    update_curr_line ();

    if ( curr_file_des == NULL ) then
        return;

    /* If there is no file, prompt the user for a new filename, and open it. */

    if (curr_conn == NULL) 
    begin
        open_file_dialog ( select_file_write, temp.text, MAX_LINE_SIZE, TRUE );

        if ( strlen(temp.text) != 0 )
        begin
           /* Now that we know the name of the current file, update the     */
           /* file descriptor for future reference.                         */

           strcpy ( curr_filename, temp.text               );
           strcpy ( curr_file_des->filename, curr_filename );
        end
        else
        begin
           write_ve_msg ( no_file_select_write );
           return;
        end

    end

     /* Attempt to write the file out */

    result = write_file ( curr_filename, TRUE, curr_file.file_format, curr_file.head, 
                          curr_file.tail, &curr_file.num_chars, &curr_file.num_lines, 
                          &curr_file.checksum                              );

    /* Output an indication of file writing status. If sucessful, set   */
    /* curr_conn to a non NULL value, so that no reprompting will occur */

    if ( result == E_OK ) 
    begin
       write_ve_msg ( "File has been written" );

       if ( curr_conn == NULL )
       begin
          curr_conn = (FILE *) 0xc7c7;      /* any non NULL value is OK */
          set_status_line ( TRUE, E_OK, last_ve_mode );
       end
    end

    else
    begin
       write_ve_msg ( "Failed to write file"  );

       if ( curr_conn == NULL )
          strcpy ( curr_filename, "Unnamed File" );
    end

end /* update_cmd */

/* .page */

void quit_cmd ()

/****************************************************************************
 *                                                                          *
 *   Present a menu of quit options, and act on the users selection.        *
 *                                                                          *
 ****************************************************************************/

begin
    int num_changed_files;  /* Number of files with unsaved changes         */
    int new_file_ix;        /* Index of new file to switch to               */
    int prompt_ix;          /* Index for replacement prompt                 */
    int status;             /* Status of user's input selection             */
    int fn_len;             /* Length of truncated filename                 */
    char key;               /* Users selection                              */
    BOOL invalid_selection; /* Was the entered key a valid selection?       */
    FILE *temp_conn;        /* File connection                              */
    char display_fn [82];   /* Displayed version of current filename        */

    char select_another[] = " - Select Another File to Switch To";
    char quit_menu []     = "Abs/All  Save  Discard/Quit  Replace  Write  Update : ";

    update_curr_line ();

    if ( files_open == 0 ) then
        write_ve_msg ( "No files open" );

    else begin
        /*  At least one file is open.  Present a menu of quit options      */
        /*  and the current filename and then act on the user's selection.  */

        truncate_str_left ( curr_filename, display_fn, FN_FIELD_WIDTH-1 );
        fn_len = strlen ( display_fn );

        push_status_line ( quit_menu );

        invalid_selection = TRUE;

        while ( invalid_selection ) begin

            mv_writestr ( physical_cols-fn_len-1, 0, FALSE, display_fn );
            ti_move_cursor ( strlen(quit_menu), 0 );

            key = toupper (read_char() );
            invalid_selection = FALSE;

            if ( key == 'A' ) begin

                status = E_OK;

                /* Selection is absolute: users wants to quit absolutely.   */
                /* Check for unsaved changes, and if any found give user a  */
                /* friendly reminder before allowing them to commit suicide */

                if ( unsaved_changes(&num_changed_files) )
                begin
                   status = E_UNCONFIRMED;

                   sprintf ( temp.text, "%d %s", num_changed_files, 
                            "file(s) with unsaved changes. Are you sure? " );

                   write_status_line ( temp.text );
                   key = toupper(read_char ());

                   if ( key == 'Y' ) then
                      status = E_OK;
                end

                if ( status == E_OK ) then
                    quit_ve ( E_OK );

            end /* key == A */


            else if ( key == 'S' ) begin

                /*  Selection is Save: Save this file and then discard it.  */
                /*  If there are no other open files, return to the OS.     */
                /*  If there are open files, drop into the edit menu.       */

                update_cmd ();

                /* When run over telnet to some distros, exiting VE doesn't */
                /* clear the screen. Hence it is important the status line  */
                /* be tidy at time of exit. If we are about to exit, make   */
                /* sure it is set correctly.                                */

                if ( files_open == 1 )
                   pop_status_line ();

                /* Having saved the file, now discard it, and either do a   */
                /* context switch to another file, present the interactive  */
                /* file menu, to select the next file, or exit.             */

                discard_curr_file ( (struct node *) curr_file.head, TRUE );

                if ( files_open > 0 ) begin
                    if ( files_open == 1 ) then
                       context_switch ( -1, 0 );
                    else
                       context_switch ( -1, goto_file_dialog ( select_another,
                                        temp.text, MAX_LINE_SIZE, FALSE ) );
                end

            end /* key == S */


            else if ( key == 'D' || key == 'Q' ) begin

                /*  Selection is Discard: Discard this file.  When done */
                /*  drop into the edit menu.                            */

                if ( confirm_discard () ) begin

                    /*  Get rid of this file, and go to the edit menu.      */

                    /* When run over telnet to some distros, exiting VE     */
                    /* doesn't clear the screen. Hence it is important the  */
                    /* status line be tidy at time of exit. If we are about */
                    /* to exit, make sure it is set correctly.              */

                    if ( files_open == 1)
                       pop_status_line ();

                    discard_curr_file ( (struct node *) curr_file.head, TRUE );

                    if ( files_open > 0 ) then
                        if ( files_open == 1 ) then
                           context_switch ( -1, 0 );
                        else
                           context_switch ( -1, goto_file_dialog ( select_another, 
                                            temp.text, MAX_LINE_SIZE, FALSE ) );

                end /* confirm_discard */

            end /* key == D */


            else if ( key == 'R' ) begin

                /*  Selection is Replace: Discard the current file, and     */
                /*  replace it with another file.                           */

                if ( confirm_discard() ) 
                begin
                   /* User has confirmed discard of current file */

                   discard_curr_file ( (struct node *) curr_file.head, TRUE );

                   ti_clear_display ( 2 );

                   status = E_NO_REPLACEMENT_FILE;
                   prompt_ix = 0;  /* "Select/Type Replacement File" */

                   while ( status != E_OK ) 
                   begin

                      open_file_dialog ( replace_prompt[prompt_ix],
                                         curr_filename, sizeof(curr_filename), FALSE );

                      prompt_ix = 1; /* "Could not open - Again? (ESC-q to quit)" */
                      
                      if ( strlen(curr_filename) == 0 ) 
                      begin
                         /* The user has entered ESC-q - they have decided  */
                         /* not to specify a replacement file after all. If */
                         /* we have just this one file open, that is it -   */
                         /* say goodnight Gracie! If we have exactly two    */
                         /* files open, simply switch to the other one. If  */
                         /* we have >2 files open, present the Goto File    */
                         /* Dialog, and let the user select a new file to   */
                         /* switch to.                                      */

                         status = E_OK;

                         if ( files_open == 0 ) 
                            break;

                         else if ( files_open == 1 )
                            context_switch ( -1, 0 );

                         else
                            context_switch ( -1, goto_file_dialog(select_another,
                                             temp.text, MAX_LINE_SIZE, FALSE ) );
                      end

                      else
                      begin
                         /* The user has entered a filename to replace the  */
                         /* file we are discarding. First, check if it is   */
                         /* one of the files we presently have open. If so, */
                         /* simply switch to it.                            */

                         if ( (new_file_ix = get_file (curr_filename)) != -1 )
                         begin
                            /* We already have the selected file open, so   */
                            /* just switch to it.                           */

                            context_switch ( curr_file_ix, new_file_ix );
                            status = E_OK;
                         end

                         else
                         begin
                            /* We don't have the file open, so try to add it */

                            add_file ( curr_filename );
                            status = ve_exception;
                         end

                         if ( status != E_OK ) 
                            writechar ( BEL );

                      end /* User has entered a filename */

                   end /* while trying to get file to replace with */

                end /* User has confirmed discard of current file */

            end /* key == R */


            else if ( key == 'W' ) begin

                /*  Selection is Write:  Write the current file out under   */
                /*  a different name. Temporarily overwrite the current     */
                /*  filename, use update_cmd () to write out the file, and  */
                /*  restore the current filename from the current file des. */

                open_file_dialog ( select_file_write, curr_filename, sizeof(curr_filename), TRUE );

                if ( strlen(curr_filename) != 0 )
                begin
                   /* If the user selected a file to write to, do the write */
                   /* else acknowledge that no file was written             */

                   temp_conn = curr_conn;
                   curr_conn = write_open ( curr_filename, FALSE, &status );

                   if ( status == E_OK ) then
                   begin
                        fclose ( curr_conn );  /* because update_cmd re-opens */
                        update_cmd ();
                   end
                   else
                      write_ve_msg ( "File not written" );

                   curr_conn = temp_conn;
                end
                else
                   write_ve_msg ( no_file_select_write );

                strcpy ( curr_filename, curr_file_des->filename );

            end  /* key == W */


            else if ( key == 'U' ) begin

                /*  Selection is Update:  Update the current file.          */

                update_cmd ();

            end /* Key == U */


            else if ( key != ENTER ) begin

                /*  No valid selection was chosen.  Tell the dummy this !   */

                writechar ( BEL );
                invalid_selection = TRUE;

                write_status_line ( tos(&status_stack) );

            end /* no valid selection made */

        end /* while invalid_selection */

        if ( files_open == 0 ) begin

            /* No files are open, so quit VE */

            quit_ve ( E_OK );

        end /* Files open == 0 */

        pop_status_line (); /* Restore previous menu, if any */

    end /* One or more files open */

end /* quit_cmd */

