#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;
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
:
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] =
' '
;
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;
}