| home | contents | previous | next page | send comment | send link | add bookmark |

Textview.cpp

/*---------------------------------------------------------------------------------*
 *  "textview.cpp"
 *
 *  Definitions for the textview class.  These provide for user input and
 *  the display of spreadsheet data.
 *
 *  Stephen R. Schmitt
 *
 *  January 1999
 */

#include <unistd.h>                                 // sleep()
#include "textview.h"
#include "scalc.h"

const int KEY_DELETE = 127;
const int KEY_TAB = 9;
const int OFFSET = 4;                               // start of cell edit line

char menu[] =
     "Tab-edit  Q-quit  S-save  L-load  C-clear  R-recalc A-auto";
char edit[] =
     "Enter-finish  Tab-cancel                                  ";

/*---------------------------------------------------------------------------------*
 *  "textview" constructor initializes curses facility
 *
 *  returns: nothing
 */
textview::textview()
{

    // initialize curses
    initscr();                                      // initialize curses
    cbreak();                                       // inhibit response to Ctrl-C
    noecho();                                       // stop echoing keyboard input
    nonl();                                         // no CR after LF
    intrflush( stdscr, FALSE );                     // make curses see true screen
    keypad( stdscr, TRUE );                         // enable keypad

    // initialize view
    CX = 0;
    CY = 0;
    grid();
}

/*---------------------------------------------------------------------------------*
 *  "~textview" destructor restores terminal from curses settings.
 *
 *  returns: nothing
 */
textview::~textview()
{
    endwin();                                       // restore terminal
    printf( "\n\n" );
}

/*---------------------------------------------------------------------------------*
 *  "run" operates view of spreadsheet
 *
 *  returns: nothing
 */
void textview::run()
{
    bool ok = true;
    int  key;

    // initialize the display
    update_cells();
    enter_cell();
    function_menu( NULL );

    while( ok )
    {
        key = getch();

        switch( key )
        {
        case KEY_UP:    move_up();             break;
        case KEY_DOWN:  move_down();           break;
        case KEY_RIGHT: move_right();          break;
        case KEY_LEFT:  move_left();           break;
        case KEY_TAB:   edit_cell_contents();  break;
        default:   ok = function_menu( key );  break;
        }
    }
}

/*---------------------------------------------------------------------------------*
 *  "grid" displays the grid indices on left and top of view
 *
 *  returns: nothing
 */
void textview::grid()
{
    int i;
    char j;
    char buffer[16];

    for( j = 0; j < CXmax; j++ )
    {
        sprintf( buffer, "|      %c       |", j + 'A' );
        put_string( Ypos( -1 ), Xpos( j ) - 1, buffer );
    }

    for( i = 0; i < CYmax; i++ )
    {
        sprintf( buffer, "%2d", i + 1 );
        put_string( Ypos( i ) - 1, 0, "--" );
        put_string( Ypos( i )    , 0, buffer );
        put_string( Ypos( i ) + 1, 0, "--" );
    }

    put_string( Ypos( CYmax ), 0, menu );
}

/*---------------------------------------------------------------------------------*
 *  "update_cells" writes the current value or text of each cell.
 *
 *  returns: nothing
 */
void textview::update_cells()
{
    int x, y;

    for( y = 0; y < CYmax; y++ )
    {
        for( x = 0; x < CXmax; x++ )
             show_cell( y, x );
    }
    refresh();
}

/*---------------------------------------------------------------------------------*
 *  "put_string" puts a string of text on the screen
 *
 *  returns: nothing
 */
void textview::put_string( int y,                   // row
                           int x,                   // col
                           char *str )              // to put on screen
{
    move( y, x );
    printw( "%s", str );
    move( LINES - 1, COLS - 1 );                    // 'hide' cursor
}

/*---------------------------------------------------------------------------------*
 *  "clear_to_eol" clears a row from col to the right edge of the screen
 *
 *  returns: nothing
 */
void textview::clear_to_eol( int y,                 // row
                             int x )                // col
{
    move( y, x );
    clrtoeol();
    move( LINES - 1, COLS - 1 );                    // 'hide' cursor
}


/*---------------------------------------------------------------------------------*
 *  "move_up" moves the selected cell up one line or wrap around.
 *
 *  returns: nothing
 */
