The Perl and Raku Conference 2025: Greenville, South Carolina - June 27-29 Learn more

/*------------------------------------------------------------*/
/* filename - tinputli.cpp */
/* */
/* function(s) */
/* TInputLine member functions */
/*------------------------------------------------------------*/
/*
* Turbo Vision - Version 2.0
*
* Copyright (c) 1994 by Borland International
* All Rights Reserved.
*
*/
#define Uses_TGroup
#define Uses_TKeys
#define Uses_TInputLine
#define Uses_TDrawBuffer
#define Uses_TEvent
#define Uses_TValidator
#define Uses_opstream
#define Uses_ipstream
#define Uses_TText
#define Uses_TClipboard
#include <tvision/tv.h>
#if !defined( __CTYPE_H )
#include <ctype.h>
#endif // __CTYPE_H
#if !defined( __STRING_H )
#include <string.h>
#endif // __STRING_H
#if !defined( __DOS_H )
#include <dos.h>
#endif // __DOS_H
const int CONTROL_Y = 25;
char hotKey( const char *s ) noexcept
{
char *p;
if( (p = strchr( (char *) s, '~' )) != 0 )
return toupper((uchar) p[1]);
else
return 0;
}
static int prevWord(const char *s, int pos) noexcept
{
for (int i = pos - 1; i >= 1; --i)
{
if( s[i] != ' ' && s[i - 1] == ' ' )
return i;
}
return 0;
};
static int nextWord(TStringView s, int pos) noexcept
{
for (int i = pos; i < int(s.size()) - 1; ++i)
{
if( s[i] == ' ' && s[i + 1] != ' ' )
return i + 1;
}
return int(s.size());
};
#define cpInputLine "\x13\x13\x14\x15"
TInputLine::TInputLine( const TRect& bounds, int limit, TValidator *aValid, ushort limitMode ) noexcept :
TView(bounds),
maxLen ( (limitMode == ilMaxBytes) ? min(max(limit - 1, 0), INT_MAX - 1) : 255 ),
maxWidth( (limitMode == ilMaxWidth) ? limit : INT_MAX ),
maxChars( (limitMode == ilMaxChars) ? limit : INT_MAX ),
curPos( 0 ),
firstPos( 0 ),
selStart( 0 ),
selEnd( 0 ),
validator( aValid )
{
data = new char[maxLen + 1];
oldData = new char[maxLen + 1];
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( strwidth(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;
TColorAttr color = getColor( (state & sfFocused) ? 2 : 1 );
b.moveChar( 0, ' ', color, size.x );
if( size.x > 1 )
{
b.moveStr( 1, data, color, size.x - 1, firstPos);
}
if( canScroll(1) )
b.moveChar( size.x-1, rightArrow, getColor(4), 1 );
if( canScroll(-1) )
b.moveChar( 0, leftArrow, getColor(4), 1 );
if( (state & sfSelected) != 0 )
{
l = displayedPos(selStart) - firstPos;
r = displayedPos(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( displayedPos(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 );
TStringView text = data;
return TText::scroll(text, pos, False);
}
int TInputLine::displayedPos( int pos )
{
return strwidth( TStringView(data, pos) );
}
void TInputLine::deleteSelect()
{
if( selStart < selEnd )
{
int len = strlen(data);
memmove( data+selStart, data+selEnd, len-selEnd );
data[len-selEnd+selStart] = EOS;
curPos = selStart;
}
}
void TInputLine::deleteCurrent()
{
TStringView text = data;
if( curPos < (int) text.size() )
{
selStart = curPos;
selEnd = curPos + TText::next(text.substr(curPos));
deleteSelect();
}
}
void TInputLine::adjustSelectBlock()
{
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;
}
}
void TInputLine::restoreState()
{
if (validator)
{
strcpy(data, oldData);
curPos = oldCurPos;
firstPos = oldFirstPos;
selStart = oldSelStart;
selEnd = oldSelEnd;
}
}
Boolean TInputLine::checkValid(Boolean noAutoFill)
{
int oldLen, newLen;
char *newData = 0;
Boolean result = True;
if (validator)
{
newData = new char[maxLen + 1];
oldLen = strlen(data);
memcpy(newData, data, oldLen + 1);
if (!validator->isValidInput(newData, noAutoFill))
{
restoreState();
result = False;
}
else
{
newLen = strlen(newData);
if (newLen > maxLen)
{
newData[maxLen] = '\0';
newLen = maxLen;
}
memcpy(data, newData, newLen + 1);
if ((curPos >= oldLen) && (newLen > oldLen))
curPos = newLen;
}
}
delete[] newData;
return result;
}
void TInputLine::handleEvent( TEvent& event )
{
Boolean extendBlock;
/* Home, Left Arrow, Right Arrow, End, Ctrl-Left Arrow, Ctrl-Right Arrow */
static const char padKeys[] = {0x47,0x4b,0x4d,0x4f,0x73,0x74};
TView::handleEvent(event);
char keyText[ sizeof( event.keyDown.text )+1 ];
int delta, i, len, curWidth;
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();
event.keyDown.keyCode = ctrlToArrow(event.keyDown.keyCode);
if( memchr(padKeys, event.keyDown.charScan.scanCode, sizeof(padKeys)) != 0 &&
(event.keyDown.controlKeyState & kbShift) != 0
)
{
event.keyDown.charScan.charCode = 0;
if (curPos == selEnd)
anchor = selStart;
else if (selStart == selEnd)
anchor = curPos;
else
anchor = selEnd;
extendBlock = True;
}
else
extendBlock = False;
switch( event.keyDown.keyCode )
{
case kbLeft:
curPos -= TText::prev(data, curPos);
break;
case kbRight:
curPos += TText::next(data+curPos);
break;
case kbCtrlLeft:
curPos = prevWord(data, curPos);
break;
case kbCtrlRight:
curPos = nextWord(data, curPos);
break;
case kbHome:
curPos = 0;
break;
case kbEnd:
curPos = strlen(data);
break;
case kbBack:
if( selStart == selEnd )
{
selStart = curPos - TText::prev(data, curPos);
selEnd = curPos;
}
deleteSelect();
checkValid(True);
break;
case kbCtrlBack:
case kbAltBack:
if( selStart == selEnd )
{
selStart = prevWord(data, curPos);
selEnd = curPos;
}
deleteSelect();
checkValid(True);
break;
case kbDel:
if( selStart == selEnd )
deleteCurrent();
else
deleteSelect();
checkValid(True);
break;
case kbCtrlDel:
if( selStart == selEnd )
{
selStart = curPos;
selEnd = nextWord(data, curPos);
}
deleteSelect();
checkValid(True);
break;
case kbIns:
setState(sfCursorIns, Boolean(!(state & sfCursorIns)));
break;
default:
// The event text may contain null characters, but 'data' is null-terminated,
// so rely on strlen to measure the text length.
strnzcpy( keyText, event.keyDown.getText(), sizeof( keyText ) );
if( (len = strlen(keyText)) > 0 )
{
deleteSelect();
if( (state & sfCursorIns) != 0 )
deleteCurrent();
if( checkValid(True) )
{
if( strchr("\t\r\n", keyText[0]) != 0 )
keyText[0] = ' '; // Replace tabs and newlines into spaces.
TTextMetrics dataMts = TText::measure(data);
TTextMetrics keyMts = TText::measure(keyText);
if( strlen(data) + len <= (uint) maxLen &&
dataMts.width + keyMts.width <= (uint) maxWidth &&
dataMts.graphemeCount + keyMts.graphemeCount <= (uint) maxChars
)
{
if( firstPos > curPos )
firstPos = curPos;
memmove( data+curPos+len, data+curPos, strlen(data+curPos)+1 );
memcpy( data+curPos, keyText, len );
curPos += len;
}
checkValid(False);
}
}
else if( event.keyDown.charScan.charCode == CONTROL_Y )
{
*data = EOS;
curPos = 0;
}
else
return;
}
if (extendBlock)
adjustSelectBlock();
else
selStart = selEnd = 0;
curWidth = displayedPos(curPos);
if( firstPos > curWidth )
firstPos = curWidth;
i = curWidth - size.x + 2;
if( firstPos < i )
firstPos = i;
drawView();
clearEvent( event );
break;
case evCommand:
if( event.message.command == cmPaste )
{
TClipboard::requestText();
clearEvent( event );
}
else if( event.message.command == cmCut || event.message.command == cmCopy )
{
TStringView sel( data + selStart, selEnd - selStart );
TClipboard::setText(sel);
if( event.message.command == cmCut )
{
saveState();
deleteSelect();
checkValid(True);
selStart = selEnd = 0;
drawView();
}
clearEvent( event );
}
break;
}
if( canUpdateCommands() )
updateCommands();
}
}
void TInputLine::selectAll( Boolean enable, Boolean scroll )
{
selStart = 0;
if( enable )
curPos = selEnd = strlen(data);
else
curPos = selEnd = 0;
if( scroll )
firstPos = max( 0, displayedPos(curPos)-size.x+2 );
drawView();
if( canUpdateCommands() )
updateCommands();
}
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 )
{
Boolean updateBefore = canUpdateCommands();
TView::setState( aState, enable );
Boolean updateAfter = canUpdateCommands();
if( aState == sfSelected || (aState == sfActive && (state & sfSelected)) )
selectAll( enable, False );
if( updateBefore != updateAfter )
updateCommands();
}
void TInputLine::setValidator( TValidator* aValid )
{
if (validator!=0)
destroy(validator);
validator = aValid;
}
Boolean TInputLine::canUpdateCommands()
{
return Boolean( (~state & (sfActive | sfSelected)) == 0 );
}
void TInputLine::setCmdState( ushort command, Boolean enable )
{
TCommandSet s;
s += command;
if( enable && canUpdateCommands() )
enableCommands(s);
else
disableCommands(s);
}
void TInputLine::updateCommands()
{
setCmdState( cmCut, Boolean( selStart < selEnd ) );
setCmdState( cmCopy, Boolean( selStart < selEnd ) );
setCmdState( cmPaste, True );
}
#if !defined(NO_STREAMABLE)
void TInputLine::write( opstream& os )
{
TView::write( os );
os << maxLen << maxWidth << maxChars << curPos << firstPos
<< selStart << selEnd;
os.writeString( data);
os << validator;
}
void *TInputLine::read( ipstream& is )
{
TView::read( is );
is >> maxLen >> maxWidth >> maxChars >> curPos >> firstPos
>> selStart >> selEnd;
data = new char[maxLen + 1];
oldData = new char[maxLen + 1];
is.readString(data, maxLen+1);
state |= sfCursorVis;
is >> (void*&)validator;
return this;
}
TStreamable *TInputLine::build()
{
return new TInputLine( streamableInit );
}
TInputLine::TInputLine( StreamableInit ) noexcept : 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))
{
select();
return False;
}
}
return True;
}