/*
 * TInputLine.cc
 *
 * Turbo Vision - Version 2.0
 *
 * Copyright (c) 1994 by Borland International
 * All Rights Reserved.
 *
 * Modified by Sergio Sigala <sergio@sigala.it>
 */

#define Uses_TGroup
#define Uses_TKeys
#define Uses_TInputLine
#define Uses_TDrawBuffer
#define Uses_TEvent
#define Uses_TValidator
#define Uses_opstream
#define Uses_ipstream
#include <tvision/tv.h>

#include <ctype.h>
#include <string.h>

const int CONTROL_Y = 25;

char hotKey( const char *s )
{
    char *p;

    if( (p = strchr( (char *) s, '~' )) != 0 )
        return toupper((uchar)(p[1]));
    else
        return 0;
}

#define cpInputLine "\x13\x13\x14\x15"

TInputLine::TInputLine( const TRect& bounds, int aMaxLen, TValidator *aValid ) :
    TView(bounds),
    data( new char[aMaxLen] ),
    maxLen( aMaxLen-1 ),
    curPos( 0 ),
    firstPos( 0 ),
    selStart( 0 ),
    selEnd( 0 ),
    validator( aValid ),
    anchor( -1 ),
#ifndef __UNPATCHED
    oldAnchor( -1 ),
#endif
    oldData( new char[aMaxLen] )
{
    state |= sfCursorVis;
    options |= ofSelectable | ofFirstClick;
    *data = EOS;
}

TInputLine::~TInputLine()
{
    delete data;
    delete oldData;
    destroy(validator);
}

Boolean TInputLine::canScroll( int delta )
{
    if( delta < 0 )
        return Boolean( firstPos > 0 );
    else
        if( delta > 0 )
            return Boolean( (int)strlen(data) - firstPos + 2 > size.x );
        else
            return False;
}

ushort TInputLine::dataSize()
{
    ushort dSize = 0;

    if (validator)
        dSize = validator->transfer(data, NULL, vtDataSize);
    if (dSize == 0)
        dSize = maxLen + 1;
    return dSize;
}

void TInputLine::draw()
{
    int l, r;
    TDrawBuffer b;

    uchar color = (state & sfFocused) ? getColor( 2 ) : getColor( 1 );

    b.moveChar( 0, ' ', color, size.x );
    char buf[256];
    strncpy( buf, data+firstPos, size.x - 2 );
    buf[size.x - 2 ] = EOS;
    b.moveStr( 1, buf, color );

    if( canScroll(1) )
        b.moveChar( size.x-1, rightArrow, getColor(4), 1 );
    if( (state & sfSelected) != 0 )
        {
        if( canScroll(-1) )
            b.moveChar( 0, leftArrow, getColor(4), 1 );
        l = selStart - firstPos;
        r = selEnd - firstPos;
        l = max( 0, l );
        r = min( size.x - 2, r );
        if (l <  r)
            b.moveChar( l+1, 0, getColor(3), r - l );
        }
    writeLine( 0, 0, size.x, size.y, b );
    setCursor( curPos-firstPos+1, 0);
}

void TInputLine::getData( void *rec )
{
    if ((validator == 0) || (validator->transfer(data, rec, vtGetData) == 0))
        memcpy( rec, data, dataSize() );
}

TPalette& TInputLine::getPalette() const
{
    static TPalette palette( cpInputLine, sizeof( cpInputLine )-1 );
    return palette;
}

int TInputLine::mouseDelta( TEvent& event )
{
    TPoint mouse = makeLocal( event.mouse.where );

    if( mouse.x <= 0 )
        return -1;
    else
        if( mouse.x >= size.x - 1 )
            return 1;
        else
            return 0;
}

int TInputLine::mousePos( TEvent& event )
{
    TPoint mouse = makeLocal( event.mouse.where );
    mouse.x = max( mouse.x, 1 );
    int pos = mouse.x + firstPos - 1;
    pos = max( pos, 0 );
    pos = min( pos, strlen(data) );
    return pos;
}

void  TInputLine::deleteSelect()
{
    if( selStart < selEnd )
        {
        strcpy( data+selStart, data+selEnd );
        curPos = selStart;
        }
}

