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

timehelp.cpp

/*-------------------------------------------------------------------------*
    TIMEHELP.CPP

    on-line help module 

    usage:  timehelp filename topic

            filename - name of help data file
            topic    - string corresponding to topic to lookup 

    by:  Stephen R. Schmitt
 *-------------------------------------------------------------------------*/

#include <assert.h>
#include <conio.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "input.h"

input Console;

#define HELP_WINDOW  2000
#define HELP_TITLE     80
#define HELP_TOPIC   8192
#define HELP_LINES    256

#define MIN_WINDOW      0
#define MIN_X          20
#define MIN_Y           5
#define MIN_DX         40
#define MIN_DY         10

#define MAX_WINDOW      1
#define MAX_X           1
#define MAX_Y           2
#define MAX_DX         78
#define MAX_DY         22

#define MAX_NAME       24

#define HELP_TEXT      CYAN*16 + BLACK
#define HELP_JUMP      CYAN*16 + YELLOW
#define HELP_BORDER    CYAN*16 + WHITE

#define LF_TOP1        218
#define RT_TOP1        191
#define HORZ1          196
#define VERT1          179
#define LF_BOT1        192
#define RT_BOT1        217

typedef struct
{
   char name[MAX_NAME];                         // ptr to topic string
   long offset;                                 // file offset of topic
}
JUMP;

class help
{
public:
    char *screen;                               // under the help window

    help();
    ~help();
    int get_help( char *, char * );

private:
    char *name;                                 // of help window
    int  change_title;                          // signal for new topic
    int  topic_count;                           // number of topics
    JUMP *topic_jump;                           // offsets of topic text
    long topic_start;                           // starting offset for text
    char *topic_title;                          // current topic
    char *topic_window;                         // window for text
    char *topic_text;                           // text of current topic
    char **topic_line;                          // pointers to lines
    FILE *hlp;                                  // handle of help text file
    int  size;                                  // window size flag
    int  x;                                     // left column
    int  y;                                     // top row
    int  dx;                                    // width of view
    int  dy;                                    // height of view
    int  last_line;                             // last line number
    int  top_line;                              // of displayed text
    int  left_col;                              // of displayed text
    int  scrn_x;                                // cursor screen location
    int  scrn_y;                                // cursor screen location
    int  cur_lin;                               // cursor file line
    int  cur_col;                               // cursor file column

    void paint_window();
    void paint_border();
    void update_cursor( int, int );
    void change_size();
    void line_up();
    void line_down();
    void page_up();
    void page_down();
    void goto_topic_begin();
    void goto_topic_end();
    void column_left();
    void column_right();
    void goto_line_begin();
    void goto_line_end();
    void move_window( int );
    void function( int );
    void enter();
    void tab_select();
    void character( int );
    void mouse_left( int, int );
    void process( int, int, int, int );
    void load_topic_text( FILE *, long );
    int  get_topic_from_file();
};

/*-------------------------------------------------------------------------*
 *  "help" constructor allocates memory
 *
 *  returns: nothing
 */
help::help()
{
    name         = "Help";
    screen       = (char *) calloc( HELP_WINDOW, 2 * sizeof( char ) );
    topic_title  = (char *) calloc( HELP_TITLE,  sizeof( char ) );
    topic_window = (char *) calloc( HELP_WINDOW, 2 * sizeof( char ) );
    topic_text   = (char *) calloc( HELP_TOPIC,  sizeof( char ) );
    topic_line   = (char **)calloc( HELP_LINES,  sizeof( char * ) );
    topic_jump   = (JUMP *) calloc( 1024, sizeof( JUMP ) );
}

/*-------------------------------------------------------------------------*
 *  "help" destructor frees memory
 *
 *  returns: nothing
 */
help::~help()
{
    free( screen );
    free( topic_title );
    free( topic_window );
    free( topic_text );
    free( topic_line );
    free( topic_jump );
}

/*-------------------------------------------------------------------------*
 *  "paint_window" writes text in the help window
 *
 *  returns: nothing
 */
