Skip to content

Commit cb489d4

Browse files
committed
TInputLine: add Unicode support
This is great news!
1 parent ca83ead commit cb489d4

File tree

4 files changed

+106
-33
lines changed

4 files changed

+106
-33
lines changed

README.md

+2-4
Original file line numberDiff line numberDiff line change
@@ -255,8 +255,6 @@ KeyDownEvent {
255255
```
256256
So, in short: views designed without Unicode input in mind will continue to work exactly as they did before, and views which want to be Unicode-aware will have no issues in being so.
257257

258-
For the time being, none of the builtin views have been modified to support Unicode input.
259-
260258
## Displaying Unicode text
261259

262260
The original design of Turbo Vision uses 16 bits to represent a *screen cell*—8 bit for a character and 8 bit for [BIOS color attributes](https://en.wikipedia.org/wiki/BIOS_color_attributes).
@@ -380,7 +378,7 @@ Here is what it looks like:
380378
381379
Support for creating Unicode-aware views is in place, but most of the views that are part of the original Turbo Vision library have not been adapted to handle Unicode.
382380
383-
* At least `TFrame`, `THistoryViewer`, `TListViewer`, `TInputLine` and `TMenuBox` are able to display Unicode text properly.
381+
* At least `TFrame`, `THistoryViewer`, `TListViewer` and `TMenuBox` are able to display Unicode text properly.
384382
* Automatic shortcuts in `TMenuBox` won't work with Unicode text, as shortcuts are still compared against `event.keyDown.charScan.charCode`.
385-
* `TInputLine` can display but not process Unicode text, as it still believes that every byte is one column wide. This can lead to inconsistencies, because some non-ASCII characters can be represented in the active codepage. For instance, if using CP437, it is possible to type `ç` into a `TInputLine`, but it will be a narrow character instead of a UTF-8 sequence. If you manage to place UTF-8 text in a `TInputLine` (e.g. by selecting a file in a `TFileDialog`), the caret will appear to be out of sync as its position is measured assuming a single-byte encoding.
383+
* `TInputLine` can display and process Unicode text.
386384
* `TEditor` assumes a single-byte encoding both when handling input events and when displaying text. So it won't display UTF-8 but at least it has a consistent behaviour.

include/tvision/dialogs.h

+2
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,9 @@ class TInputLine : public TView
185185
Boolean canScroll( int delta );
186186
int mouseDelta( TEvent& event );
187187
int mousePos( TEvent& event );
188+
int displayedPos( int pos );
188189
void deleteSelect();
190+
void deleteCurrent();
189191
void adjustSelectBlock();
190192
void saveState();
191193
void restoreState();

include/tvision/ttext.h

+59
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,10 @@ class TText {
114114

115115
public:
116116

117+
static size_t next(TStringView text);
118+
static size_t prev(TStringView text, size_t index);
119+
static size_t wseek(TStringView text, int count);
120+
117121
#ifndef __BORLANDC__
118122
static void eat(TScreenCell *cell, size_t n, size_t &width, std::string_view src, size_t &bytes);
119123
static void next(std::string_view src, size_t &bytes, size_t &width);
@@ -122,6 +126,61 @@ class TText {
122126

123127
};
124128

129+
#ifdef __BORLANDC__
130+
131+
inline size_t TText::next(TStringView text)
132+
{
133+
return text.size() ? 1 : 0;
134+
}
135+
136+
inline size_t TText::prev(TStringView text, size_t index)
137+
{
138+
return index ? 1 : 0;
139+
}
140+
141+
inline size_t TText::wseek(TStringView text, int count)
142+
{
143+
return count > 0 ? count : 0;
144+
}
145+
146+
#else
147+
148+
inline size_t TText::next(TStringView text)
149+
{
150+
if (text.size()) {
151+
std::mbstate_t state {};
152+
int64_t len = std::mbrtowc(nullptr, text.data(), text.size(), &state);
153+
return len <= 1 ? 1 : len;
154+
}
155+
return 0;
156+
}
157+
158+
inline size_t TText::prev(TStringView text, size_t index)
159+
{
160+
if (index) {
161+
// Try reading backwards character by character, until a valid
162+
// character is found. This tolerates invalid characters.
163+
size_t lead = std::min<size_t>(index, 4);
164+
for (size_t i = 1; i <= lead; ++i) {
165+
std::mbstate_t state {};
166+
int64_t size = std::mbrtowc(nullptr, &text[index - i], i, &state);
167+
if (size > 0)
168+
return size == i ? i : 1;
169+
}
170+
return 1;
171+
}
172+
return 0;
173+
}
174+
175+
inline size_t TText::wseek(TStringView text, int count)
176+
{
177+
size_t index = 0, remainder = 0;
178+
wseek(text, index, remainder, count);
179+
return index;
180+
}
181+
182+
#endif
183+
125184
#ifndef __BORLANDC__
126185

127186
inline void TText::eat( TScreenCell *cell, size_t n, size_t &width,

source/tvision/tinputli.cpp

+43-29
Original file line numberDiff line numberDiff line change
@@ -111,15 +111,15 @@ void TInputLine::draw()
111111
{
112112
if( canScroll(-1) )
113113
b.moveChar( 0, leftArrow, getColor(4), 1 );
114-
l = selStart - firstPos;
115-
r = selEnd - firstPos;
114+
l = displayedPos(selStart) - firstPos;
115+
r = displayedPos(selEnd) - firstPos;
116116
l = max( 0, l );
117117
r = min( size.x - 2, r );
118118
if (l < r)
119119
b.moveChar( l+1, 0, getColor(3), r - l );
120120
}
121121
writeLine( 0, 0, size.x, size.y, b );
122-
setCursor( curPos-firstPos+1, 0);
122+
setCursor( displayedPos(curPos)-firstPos+1, 0);
123123
}
124124

125125
void TInputLine::getData( void *rec )
@@ -153,8 +153,14 @@ int TInputLine::mousePos( TEvent& event )
153153
mouse.x = max( mouse.x, 1 );
154154
int pos = mouse.x + firstPos - 1;
155155
pos = max( pos, 0 );
156-
pos = min( pos, strlen(data) );
157-
return pos;
156+
TStringView text = data;
157+
pos = min( pos, strwidth(text) );
158+
return TText::wseek(text, pos);
159+
}
160+
161+
int TInputLine::displayedPos( int pos )
162+
{
163+
return strwidth( TStringView(data, pos) );
158164
}
159165

160166
void TInputLine::deleteSelect()
@@ -166,6 +172,17 @@ void TInputLine::deleteSelect()
166172
}
167173
}
168174

175+
void TInputLine::deleteCurrent()
176+
{
177+
TStringView text = data;
178+
if( curPos < text.size() )
179+
{
180+
selStart = curPos;
181+
selEnd = curPos + TText::next(text.substr(curPos));
182+
deleteSelect();
183+
}
184+
}
185+
169186
void TInputLine::adjustSelectBlock()
170187
{
171188
if (curPos < anchor)
@@ -243,7 +260,7 @@ void TInputLine::handleEvent( TEvent& event )
243260
static char padKeys[] = {0x47,0x4b,0x4d,0x4f,0x73,0x74, 0};
244261
TView::handleEvent(event);
245262

246-
int delta, i;
263+
int delta, i, len, curWidth;
247264
if( (state & sfSelected) != 0 )
248265
switch( event.what )
249266
{
@@ -296,12 +313,10 @@ void TInputLine::handleEvent( TEvent& event )
296313
switch( event.keyDown.keyCode )
297314
{
298315
case kbLeft:
299-
if( curPos > 0 )
300-
curPos--;
316+
curPos -= TText::prev(TStringView(data), curPos);
301317
break;
302318
case kbRight:
303-
if( curPos < strlen(data) )
304-
curPos++;
319+
curPos += TText::next(TStringView(data+curPos));
305320
break;
306321
case kbHome:
307322
curPos = 0;
@@ -312,41 +327,39 @@ void TInputLine::handleEvent( TEvent& event )
312327
case kbBack:
313328
if( curPos > 0 )
314329
{
315-
strcpy( data+curPos-1, data+curPos );
316-
curPos--;
330+
TStringView text = data;
331+
int len = TText::prev(text, curPos);
332+
memmove( data+curPos-len, data+curPos, text.size()-curPos+1 );
333+
curPos -= len;
317334
checkValid(True);
318335
}
319336
break;
320337
case kbDel:
321338
if( selStart == selEnd )
322-
if( curPos < strlen(data) )
323-
{
324-
selStart = curPos;
325-
selEnd = curPos + 1;
326-
}
327-
deleteSelect();
339+
deleteCurrent();
340+
else
341+
deleteSelect();
328342
checkValid(True);
329343
break;
330344
case kbIns:
331345
setState(sfCursorIns, Boolean(!(state & sfCursorIns)));
332346
break;
333347
default:
334-
if( event.keyDown.charScan.charCode >= ' ' )
348+
if( (len = event.keyDown.textLength) )
335349
{
336350
deleteSelect();
337351
if( (state & sfCursorIns) != 0 )
338-
/* The following must be a signed comparison! */
339-
if( curPos < (int) strlen(data) )
340-
strcpy( data + curPos, data + curPos + 1 );
352+
deleteCurrent();
341353

342354
if( checkValid(True) )
343355
{
344-
if( strlen(data) < maxLen )
356+
if( strlen(data) + len <= maxLen )
345357
{
346358
if( firstPos > curPos )
347359
firstPos = curPos;
348-
memmove( data+curPos+1, data+curPos, strlen(data+curPos)+1 );
349-
data[curPos++] = event.keyDown.charScan.charCode;
360+
memmove( data+curPos+len, data+curPos, strlen(data+curPos)+1 );
361+
memcpy( data+curPos, event.keyDown.text, len );
362+
curPos += len;
350363
}
351364
checkValid(False);
352365
}
@@ -366,9 +379,10 @@ void TInputLine::handleEvent( TEvent& event )
366379
selStart = 0;
367380
selEnd = 0;
368381
}
369-
if( firstPos > curPos )
370-
firstPos = curPos;
371-
i = curPos - size.x + 2;
382+
curWidth = displayedPos(curPos);
383+
if( firstPos > curWidth )
384+
firstPos = curWidth;
385+
i = curWidth - size.x + 2;
372386
if( firstPos < i )
373387
firstPos = i;
374388
drawView();
@@ -384,7 +398,7 @@ void TInputLine::selectAll( Boolean enable )
384398
curPos = selEnd = strlen(data);
385399
else
386400
curPos = selEnd = 0;
387-
firstPos = max( 0, curPos-size.x+2 );
401+
firstPos = max( 0, displayedPos(curPos)-size.x+2 );
388402
drawView();
389403
}
390404

0 commit comments

Comments
 (0)