void TInputLine::adjustSelectBlock()
{
#ifndef __UNPATCHED
    if(anchor < 0)
        selEnd = selStart = 0;
    else
#endif
    if (curPos < anchor)
        {
        selStart = curPos;
        selEnd =  anchor;
        }
    else
        {
        selStart = anchor;
        selEnd = curPos;
        }
}

void TInputLine::saveState()
{
    if (validator)
        {
        strcpy(oldData,data);
        oldCurPos = curPos;
        oldFirstPos = firstPos;
        oldSelStart = selStart;
        oldSelEnd = selEnd;
#ifndef __UNPATCHED
	oldAnchor = anchor;
#endif
        }
}

void TInputLine::restoreState()
{
    if (validator)
        {
        strcpy(data, oldData);
        curPos = oldCurPos;
        firstPos = oldFirstPos;
        selStart = oldSelStart;
        selEnd = oldSelEnd;
#ifndef __UNPATCHED
	anchor = oldAnchor;
#endif
        }
}

Boolean TInputLine::checkValid(Boolean noAutoFill)
{
    int oldLen;
    char *newData;

    if (validator)
        {
        oldLen = strlen(data);
        newData = new char[256];
        strcpy(newData, data);
        if (!validator->isValidInput(newData, noAutoFill))
            {
                restoreState();
                delete newData;
                return False;
            }
        else
            {
            if ((int)strlen(newData) > maxLen)
                newData[maxLen] = 0;
            strcpy(data,newData);
            if ((curPos >= oldLen) && ((int)strlen(data) > oldLen))
                curPos = strlen(data);
            delete newData;
            return True;
            }
        }
    else
        return True;
}


void TInputLine::handleEvent( TEvent& event )
{
#ifndef __UNPATCHED
    // Boolean extendBlock;
#else
    Boolean extendBlock;
#endif
    /* Home, Left Arrow, Right Arrow, End, Ctrl-Left Arrow, Ctrl-Right Arrow */
    static char padKeys[] = {0x47,0x4b,0x4d,0x4f,0x73,0x74, 0};
    TView::handleEvent(event);

    int delta, i;
    if( (state & sfSelected) != 0 )
        switch( event.what )
            {
            case evMouseDown:
                if( canScroll(delta = mouseDelta(event)) )
                    do  {
                        if( canScroll(delta) )
                            {
                            firstPos += delta;
                            drawView();
                            }
                        } while( mouseEvent( event, evMouseAuto ) );
                else if (event.mouse.eventFlags & meDoubleClick)
                    selectAll(True);
                else
                    {
                    anchor =  mousePos(event);
                    do  {
                        if( event.what == evMouseAuto)
                            {
                            delta = mouseDelta(event);
                            if (canScroll(delta))
                                firstPos += delta;
                            }
                        curPos = mousePos(event);
                        adjustSelectBlock();
                        drawView();
                        }
                        while (mouseEvent(event,evMouseMove | evMouseAuto));
                    }
                clearEvent(event);
                break;
            case evKeyDown:
                saveState();

		/* SS: save the value so it can be used by other objects */

		int oldKeyCode = event.keyDown.keyCode;
                event.keyDown.keyCode = ctrlToArrow(event.keyDown.keyCode);

		/* SS: scanCode must be non zero */

		if (event.keyDown.charScan.scanCode != 0 &&
		    strchr(padKeys, event.keyDown.charScan.scanCode ) &&
                    (event.keyDown.controlKeyState & kbShift) != 0
                  )
                    {
                    event.keyDown.charScan.charCode = 0;
#ifndef __UNPATCHED
                    if(anchor < 0)
                        anchor = curPos;
                }
                else
		    anchor = -1;
#else
                    if (curPos == selEnd)
                        anchor = selStart;
                    else
                        anchor = selEnd;
                    extendBlock = True;
                    }
                else
                    extendBlock = False;
#endif
                switch( event.keyDown.keyCode )
                    {
                    case kbLeft:
                        if( curPos > 0 )
                            curPos--;
                        break;
                    case kbRight:
                        if( curPos < (int)strlen(data) )
                            curPos++;
                        break;
                    case kbHome:
                        curPos =  0;
                        break;
                    case kbEnd:
                        curPos = strlen(data);
                        break;
                    case kbBack:
                        if( curPos > 0 )
                            {
                            strcpy( data+curPos-1, data+curPos );
                            curPos--;
                            if( firstPos > 0 )
                                firstPos--;
                            checkValid(True);
                            }
                        break;
                    case kbDel:
                        if( selStart == selEnd )
                            if( curPos < (int)strlen(data) )
                                {
                                selStart = curPos;
                                selEnd = curPos + 1;
                                }
                        deleteSelect();
                        checkValid(True);
                        break;
                    case kbIns:
                        setState(sfCursorIns, Boolean(!(state & sfCursorIns)));
                        break;
                    default:
                        if( event.keyDown.charScan.charCode >= ' ' )
                            {
                            deleteSelect();
                            if( (state & sfCursorIns) != 0 )
                                /* The following must be a signed comparison! */
                                if( curPos < (int) strlen(data) )
                                    strcpy( data + curPos, data + curPos + 1 );

                            if( checkValid(True) )
                                {
                                if( (int)strlen(data) < maxLen )
                                    {
                                    if( firstPos > curPos )
                                        firstPos = curPos;
                                    memmove( data+curPos+1, data+curPos, strlen(data+curPos)+1 );
                                    data[curPos++] = event.keyDown.charScan.charCode;
                                    }
                                checkValid(False);
                                }
                            }
                        else if( event.keyDown.charScan.charCode == CONTROL_Y)
                            {
                            *data = EOS;
                            curPos = 0;
                            }
                            else
			    {
				/* SS: restore the old value before exit */

				event.keyDown.keyCode = oldKeyCode;
				return;
			    }
                    }
#ifndef __UNPATCHED
		adjustSelectBlock();
#else
                if (extendBlock)
                    adjustSelectBlock();
                else
                    {
                    selStart = 0;
                    selEnd = 0;
                    }
#endif
                if( firstPos > curPos )
                    firstPos = curPos;
                i = curPos - size.x + 2;
                if( firstPos < i )
                    firstPos = i;
                drawView();
                clearEvent( event );
                break;
            }
}