void textview::move_up()
{
    leave_cell();

    CY--;
    if( CY < 0 )
        CY = CYmax - 1;

    enter_cell();
}

/*---------------------------------------------------------------------------------*
 *  "move_left" moves the selected cell left one column or wrap around.
 *
 *  returns: nothing
 */
void textview::move_left()
{
    leave_cell();

    CX--;
    if( CX < 0 )
        CX = CXmax - 1;

    enter_cell();
}

/*---------------------------------------------------------------------------------*
 *  "move_down" moves the selected cell down one line or wrap around.
 *
 *  returns: nothing
 */
void textview::move_down()
{
    leave_cell();

    CY++;
    if( CY >= CYmax )
        CY = 0;

    enter_cell();
}

/*---------------------------------------------------------------------------------*
 *  "move_right" moves the selected cell right one column or wrap around.
 *
 *  returns: nothing
 */
void textview::move_right()
{
    leave_cell();

    CX++;
    if( CX >= CXmax )
        CX = 0;

    enter_cell();
}

/*---------------------------------------------------------------------------------*
 *  "enter_cell" updates the screen for the just selected cell.
 *
 *  returns: nothing
 */
void textview::enter_cell()
{
    // draw box to show selected cell
    put_string( Ypos( CY ) - 1, Xpos( CX ) - 1, "+--------------+" );
    put_string( Ypos( CY ),     Xpos( CX ) - 1, "                " );
    put_string( Ypos( CY ) + 1, Xpos( CX ) - 1, "+--------------+" );

    // rewrite cell value and update cell contents on edit line
    show_cell_contents( CY, CX );
    show_cell( CY, CX );
}

/*---------------------------------------------------------------------------------*
 *  "leave_cell" updates the screen for the just de-selected cell.
 *
 *  returns: nothing
 */
void textview::leave_cell()
{
    put_string( Ypos( CY ) - 1, Xpos( CX ) - 1, "                " );
    put_string( Ypos( CY ),     Xpos( CX ) - 1, "                " );
    put_string( Ypos( CY ) + 1, Xpos( CX ) - 1, "                " );

    //rewrite cell value since it was blotted out
    show_cell( CY, CX );
}

/*---------------------------------------------------------------------------------*
 *  "show_cell" creates a string for a cell to display corresponding to
 *  the cells status.
 *
 *  returns: nothing
 */
void textview::show_cell( int y,                    // row
                          int x )                   // col
{
    double val;                                     // value of cell expression
    char   buf[MAX_CONTENTS];                       // work space

    // blank old contents
    memset( buf, ' ', MAX_CONTENTS );
    buf[CWmax - 1] = 0;
    put_string( Ypos(y), Xpos(x), buf );

    switch( ss->get_cell_status( y, x ) )
    {
    case TEXTCELL:
        ss->get_cell_contents( y, x, buf );
        break;

    case EXPRESSION:
        val = ss->get_cell_value( y, x );
        sprintf( buf, "%*g", CWmax - 1, val );
        break;

    case SYNTAX:
        strcpy( buf, " * Error * " );
        break;

    default:
        buf[0] = 0;
        break;
    }

    // put new contents
    buf[CWmax - 1] = 0;
    put_string( Ypos(y), Xpos(x), buf );
}

/*---------------------------------------------------------------------------------*
 *  "show_cell_contents" displays the contents of the selected cell on
 *  the edit line.
 *
 *  returns: nothing
 */
void textview::show_cell_contents( int y,           // row
                                   int x )          // col
{
    char buffer[MAX_CONTENTS];                      // work space

    // clear existing text
    clear_to_eol( 0, 0 );

    // update cell name field and data entry field
    // columns lettered  A ...  rows numbered  1 ...
    sprintf( buffer, "%c%c :", char( 'A' + CX ), char( '1' + CY ) );
    put_string( 0, 0, buffer );

    // add cell contents
    ss->get_cell_contents( CY, CX, buffer );
    put_string( 0, OFFSET, buffer );

    // add message line
    cell_message( y, x );
}

/*---------------------------------------------------------------------------------*
 *  "cell_message" creates a message to display under the edit line
 *  showing details of the cell's status.
 *
 *  returns: nothing
 */