void help::paint_window()
{
    char *p;
    int  row, i, j, jump;

    // setup topic buffer
    memset( topic_window, 0, 2 * HELP_WINDOW );
    for( i = 0; i < HELP_WINDOW; i++ )
        topic_window[2*i+1] = HELP_TEXT;

    p = topic_line[top_line];

    row = 0;
    i = 0;
    j = 0;
    jump = 0;

    while( row < dy && *p )
    {
        switch( *p )
        {
        case '\n':
            p++;
            i = 0;
            j = 0;
            row++;
            break;

        case '@':
            jump = 1;
            p++;
            break;

        case '$':
            jump = 0;
            p++;
            break;

        default:
            if( i >= left_col && j < dx )
            {
                topic_window[2*dx*row + 2*j] = *p;
                if( jump )
                    topic_window[2*dx*row + 2*j + 1] = HELP_JUMP;
                j++;
            }
            i++;
            p++;
            break;
        }
    }

    Console.mouse_cursor_off();
    puttext( x + 1, y + 1, x + dx, y + dy, topic_window );
    Console.mouse_cursor_on();
}

/*-------------------------------------------------------------------------*
 *  "paint_border" draws a border around a help window and rewrites
 *  the title
 *
 *  returns: nothing
 */
void help::paint_border()
{
    int i, n, left, top, right, bottom;

    Console.mouse_cursor_off();

    left   = x;
    top    = y;
    right  = x + dx + 1;
    bottom = y + dy + 1;

    for( i = 0; i < 80; i++ )
    {
        topic_window[ 2*i ] = HORZ1;
        topic_window[ 2*i + 1 ] = HELP_BORDER;
    }

    topic_window[ 0 ] = LF_TOP1;
    topic_window[ 2*( right - left ) ] = RT_TOP1;
    puttext( left, top, right, top, topic_window );

    topic_window[ 0 ] = LF_BOT1;
    topic_window[ 2*( right - left ) ] = RT_BOT1;
    puttext( left, bottom, right, bottom, topic_window );

    for( i = 0; i < 25; i++ )
        topic_window[ 2*i ] = VERT1;

    puttext( left, top + 1, left, bottom - 1, topic_window );
    puttext( right, top + 1, right, bottom - 1, topic_window );

    // update file name display
    n = strlen( name ) + 2;
    topic_window[0] = ' ';
    for( i = 0; i < n; i++ )
        topic_window[2*i + 2] = name[i];
    topic_window[2*i + 2] = ' ';

    // display new name
    puttext( x + ( dx / 2 ) - ( n / 2 ) + 1,     y,
             x + ( dx / 2 ) + ( n / 2 ) + n % 2, y, topic_window );

    Console.mouse_cursor_on();
}

/*-------------------------------------------------------------------------*
 *  "update_cursor" repositions the cursor and updates the line and 
 *  column of the cursor.
 *
 *  returns: nothing
 */
void help::update_cursor( int cx,               // column
                          int cy )              // row
{
    gotoxy( cx + x + 1, cy + y + 1 );
}

/*-------------------------------------------------------------------------*
 *  "change_size" changes the size of the help window.
 *
 *  returns: nothing
 */
void help::change_size()
{
    static int mx = MIN_X;
    static int my = MIN_Y;

    Console.mouse_cursor_off();
    puttext( 1, 1, 80, 25, screen );

    if( size == MIN_WINDOW )
    {
        size = MAX_WINDOW;
        mx   = x;
        my   = y;
        x    = MAX_X;
        y    = MAX_Y;
        dx   = MAX_DX;
        dy   = MAX_DY;
    }
    else
    {
        size = MIN_WINDOW;
        x    = mx;
        y    = my;
        dx   = MIN_DX;
        dy   = MIN_DY;
    }

    paint_border();
    paint_window();
    update_cursor( scrn_x, scrn_y );
    Console.mouse_cursor_on();
}

/*-------------------------------------------------------------------------*
 *  "line_up" moves cursor up by one line; scrolls up if needed
 *
 *  returns: nothing
 */
