#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  : blockutil.c
 
                       VERSION : v3.3
 
                       REVISED : 28 May 2005
 
                       AUTHOR  : Michael A. Campbell
 
 
 
 
 
 


                          FUNCTIONAL DESCRIPTION
                          ----------------------
 
               This module contains utilities common to the two VE
 
               block commands, Block and Column. Included are functions
 
               to define the line and column ranges of a block, and 
 
               fucntions to clear range markers on screen.










*/

/* .page */

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

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

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

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

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

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

/* .page */

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


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

/* .page */

/**********************
 * PRIVATE PROCEDURES *
 **********************/

char write_marker ( marker, cx, cy, line )

/*****************************************************************************
 * This function places a visual marker on screen at the indicated (x, y)    *
 * and returns the character that was displayed at that position             *
 *****************************************************************************/

char marker;         /* The character to write                               */
int cx, cy;          /* x, y co-ordinate to write the marker at              */
struct node *line;   /* The line displayed at cy                             */

begin

   ti_move_cursor ( cx, cy );
   writechar_emphasis ( marker );
   ti_move_cursor ( cx, cy );

   return ( (line->length > cx) ? line->text[cx] : ' ' );

end /* write_marker */

/* .page */

void delete_line_markers ( cx, block_start, start_char, block_end, end_char )

/******************************************************************************
 *                                                                            *
 *   Delete the visual delimiters of the current line range, which have been  *
 *   output during interactive definition of the line range.                  *
 *                                                                            *
 ******************************************************************************/

int         cx;            /* x co-ordinate that markers were placed at       */
struct node *block_start;  /* First line of block                             */
char        start_char;    /* Original character at marker position           */
struct node *block_end;    /* Last line of block                              */
char        end_char;      /* Original character at marker position           */

begin
   int cy;

   /* Replace the first marker (start of block)                             */

   if (displayed(block_start, &cy)) 
   begin
       ti_move_cursor (cx, cy);
       writechar (start_char);
   end

   /* Replace the second marker, if required                                */

   if (block_start != block_end) 
   begin
       ti_move_cursor ( cx, curr_screen.cy );
       writechar (end_char);
   end

   /* Return the cursor to its current x, y                                 */

   restore_cursor ();

end /* delete_line_markers */

/* .page */

void delete_column_markers ( cy, line )

/*****************************************************************************
 *                                                                           *
 *   Delete the visual delimiters of the current column range, which have    *
 *   been output during interactive definition of the line range.            *
 *                                                                           *
 *****************************************************************************/

int    cy;
struct node *line;

begin
   ti_move_cursor ( curr_screen.cx, cy );

   /* For a zero length line, simply go to the left edge and clear to the    */
   /* end of the line                                                        */

   if ( line->length == 0 )
   begin
      ti_move_cursor ( ti_get_left_cx(), cy );
      ti_clear_to_eol ( cy );
   end

   /* For lines with text, simply redraw the line. In case the markers are   */
   /* beyond the end of the text, clear to end of line. This is not done if  */
   /* the line we are writing fills the full screen width, since ncurses     */
   /* seems to wrap to the next line under these conditions.                 */

   else
   begin
      writeln ( line->text, line->length, 0 );
      if ( line->length < ti_get_right_cx() )
         ti_clear_to_eol ( cy );
   end

end /* delete_column_markers */

/* .page */

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

void exchange_line_column_blocks ()

/*****************************************************************************
 *                                                                           *
 * This function exchanges the contents of the Line and Column block         *
 * buffers, allowing line operations to be undertaken on copied columns      *
 * (rectangles) and/or visa versa. This function is called from both the     *
 * Block and Column commands, via their respective Xchng commands.           *
 *                                                                           *
 *****************************************************************************/

begin
   struct file_ctl temp_block;
   struct file_ctl *column_block;

   column_block = get_column_block_ptr ();

   /* Save the Column block to the Temporary block, and then copy the Line  */
   /* block to the Column block. Finally, overwrite the Line block with     */
   /* the Temporary block.                                                  */

   movb ( column_block, &temp_block,  sizeof(struct file_ctl) );
   movb ( &line_block,  column_block, sizeof(struct file_ctl) );
   movb ( &temp_block,  &line_block,  sizeof(struct file_ctl) );

end /* exchange_line_column_blocks */

/* .page */

BOOL define_line_range ( file_area, cx, exit_cmds, num_exit_cmds )