void textview::cell_message( int y,                 // row
                             int x )                // column
{
    int n;                                          // error location
    char buf[MAX_CONTENTS];                         // work space

    // blank old contents
    clear_to_eol( 1, 0 );

    switch( ss->get_cell_status( y, x ) )
    {
    case TEXTCELL:
        sprintf( buf, "text" );
        break;

    case EXPRESSION:
        sprintf( buf, "expression" );
        break;

    case SYNTAX:
        n = ss->get_cell_errloc( y, x );
        if( 0 < n )
        {
            // point to location of syntax error
            memset( buf, ' ', MAX_CONTENTS );
            buf[OFFSET + n - 1] = '^';
            buf[OFFSET + n] = 0;
        }
        break;

    default:
        sprintf( buf, "empty" );
        break;
    }

    // write message
    put_string( 1, 0, buf );
}

/*---------------------------------------------------------------------------------*
 *  "edit_cell_contents" processes text input which changes the active cell
 *  contents.
 *
 *  returns: nothing
 */
void textview::edit_cell_contents()
{
    int n, ch;
    bool done = false;
    char buffer[MAX_CONTENTS];

    // setup for input
    put_string( Ypos( CYmax ), 0, edit );
    show_cell_contents( CY, CX );
    memset( buffer, 0, MAX_CONTENTS );
    ss->get_cell_contents( CY, CX, buffer );
    n = strlen( buffer );
    move( 0, OFFSET + n );

    while( !done )
    {
        ch = getch();

        switch( ch )
        {
        case KEY_TAB:  // restore display to original
            ss->get_cell_contents( CY, CX, buffer );
            put_string( 0, OFFSET, buffer );
        case '\r':     // Enter or Return
            done = true;
            break;

        case '\b':     // backspace (not on all terminals)
        case KEY_DELETE:
            if( 0 < n ) n--;
                memmove( &buffer[n], &buffer[n+1], MAX_CONTENTS - n - 1 );
            break;

        case KEY_RIGHT:
            if( int( strlen( buffer ) ) > n ) n++;
            break;

        case KEY_LEFT:
            if( 0 < n ) n--;
            break;

        default:
            // add char when buffer not full and char is text
            if( n < MAX_CONTENTS - 1 && isprint( ch ) )
            {
                memmove( &buffer[n+1], &buffer[n], MAX_CONTENTS - n - 1 );
                buffer[n] = char( ch );
                n++;
                buffer[MAX_CONTENTS - 1] = 0; // truncate at limit
            }
            break;
        }

        clear_to_eol( 0, OFFSET );
        put_string( 0, OFFSET, buffer );
        move( 0, OFFSET + n );
    }

    // update data entry line
    ss->put_cell_contents( CY, CX, buffer );
    ss->evaluate( CY, CX );
    if( auto_calc )
    {
        ss->recalculate();
        update_cells();
    }

    show_cell_contents( CY, CX );
    show_cell( CY, CX );
    put_string( Ypos( CYmax ), 0, menu );
}

/*---------------------------------------------------------------------------------*
 *  "function_menu" displays a list of function on the message line.
 *
 *  returns: false if Quit selected, else true
 */
bool textview::function_menu( int ch )              // user input from keyboard
{
    bool run = true;                                // continue program

    switch( tolower( char( ch ) ) )
    {
    case 'q':
        run = fn_quit();                            // program terminates on false
        break;

    case 's':
        fn_save();
        break;

    case 'l':
        fn_load();
        break;

    case 'c':
        fn_clear();
        break;

    case 'r':
        fn_recalc();
        break;

    case 'a':
        // toggle mode
        auto_calc = auto_calc ? false : true;
        break;
    }

    clear_to_eol( 2, 0 );
    if( auto_calc )
        put_string( 2, 0, "AutoCalc On " );
    else
        put_string( 2, 0, "AutoCalc Off" );

    return run;
}

/*---------------------------------------------------------------------------------*
 *  "fn_quit" asks user to confirm quit command.
 *
 *  returns: false if Quit confirmed, else true
 */
bool textview::fn_quit()
{
    clear_to_eol( 2, 0 );
    put_string( 2, 0, "Quit? [y|N]" );
    if( tolower( getch() ) == 'y' )
        return false;
    else
        return true;
}