void help::line_up()
{
    scrn_y--;
    cur_lin--;

    // if at top of window, must scroll up
    if( scrn_y < 0 )
    {
        if( top_line > 0 )
        {
            scrn_y++;
            top_line--;
            paint_window();
        }
        else if( top_line == 0 )
        {
            scrn_y++;
            cur_lin++;
        }
    }
}

/*-------------------------------------------------------------------------*
 *  "line_down" moves cursor down one line
 *
 *  returns: nothing
 */
void help::line_down()
{
    if( top_line < last_line )
    {
        scrn_y++;
        cur_lin++;

        // if below bottom line, scroll up
        if( scrn_y == dy )
        {
            scrn_y = dy - 1;
            top_line++;
            paint_window();
        }
    }
}

/*-------------------------------------------------------------------------*
 *  "page_up" moves cursor up by dy - 2 lines; scrolls up if needed
 *
 *  returns: nothing
 */
void help::page_up()
{
    int i;
    int paint = 0;

    for( i = 0; i < dy - 2; i++ )
    {
        scrn_y--;
        cur_lin--;

        // if at top of window, must scroll up
        if( scrn_y < 0 )
        {
            if( top_line > 0 )
            {
                scrn_y++;
                top_line--;
                paint = 1;
            }
            else if( top_line == 0 )
            {
                scrn_y++;
                cur_lin++;
                break;
            }
        }
    }

    if( paint )
        paint_window();
}

/*-------------------------------------------------------------------------*
 *  "page_down" moves cursor down by dy - 2 lines
 *
 *  returns: nothing
 */
void help::page_down()
{
    int i;
    int paint = 0;

    for( i = 0; i < dy - 2; i++ )
    {
        if( top_line < last_line )
        {
            scrn_y++;
            cur_lin++;

            // if below bottom line, scroll up
            if( scrn_y == dy )
            {
                scrn_y = dy - 1;
                top_line++;
                paint = 1;
            }
        }
    }

    if( paint )
        paint_window();
}

/*-------------------------------------------------------------------------*
 *  "goto_topic_begin" moves cursor to beginning of topic
 *
 *  returns: nothing
 */
void help::goto_topic_begin()
{
    int paint = 0;

    while( cur_lin > 0 )
    {
        scrn_y--;
        cur_lin--;

        // if at top of window, must scroll up
        if( scrn_y < 0 )
        {
            if( top_line > 0 )
            {
                scrn_y++;
                top_line--;
                paint = 1;
            }
            else if( top_line == 0 )
            {
                scrn_y++;
                cur_lin++;
                break;
            }
        }
    }

    if( paint )
        paint_window();

    goto_line_begin();
}

/*-------------------------------------------------------------------------*
 *  "goto_topic_end" moves cursor to the end of the topic.
 *
 *  returns: nothing
 */
void help::goto_topic_end()
{
    int paint = 0;

    while( cur_lin < last_line )
    {
        if( top_line < last_line )
        {
            scrn_y++;
            cur_lin++;

            // if below bottom line, scroll up
            if( scrn_y == dy )
            {
                scrn_y = dy - 1;
                top_line++;
                paint = 1;
            }
        }
    }

    if( paint )
        paint_window();

    goto_line_end();
}

/*-------------------------------------------------------------------------*
 *  "column_left" moves the cursor one space left
 *
 *  returns: nothing
 */
void help::column_left()
{
    scrn_x--;
    cur_col--;

    if( scrn_x < 0 )
    {
        if( left_col > 0 )
        {
            left_col--;
            scrn_x++;
            paint_window();
        }
        else if( left_col == 0 )
        {
            scrn_x++;
            cur_col++;
        }
    }
}

/*-------------------------------------------------------------------------*
 *  "column_right" moves the cursor one space right
 *
 *  returns: nothing
 */
void help::column_right()
{
    if( left_col < 80 )
    {
        scrn_x++;
        cur_col++;

        if( scrn_x > dx - 1 )
        {
            left_col++;
            scrn_x--;
            paint_window();
        }
    }
}

/*-------------------------------------------------------------------------*
 *  "goto_line_begin" moves the cursor to the beginning of the line.
 *
 *  returns: nothing
 */