void TInputLine::selectAll( Boolean enable )
{
    selStart = 0;
    if( enable )
        curPos = selEnd = strlen(data);
    else
        curPos = selEnd = 0;
    firstPos = max( 0, curPos-size.x+2 );
#ifndef __UNPATCHED
    anchor = 0;                   //<----- This sets anchor to avoid deselect
    drawView();                   //       on initial selection
#else
    drawView();
#endif
}

void TInputLine::setData( void *rec )
{
    if ((validator == 0) || (validator->transfer(data,rec,vtSetData)==0))
        {
        memcpy( data, rec, dataSize()-1 );
        data[dataSize()-1] = EOS;
        }
    selectAll( True );
}

void TInputLine::setState( ushort aState, Boolean enable )
{
    TView::setState( aState, enable );
    if( aState == sfSelected ||
        ( aState == sfActive && (state & sfSelected) != 0 )
      )
        selectAll( enable );
}

void TInputLine::setValidator( TValidator* aValid )
{
    if (validator!=0)
      destroy(validator);

    validator = aValid;
}

#if !defined(NO_STREAMABLE)

void TInputLine::write( opstream& os )
{
    TView::write( os );
    os << maxLen << curPos << firstPos
       << selStart << selEnd;
    os.writeString( data);
    os << validator;
}

void *TInputLine::read( ipstream& is )
{
    TView::read( is );
    is >> maxLen >> curPos >> firstPos
       >> selStart >> selEnd;
    data = new char[maxLen + 1];
    oldData = new char[maxLen + 1];
    is.readString(data, maxLen+1);
    state |= sfCursorVis;
//    is >> validator; /* XXX */
    is >> (void*&) validator; /* XXX */
#ifndef __UNPATCHED
    // options |= ofSelectable | ofFirstClick;
#else
    options |= ofSelectable | ofFirstClick;
#endif
    return this;
}

TStreamable *TInputLine::build()
{
    return new TInputLine( streamableInit );
}

TInputLine::TInputLine( StreamableInit ) : TView( streamableInit )
{
}

#endif

Boolean TInputLine::valid(ushort cmd)
{
    if (validator)
        {
        if (cmd == cmValid)
            return Boolean(validator->status == vsOk);
        else if (cmd != cmCancel)
            if (!validator->validate(data))
                {
#ifndef __UNPATCHED
                // owner->current = 0;
#else
                owner->current = 0;
#endif
                select();
                return False;
                }
        }
    return True;
}


syntax highlighted by Code2HTML, v. 0.9.1