/*---------------------------------------------------------------------------------*
 *  "set_filename" sets the name of the file to be saved or loaded.
 *
 *  returns: false to abort operation, else true
 */
bool textview::set_filename()
{
    int n, ch;                                      // index, char
    bool ok = true;                                 // do operation
    bool done = false;                              // continue editing
    char buffer[MAX_CONTENTS];                      // work space

    // setup filename entry line and turn on cursor
    put_string( Ypos( CYmax ), 0, edit );
    clear_to_eol( 2, 0 );
    put_string( 2, 0, "File? " );
    memset( buffer, 0, MAX_CONTENTS );
    ss->get_filename( buffer );
    put_string( 2, 6, buffer );
    n = strlen( buffer );
    move( 2, 6 + n );

    while( !done )
    {
        ch = getch();

        switch( ch )
        {
        case KEY_TAB:                               // abort operation
            ok = false;
        case '\r':                                  // Enter
            done = true;
            break;

        case '\b':                                  // Backspace
        case KEY_DELETE:
            if( 0 < n ) n--;
            memmove( &buffer[n], &buffer[n+1], MAX_CONTENTS - n - 1 );
            break;

        case KEY_RIGHT:                             // -->
            if( int( strlen( buffer ) ) > n ) n++;
            break;

        case KEY_LEFT:                              // <--
            if( 0 < n ) n--;
            break;

        default:
            // add character if buffer not full and is text character
            if( n < MAX_CONTENTS - 1 && isprint( ch ) )
            {
                memmove( &buffer[n+1], &buffer[n], MAX_CONTENTS - n - 1 );
                buffer[n] = char( ch );
                n++;
                buffer[MAX_CONTENTS - 1] = 0;       // truncate at limit
            }
            break;
        }

        clear_to_eol( 2, 6 );
        put_string( 2, 6, buffer );
        move( 2, 6 + n );
    }

    if( ok )
        ss->put_filename( buffer );

    put_string( Ypos( CYmax ), 0, menu );

    return ok;
}

/*---------------------------------------------------------------------------------*
 *  "fn_save" saves the current spread sheet.
 *
 *  returns: nothing
 */
void textview::fn_save()
{
    if( !set_filename() )
        return;

    clear_to_eol( 2, 0 );
    put_string( 2, 0, "Saving spreadsheet . . ." );

    // let user see operation
    refresh();
    sleep( 1 );

    if( !ss->save() )
    {
        clear_to_eol( 2, 0 );
        put_string( 2, 0, "Save failed" );
        refresh();
        sleep( 2 );
    }
}

/*---------------------------------------------------------------------------------*
 *  "fn_load" loads a spread sheet from disk.
 *
 *  returns: nothing
 */
void textview::fn_load()
{
    if( !set_filename() )
        return;

    clear_to_eol( 2, 0 );
    put_string( 2, 0, "Loading spreadsheet . . ." );

    // let user see operation
    refresh();
    sleep( 1 );

    // perform operation
    if( ss->load() )
    {
        ss->recalculate();
        update_cells();
    }
    else
    {
        clear_to_eol( 2, 0 );
        put_string( 2, 0, "Load failed" );
        refresh();
        sleep( 2 );
    }

    // update selected cell
    show_cell_contents( CY, CX );
}

/*---------------------------------------------------------------------------------*
 *  "fn_clear" clears the current spread sheet.
 *
 *  returns: nothing
 */
void textview::fn_clear()
{
    clear_to_eol( 2, 0 );
    put_string( 2, 0, "Clear this worksheet? [y|N]" );
    if( tolower( getch() ) == 'y' )
    {
        ss->clear_sheet();
        update_cells();
    }
    enter_cell();
}

/*---------------------------------------------------------------------------------*
 *  "fn_recalc" recalculates all cells in the spread sheet.
 *
 *  returns: nothing
 */
void textview::fn_recalc()
{
    clear_to_eol( 2, 0 );
    put_string( 2, 0, "Recalculating . . ." );

    // let user see operation
    refresh();
    sleep( 1 );

    ss->recalculate();
    update_cells();
}

| home | contents | previous | next page | send comment | send link | add bookmark |

Copyright © 2004, Stephen R. Schmitt