void help::goto_line_begin()
{
    int paint;

    paint = 0;

    while( cur_col > 0 )
    {
        scrn_x--;
        cur_col--;

        if( scrn_x < 0 )
        {
            if( left_col > 0 )
            {
                left_col--;
                scrn_x++;
                paint = 1;
            }
            else if( left_col == 0 )
            {
                scrn_x++;
                cur_col++;
                break;
            }
        }
    }

    if( paint )
        paint_window();
}

/*-------------------------------------------------------------------------*
 *  "goto_line_end" moves the cursor to the end of the line.
 *
 *  returns: nothing
 */
void help::goto_line_end()
{
    int len, paint;
    char *p;

    len = 0;
    paint = 0;
    p = topic_line[cur_lin];

    while( *p != '\n' && *p != '\0' )
    {
        if( *p != '@' && *p != '$' )
            len++;

        p++;
    }

    while( cur_col < len )
    {
        if( left_col < 80 )
        {
            scrn_x++;
            cur_col++;

            if( scrn_x > dx - 1 )
            {
                left_col++;
                scrn_x--;
                paint = 1;
            }
        }
    }

    while( cur_col > len )
    {
        scrn_x--;
        cur_col--;

        if( scrn_x < 0 )
        {
            if( left_col > 0 )
            {
                left_col--;
                scrn_x++;
                paint = 1;
            }
            else if( left_col == 0 )
            {
                scrn_x++;
                cur_col++;
                break;
            }
        }
    }

    if( paint )
        paint_window();
}

/*-------------------------------------------------------------------------*
 *  "move_window" moves the help window on the screen.
 *
 *  returns: nothing
 */
void help::move_window( int code )              // key code
{
    int move;

    if( size == MAX_WINDOW )
        return;

    move = 0;

    switch( code )
    {
    case ALT_UP_ARROW:
        if( y > 1 )
        {
            y--;
            move = 1;
        }
        break;

    case ALT_LF_ARROW:
        if( x > 1 )
        {
            x--;
            move = 1;
        }
        break;

    case ALT_RT_ARROW:
        if( x + dx + 1 < 80 )
        {
            x++;
            move = 1;
        }
        break;

    case ALT_DN_ARROW:
        if( y + dy + 1 < 25 )
        {
            y++;
            move = 1;
        }
        break;
    }

    if( move )
    {
        Console.mouse_cursor_off();
        puttext( 1, 1, 80, 25, screen );
        paint_border();
        paint_window();
        update_cursor( scrn_x, scrn_y );
        Console.mouse_cursor_on();
    }
}

/*-------------------------------------------------------------------------*
 *  "function" processes a function key message
 *
 *  returns: nothing
 */
void help::function( int code )
{
    switch( code )
    {
    case F1_KEY:
        memset( topic_title, 0, HELP_TITLE );
        change_title = 1;
        break;

    case F5_KEY:
        change_size();
        break;

    case HOME:
        goto_line_begin();
        break;

    case END:
        goto_line_end();
        break;

    case LF_ARROW:
        column_left();
        break;

    case RT_ARROW:
        column_right();
        break;

    case UP_ARROW:
        line_up();
        break;

    case DN_ARROW:
        line_down();
        break;

    case PAGE_UP:
        page_up();
        break;

    case PAGE_DN:
        page_down();
        break;

    case CTRL_PAGE_DN:
        goto_topic_end();
        break;

    case CTRL_PAGE_UP:
        goto_topic_begin();
        break;

    case ALT_UP_ARROW:
    case ALT_LF_ARROW:
    case ALT_RT_ARROW:
    case ALT_DN_ARROW:
        move_window( code );
        break;
    }
}

/*-------------------------------------------------------------------------*
 *  "enter" checks for a jump to a new topic
 *
 *  returns: nothing
 */
void help::enter()
{
    int i, key, jump;
    char *p, *beg, *end;

    i = 0;
    key = 0;
    jump = 0;
    p = topic_line[cur_lin];

    while( *p && *p != '\n' )
    {
        if( *p == '@' )
        {
            key = 1;
            p++;
            beg = p;
        }
        else if( *p == '$' )
        {
            if( jump )
            {
                end = p;
                break;
            }
            key = 0;
            p++;
        }

        if( i == cur_col )
            jump = key;

        p++;
        i++;
    }

    if( jump )
    {
        memset( topic_title, 0, HELP_TITLE );
        strncpy( topic_title, beg, (int)( end - beg ) );
        change_title = 1;
    }
}