/*****************************************************************************
 *                                                                           *
 *  This function interacts with the user on screen to define the range of   *
 *  lines to be included in the block being defined. This range is returned  *
 *  as part of the file_area argument, which is updated by this function.    *
 *  See later function define_column_range for the equivalent function for   *
 *  columns. The VE Block command uses only this define_line_range function. *
 *  The VE Column command uses both this and the define_column_range         *
 *  functions.                                                               *
 *                                                                           *
 *  This function returns TRUE if a line range is successfully defined, or   *
 *  FALSE if not (typically because user decided to exit via ENTER).         *
 *                                                                           *
 *****************************************************************************/

struct t_file_area *file_area;        /* The file_area structure             */
int    cx;                            /* x co-ord to put line marker at      */
char   exit_cmds [];                  /* Cmds that will exit range defn      */
int    num_exit_cmds;                 /* Number of commands in above         */

begin
    register int cmd_ix, i;      /* x co-ord, command index, loop counter    */
    register char key;           /* User input                               */
    register struct node *cnode; /* Working curr line                        */
    int  prev_ve_mode;           /* Last_ve_mode prior to block_cmd          */
    int  cy;                     /* Working y co-ord                         */

    struct node *block[2];      /* First and last lines of the line range    */
    struct node *block_start;
    struct node *block_end;

    char start_char, end_char;  /* Characters overwritten with markers       */
    char dont_care;             /* Return value that isn't important         */

    /* Initialize the block definition variables                             */

    prev_ve_mode = last_ve_mode;

    block[0] = block[1] = block_start = block_end = curr_line;

    /* Output visual marker for start of block                               */

    start_char = end_char = write_marker ( '>', cx, curr_screen.cy, curr_line );

    /* Allow the user to define the block by way of cursor_up, cursor_down,  */
    /* home, and jump commands                                               */

    FOREVER begin
        write_status_line ( tos(&status_stack) );  /* Overwrite paging info  */
        ti_move_cursor ( cx, curr_screen.cy );

        ve_mode = prev_ve_mode;
        key = read_char ();

        cmd_ix = findb (commands, key, NUM_CMDS);

        /* If char is one of the cursor commands, home command, or jump cmd */
        /* execute that command, using resulting new curr_line as outer     */
        /* limit of the range.                                              */

        /* NEXT IS AN INCREDIBLY FRAGILE BIT OF "DAY 1" CODE THAT MAKES 2   */
        /* TROUBLESOME ASSUMPTIONS: (1) THE INDICATED COMMANDS REMAIN       */
        /* TOGETHER IN A GROUP, DELIMITED AS SHOWN, AND (2) NONE OF THE     */
        /* BLOCK MENU COMMANDS OVERLAPS THE COMMAND CHAR FOR ANY OF THE     */
        /* CURSOR COMMANDS BELOW. THIS *HAPPENS* TO WORK RIGHT NOW, BUT IT  */
        /* NEEDS TO BE CLEANED UP IN THE FUTURE.                            */

        if ((cmd_ix >= cursor_up_ix && cmd_ix <= extended_motion_ix ) ||
            (cmd_ix >= ext_pgup_ix  && cmd_ix <= ext_end_of_file_ix ) ||
            (cmd_ix >= half_up_ix   && cmd_ix <= half_down_ix       ) ||
            (cmd_ix == jump_ix)) 
        begin
            /* If this is not the start of the range, replace the visual    */
            /* delimiter with the character it overwrote since we are about */
            /* to redefine the end line with the command just entered       */

            if (curr_line != block_start) 
            begin
                ti_move_cursor ( cx, curr_screen.cy );
                writechar (end_char);
            end

            /* Execute the command the user has entered.                    */

            (*cmd_proc[cmd_ix])();

            /* Put a visual terminator onto the new current line, and update */
            /* end of block indicator                                        */

            end_char  = write_marker ( '>', cx, curr_screen.cy, curr_line );
            block_end = curr_line;

            /* The starting visual indicator may have disappeared off        */
            /* screen due to range definition commands, and now reappeared   */
            /* due to this last command. Check for this and redraw if so.    */

            if ( displayed(block_start, &cy) )
               dont_care = write_marker ( '>', cx, cy, block_start );

            ti_move_cursor ( cx, curr_screen.cy );

        end /* valid cursor command */

        /* Entered key is not a valid block definition command.  Exit block  */
        /* definition mode.                                                  */

        else 
        begin
            /* Key should be an exit command, operating on the now defined   */
            /* block. Look it up.                                            */

            key    = toupper(key);
            cmd_ix = findb ( exit_cmds, key, num_exit_cmds );

            if ( cmd_ix == -1 ) 
            begin
                /* The key is not a valid exit command. Check if the user    */
                /* typed ENTER, indicating they want to abandon this command */
                /* If so, let them. Else beep at them!                       */

                if ( key == ENTER ) 
                begin
                   delete_line_markers ( cx, block_start, start_char, block_end, end_char );
                   restore_cursor ();
                   return (FALSE);
                end
                else
                   writechar ( BEL );
            end

            else 
            begin
                /* We're in business - we have defined a range and the user  */
                /* has entered a valid exit command to operate on the range  */

                file_area->cmd    = key;
                file_area->cmd_ix = cmd_ix;

                delete_line_markers ( cx, block_start, start_char, block_end, end_char );
                break;
            end

        end

    end /* FOREVER (input char valid for defining range) */

    /* The range is now defined, but because the user can go up or down in   */
    /* his definition, the range start may not be the lowest (closest to the */
    /* top) limit of the range.  Hence, go through the file to determine the */
    /* correct first and last positions of the range.                        */

    file_area->num_lines = 1;

    if (block_start != block_end) 
    begin    
        /* Range is greater than one line */

        cnode = curr_file.head;
        i = 0;

        while (cnode != NULL && i < 2) 
        begin
            if (cnode == block_start || cnode == block_end) then
                block[i++] = cnode;

            if ( i > 0 && i < 2 )
               file_area->num_lines++;

            cnode = cnode->fwd_link;
        end

    end /* The block is now defined */

    file_area->first_line = block[0];
    file_area->last_line  = block[1];

    restore_cursor ();

    return ( TRUE );