/*-------------------------------------------------------------------------*
 *  "tab_select" moves the cursor to the next key word
 *
 *  returns: nothing
 */
void help::tab_select()
{
    int i, j, key, paint;
    char *p;

    i = 0;
    j = cur_lin;
    key = 0;
    p = topic_line[cur_lin];

    while( *p && *p != '\n' )                   // find cursor location
    {
        if( *p == '@' || *p == '$' )
            p++;

        if( i == cur_col )
            break;

        p++;
        i++;
    }

    while( *p )                                 // find next keyword
    {
        switch( *p )
        {
        case '@':
            key = 1;
            p++;
            break;

        case '$':
            p++;
            break;

        case '\n':
            i = 0;
            j++;
            p++;
            break;

        default:
            p++;
            i++;
            break;
        }

        if( key )
            break;
    }

    paint = 0;

    if( key )
    {
        while( j > cur_lin )
        {
            if( top_line < last_line )
            {
                scrn_y++;
                cur_lin++;

                // if below bottom line, scroll up
                if( scrn_y == dy )
                {
                    scrn_y = dy - 1;
                    top_line++;
                    paint = 1;
                }
            }
        }

        while( cur_col < i )
        {
            if( left_col < 80 )
            {
                scrn_x++;
                cur_col++;

                if( scrn_x > dx - 1 )
                {
                    left_col++;
                    scrn_x--;
                    paint = 1;
                }
            }
        }

        while( cur_col > i )
        {
            scrn_x--;
            cur_col--;

            if( scrn_x < 0 )
            {
                if( left_col > 0 )
                {
                    left_col--;
                    scrn_x++;
                    paint = 1;
                }
                else if( left_col == 0 )
                {
                    scrn_x++;
                    cur_col++;
                    break;
                }
            }
        }
    }

    if( paint )
        paint_window();
}

/*-------------------------------------------------------------------------*
 *  "character" processes a character message
 *
 *  returns: nothing
 */
void help::character( int code )
{
    switch( code )
    {
    case ENTER:
        enter();
        break;

    case TAB:
        tab_select();
        break;

    default:
        // process characters here
        break;
    }
}

/*-------------------------------------------------------------------------*
 *  "mouse_left" processes a mouse left key message
 *
 *  returns: nothing
 */
void help::mouse_left( int mx,                  // mouse column
                       int my )                 // mouse row
{
    if( my >= y && my < ( y + dy ) &&
        mx >= x && mx < ( x + dx ) )
    {
        scrn_x = mx - x;
        scrn_y = my - y;

        cur_col = left_col + scrn_x;
        cur_lin = top_line + scrn_y; 
    }

    enter();
}

/*-------------------------------------------------------------------------*
 *  "process" is the entry point for editing a file
 *
 *  returns: nothing
 */
void help::process( int msg,                    // message type
                    int mod,                    // keyboard modifier
                    int code,                   // message
                    int ampl )                  // message amplifier
{
    if( mod );

    switch( msg )
    {
    case MSG_PAINT:
        paint_border();
        paint_window();
        break;

    case MSG_FUNCTION:
        function( code );
        break;

    case MSG_MOUSE_LF:
        mouse_left( code, ampl );
        break;

    case MSG_CHARACTER:
        character( code );
        break;
    }
    update_cursor( scrn_x, scrn_y );
}

/*-------------------------------------------------------------------------*
 *  "load_topic_text" loads text for a topic from data file
 *
 *  returns: nothing
 */
void help::load_topic_text( FILE *hlp,          // text source file
                            long offset )       // offset of text start
{
    int i, j, n;
    char buffer[80];

    fseek( hlp, offset, SEEK_SET );

    memset( topic_text, 0, HELP_TOPIC );
    i = 0;

    while( fgets( buffer, 80, hlp ) )
    {
        if( buffer[0] == '#' )
            break;
        else
        {
            n = strlen( topic_text );
            topic_line[i] = &topic_text[n];

            j = 0;
            while( buffer[j] )
            {
                if( buffer[j] == '\t' )     
                {
                    // replace tabs with 8 spaces
                    strcat( topic_text, "        " );
                    n += 8;
                }
                else
                {
                    topic_text[n] = buffer[j];
                    n++;
                }
                j++;
            }
            i++;
        }
    }

    last_line = i - 1;
    top_line  = 0;
    left_col  = 0;
    scrn_x    = 0;
    scrn_y    = 0;
    cur_col   = 0;
    cur_lin   = 0;

}

/*-------------------------------------------------------------------------*
 *  "get_topic_from_file" opens the help file and searches for a topic.
 *
 *  returns: nothing
 */
int help::get_topic_from_file()
{
    int j;
    int msg, mod, code, ampl;

    for( j = 0; j < topic_count; j++ )
    {
        if( !strcmp( topic_jump[j].name, topic_title ) )
             break;
    }

    load_topic_text( hlp, topic_jump[j].offset + topic_start );

    // display a window full of text
    paint_border();
    paint_window();
    update_cursor( 0, 0 );

    change_title = 0;

    while( 1 )
    {
        msg = Console.message( &mod, &code, &ampl );
        if( msg == MSG_CHARACTER && code == ESCAPE )
            break;
        else
            process( msg, mod, code, ampl );

        if( change_title )
            break;
    }
       
    return change_title;
}

/*-------------------------------------------------------------------------*
 *  "get_help" initializes help viewer and calls function to open the 
 *  help file and search for a topic.
 *
 *  returns: 1 if successful in opening help file, else 0
 */
int help::get_help( char *file,                 // help file name
                    char *word )                // word to lookup
{
    int again;

    size = MIN_WINDOW;
    x    = MIN_X;
    y    = MIN_Y;
    dx   = MIN_DX;
    dy   = MIN_DY;

    hlp = fopen( file, "r" );
    if( !hlp )
        return 0;

    // get jump table from top of help file
    int i, j, k, n;
    char buffer[80];

    j = 0;
    n = 0;

    while( fgets( buffer, 80, hlp ) )
    {
        if( buffer[0] == '#' )
            break;

        n += strlen( buffer ) + 1;

        if( buffer[0] == '<' )
        {
            for( i = 0; i < 80; i++ )
            {
                if( buffer[i+1] == '>' )
                {
                    buffer[i] = 0;
                    assert( strlen( buffer ) < MAX_NAME );
                    strcpy( topic_jump[j].name, buffer );
                    break;
                }
                else
                    buffer[i] = buffer[i+1];
            }

            i += 2;
            k = i;
            for( ; i < 80; i++ )
            {
                if( !isdigit( buffer[i] ) )
                {
                    buffer[i] = 0;
                    topic_jump[j].offset = atol( &buffer[k] );
                    break;
                }
            }

            j++;
        }
    }

    topic_count = j - 1;
    topic_start = n;

    strcpy( topic_title, word );
    again = 1;

    while( again )
        again = get_topic_from_file();

    fclose( hlp );
    return 1;
}

/*-------------------------------------------------------------------------*
 *  "main" entry point for help program.
 *
 *  returns: nothing
 */
void main( int argc, char *argv[] )
{
    help h;
    int  cx, cy, rv = 1;

    if( argc == 3 )
    {
        cx = wherex();
        cy = wherey();
        gettext( 1, 1, 80, 25, h.screen );
        Console.set_mouse( MIN_X, MIN_Y );
        Console.mouse_cursor_on();
        _setcursortype( _NORMALCURSOR );

        rv = h.get_help( argv[1], argv[2] );

        Console.mouse_cursor_off();
        _setcursortype( _NOCURSOR );
        puttext( 1, 1, 80, 25, h.screen );
        gotoxy( cx, cy );
    }

    if( rv == 0 )
        exit( -1 );
}

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

Copyright © 2004, Stephen R. Schmitt