end /* define_line_range */

/* .page */

BOOL define_column_range ( file_area, exit_cmds, num_exit_cmds )

/******************************************************************************
 *                                                                            *
 *  This function interacts with the user on screen to define the range of    *
 *  columns to be included in the block being defined. This range is returned *
 *  as part of the file_area argument, which is updated by this function.     *
 *  See earlier function define_line_range for the equivalent function for    *
 *  lines. The VE Block command uses only this define_line_range function.    *
 *  The VE Column command uses both this and the define_column_range          *
 *  functions.                                                                *
 *                                                                            *
 *  THIS FUNCTION ASSUMES THAT define_line_range HAS BEEN CALLED BEFOREHAND   *
 *  AND THE file_area->first_line IS VALID!                                   *
 *                                                                            *
 *  This function returns TRUE if a column range is successfully defined, or  *
 *  FALSE if not (typically because user decided to exit via ENTER).          *
 *                                                                            *
 ******************************************************************************/

struct t_file_area *file_area;     /* The file_area definition                */
char   exit_cmds [];               /* Set of commands that exit definition    */
int    num_exit_cmds;              /* The number of such commands             */

begin
    register int cmd_ix, i;        /* Command index, loop counter             */
    register char key, dont_care;  /* User command input, "dont care" char    */
    int prev_ve_mode;              /* Mode before column command called       */  

    char start_char,   end_char;   /* Characters overwritten with markers     */
    char start_char2,  end_char2;  /* Characters overwritten with markers     */
    int  start_column, end_column; /* Start and end of the column range       */
    int  left_cx,      right_cx;   /* x co-ord of left and right screen edges */
    BOOL last_line_displayed;      /* Is the last line of area on screen?     */
    int  last_line_cy;             /* y co-ord of last line of area           */

    char range_msg [42];           /* Life, the universe, and everything? :-) */

    update_curr_line ();

    curr_line = file_area->first_line;
    display_new_screen ( curr_screen.cx );

    last_line_displayed = FALSE;
    if ( file_area->first_line != file_area->last_line )
       last_line_displayed = displayed ( file_area->last_line, &last_line_cy );

    /* Mark the start of the column range */

    prev_ve_mode = last_ve_mode;
    start_column = end_column  = curr_screen.cx;

    start_char   = end_char    = write_marker ( 'V', curr_screen.cx, curr_screen.cy, curr_line );
    if ( last_line_displayed )
       start_char2 = end_char2 = write_marker ( 'V', curr_screen.cx, last_line_cy,   file_area->last_line );

    FOREVER 
    begin
        /* Provide feedback on the column range. Note that the feedback     */
        /* overwrites the last two selections of the Column menu.           */

        sprintf ( range_msg, "%s%3d, %s%3d%c", "[Start Col:", start_column,
                                                "End Col :",  end_column,    ']' );
        ti_move_cursor ( 49, 0 );
        writestr ( range_msg );
        restore_cursor ();

        /* Read in a new range definition command (or ENTER, to exit!) */

        ve_mode = prev_ve_mode;
        key     = read_char ();
        cmd_ix  = findb (commands, key, NUM_CMDS);

        /* If char is an intra line cursor movement commands, execute the   */
        /* command, using the resulting column as the new edge of the range */

        if ( cmd_ix == cursor_left_ix || cmd_ix == cursor_right_ix || 
             cmd_ix == ext_home_ix    || cmd_ix == ext_end_ix      ||
             cmd_ix == word_left_ix   || cmd_ix == word_right_ix   )
        begin
            /* If this is not the start of the range, replace the visual    */
            /* delimiter with the character it overwrote since we are about */
            /* to redefine the end column with the command just entered     */

            if ( curr_screen.cx != start_column ) 
            begin
                if ( last_line_displayed )
                begin
                   ti_move_cursor ( curr_screen.cx, last_line_cy );
                   writechar (end_char2);
                end
                ti_move_cursor ( curr_screen.cx, curr_screen.cy );
                writechar (end_char);
            end

            /* A few validity checks, and then execute the entered command  */

            if ( ( curr_screen.cx <= 0         && cmd_ix == cursor_left_ix  ) ||
                 ( curr_screen.cx >= columns-1 && cmd_ix == cursor_right_ix ) )
               writechar ( BEL );
            else
               (*cmd_proc[cmd_ix])();
               
            /* Put a visual terminator onto the new current line, and update */
            /* end of block indicator                                        */

            end_column = curr_screen.cx;

            end_char   = write_marker ( 'V', curr_screen.cx, curr_screen.cy, curr_line );
            if ( last_line_displayed )
               end_char2 = write_marker ( 'V', curr_screen.cx, last_line_cy, file_area->last_line );

            /* The starting visual indicator may have disappeared off        */
            /* screen due to range definition commands, and now reappeared   */
            /* due to this last command. Check for this and redraw if so.    */

            left_cx  = ti_get_left_cx();
            right_cx = ti_get_right_cx();

            if ( start_column >= left_cx && start_column <= right_cx )
            begin
               dont_care = write_marker ( 'V', start_column, curr_screen.cy, curr_line );
               if ( last_line_displayed )
                  dont_care = write_marker ( 'V', start_column, last_line_cy, file_area->last_line );
            end

        end /* valid cursor command */

        /* Entered key is not a valid block definition command.  Exit block  */
        /* definition mode.                                                  */

        else 
        begin
            /* Key should be an exit command, operating on the now defined   */
            /* block. Look it up.                                            */

            key    = toupper(key);
            cmd_ix = findb ( exit_cmds, key, num_exit_cmds );

            if ( cmd_ix == -1 ) 
            begin
                /* The key is not a valid exit command. Check if the user    */
                /* typed ENTER, indicating they want to abandon this command */
                /* If so, let them. Else beep at them!                       */

                if ( key == ENTER ) 
                begin
                   /* The user has pressed ENTER - they wish to abandon the  */
                   /* effort. Clear the markers and get outta dodge!         */

                   delete_column_markers ( curr_screen.cy, curr_line );
                   if ( last_line_displayed )
                      delete_column_markers ( last_line_cy, file_area->last_line );

                   return (FALSE);
                end
                else
                   writechar ( BEL );
            end

            else 
            begin
                /* We're in business - we have defined a range and the user  */
                /* has entered a valid exit command to operate on the range  */
                /* Set up the command and command index, and remove the      */
                /* markers.                                                  */

                file_area->cmd    = key;
                file_area->cmd_ix = cmd_ix;

                delete_column_markers ( curr_screen.cy, curr_line );
                if ( last_line_displayed )
                   delete_column_markers ( last_line_cy, file_area->last_line );

                break;
            end

        end

    end /* FOREVER (input char valid for defining range) */

    /* The range is now defined, but because the user can go left or right   */
    /* while defining, the start column may not be the first column. Hence   */
    /* check for this, and flip start and end around if this is the case.    */

    if ( start_column > end_column )
    begin
       left_cx      = end_column;
       end_column   = start_column;
       start_column = left_cx;
    end

    file_area->first_column = start_column;
    file_area->last_column  = end_column;

    /* Restore the original cursor position at the start of the definition  */

    restore_cursor ( (curr_screen.cx = start_column), curr_screen.cy );

    return ( TRUE );

end /* define_column_range */

