OpenTTD Source 20250312-master-gcdcc6b491d
textbuf.cpp
Go to the documentation of this file.
1/*
2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
6 */
7
10#include "stdafx.h"
11
12#include "textbuf_type.h"
13#include "string_func.h"
14#include "strings_func.h"
15#include "gfx_type.h"
16#include "gfx_func.h"
17#include "gfx_layout.h"
18#include "window_func.h"
19
20#include "safeguards.h"
21
28std::optional<std::string> GetClipboardContents();
29
30int _caret_timer;
31
32
39bool Textbuf::CanDelChar(bool backspace)
40{
41 return backspace ? this->caretpos != 0 : this->caretpos < this->buf.size();
42}
43
50bool Textbuf::DeleteChar(uint16_t keycode)
51{
52 bool word = (keycode & WKC_CTRL) != 0;
53
54 keycode &= ~WKC_SPECIAL_KEYS;
55 if (keycode != WKC_BACKSPACE && keycode != WKC_DELETE) return false;
56
57 bool backspace = keycode == WKC_BACKSPACE;
58
59 if (!CanDelChar(backspace)) return false;
60
61 auto s = this->buf.begin() + this->caretpos;
62 uint16_t len = 0;
63
64 if (word) {
65 /* Delete a complete word. */
66 if (backspace) {
67 /* Delete whitespace and word in front of the caret. */
68 len = this->caretpos - (uint16_t)this->char_iter->Prev(StringIterator::ITER_WORD);
69 s -= len;
70 } else {
71 /* Delete word and following whitespace following the caret. */
72 len = (uint16_t)this->char_iter->Next(StringIterator::ITER_WORD) - this->caretpos;
73 }
74 /* Update character count. */
75 for (auto ss = s; ss < s + len; Utf8Consume(ss)) {
76 this->chars--;
77 }
78 } else {
79 /* Delete a single character. */
80 if (backspace) {
81 /* Delete the last code point in front of the caret. */
82 s = Utf8PrevChar(s);
83 char32_t c;
84 len = (uint16_t)Utf8Decode(&c, s);
85 this->chars--;
86 } else {
87 /* Delete the complete character following the caret. */
88 len = (uint16_t)this->char_iter->Next(StringIterator::ITER_CHARACTER) - this->caretpos;
89 /* Update character count. */
90 for (auto ss = s; ss < s + len; Utf8Consume(ss)) {
91 this->chars--;
92 }
93 }
94 }
95
96 /* Move the remaining characters over the marker */
97 this->buf.erase(s - this->buf.begin(), len);
98
99 if (backspace) this->caretpos -= len;
100
101 this->UpdateStringIter();
102 this->UpdateWidth();
103 this->UpdateCaretPosition();
104 this->UpdateMarkedText();
105
106 return true;
107}
108
113{
114 this->buf.clear();
115 this->chars = 1;
116 this->pixels = this->caretpos = this->caretxoffs = 0;
117 this->markpos = this->markend = this->markxoffs = this->marklength = 0;
118 this->UpdateStringIter();
119}
120
128bool Textbuf::InsertChar(char32_t key)
129{
130 uint16_t len = (uint16_t)Utf8CharLen(key);
131 if (this->buf.size() + len < this->max_bytes && this->chars + 1 <= this->max_chars) {
132 /* Make space in the string, then overwrite it with the Utf8 encoded character. */
133 auto pos = this->buf.begin() + this->caretpos;
134 pos = this->buf.insert(pos, len, '\0');
135 Utf8Encode(pos, key);
136 this->chars++;
137 this->caretpos += len;
138
139 this->UpdateStringIter();
140 this->UpdateWidth();
141 this->UpdateCaretPosition();
142 this->UpdateMarkedText();
143 return true;
144 }
145 return false;
146}
147
159bool Textbuf::InsertString(const char *str, bool marked, const char *caret, const char *insert_location, const char *replacement_end)
160{
161 uint16_t insertpos = (marked && this->marklength != 0) ? this->markpos : this->caretpos;
162 if (insert_location != nullptr) {
163 insertpos = insert_location - this->buf.data();
164 if (insertpos >= this->buf.size()) return false;
165
166 if (replacement_end != nullptr) {
167 this->DeleteText(insertpos, replacement_end - this->buf.data(), str == nullptr);
168 }
169 } else {
170 if (marked) this->DiscardMarkedText(str == nullptr);
171 }
172
173 if (str == nullptr) return false;
174
175 uint16_t bytes = 0, chars = 0;
176 char32_t c;
177 for (const char *ptr = str; (c = Utf8Consume(&ptr)) != '\0';) {
178 if (!IsValidChar(c, this->afilter)) break;
179
180 uint8_t len = Utf8CharLen(c);
181 if (this->buf.size() + bytes + len >= this->max_bytes) break;
182 if (this->chars + chars + 1 > this->max_chars) break;
183
184 bytes += len;
185 chars++;
186
187 /* Move caret if needed. */
188 if (ptr == caret) this->caretpos = insertpos + bytes;
189 }
190
191 if (bytes == 0) return false;
192
193 if (marked) {
194 this->markpos = insertpos;
195 this->markend = insertpos + bytes;
196 }
197
198 this->buf.insert(insertpos, str, bytes);
199
200 this->chars += chars;
201 if (!marked && caret == nullptr) this->caretpos += bytes;
202 assert(this->buf.size() < this->max_bytes);
203 assert(this->chars <= this->max_chars);
204
205 this->UpdateStringIter();
206 this->UpdateWidth();
207 this->UpdateCaretPosition();
208 this->UpdateMarkedText();
209
210 return true;
211}
212
220{
221 auto contents = GetClipboardContents();
222 if (!contents.has_value()) return false;
223
224 return this->InsertString(contents.value().c_str(), false);
225}
226
233void Textbuf::DeleteText(uint16_t from, uint16_t to, bool update)
234{
235 uint c = 0;
236 auto s = this->buf.begin() + from;
237 auto end = this->buf.begin() + to;
238 while (s < end) {
239 Utf8Consume(s);
240 c++;
241 }
242
243 /* Strip marked characters from buffer. */
244 this->buf.erase(from, to - from);
245 this->chars -= c;
246
247 auto fixup = [&](uint16_t &pos) {
248 if (pos <= from) return;
249 if (pos <= to) {
250 pos = from;
251 } else {
252 pos -= to - from;
253 }
254 };
255
256 /* Fixup caret if needed. */
257 fixup(this->caretpos);
258
259 /* Fixup marked text if needed. */
260 fixup(this->markpos);
261 fixup(this->markend);
262
263 if (update) {
264 this->UpdateStringIter();
265 this->UpdateCaretPosition();
266 this->UpdateMarkedText();
267 }
268}
269
275{
276 if (this->markend == 0) return;
277
278 this->DeleteText(this->markpos, this->markend, update);
279 this->markpos = this->markend = this->markxoffs = this->marklength = 0;
280}
281
286const char *Textbuf::GetText() const
287{
288 return this->buf.c_str();
289}
290
293{
294 this->char_iter->SetString(this->buf.c_str());
295 size_t pos = this->char_iter->SetCurPosition(this->caretpos);
296 this->caretpos = pos == StringIterator::END ? 0 : (uint16_t)pos;
297}
298
301{
302 this->pixels = GetStringBoundingBox(this->buf, FS_NORMAL).width;
303}
304
307{
308 const auto pos = GetCharPosInString(this->buf, &this->buf[this->caretpos], FS_NORMAL);
309 this->caretxoffs = _current_text_dir == TD_LTR ? pos.left : pos.right;
310}
311
314{
315 if (this->markend != 0) {
316 const auto pos = GetCharPosInString(this->buf, &this->buf[this->markpos], FS_NORMAL);
317 const auto end = GetCharPosInString(this->buf, &this->buf[this->markend], FS_NORMAL);
318 this->markxoffs = std::min(pos.left, end.left);
319 this->marklength = std::max(pos.right, end.right) - this->markxoffs;
320 } else {
321 this->markxoffs = this->marklength = 0;
322 }
323}
324
331{
332 if (this->caretpos == 0) return false;
333
334 size_t pos = this->char_iter->Prev(what);
335 if (pos == StringIterator::END) return true;
336
337 this->caretpos = static_cast<uint16_t>(pos);
338 this->UpdateCaretPosition();
339 return true;
340}
341
348{
349 if (this->caretpos >= this->buf.size()) return false;
350
351 size_t pos = this->char_iter->Next(what);
352 if (pos == StringIterator::END) return true;
353
354 this->caretpos = static_cast<uint16_t>(pos);
355 this->UpdateCaretPosition();
356 return true;
357}
358
365bool Textbuf::MovePos(uint16_t keycode)
366{
367 switch (keycode) {
368 case WKC_LEFT:
369 case WKC_CTRL | WKC_LEFT: {
370 auto move_type = (keycode & WKC_CTRL) != 0 ? StringIterator::ITER_WORD : StringIterator::ITER_CHARACTER;
371 return (_current_text_dir == TD_LTR) ? this->MovePrev(move_type) : this->MoveNext(move_type);
372 }
373
374 case WKC_RIGHT:
375 case WKC_CTRL | WKC_RIGHT: {
376 auto move_type = (keycode & WKC_CTRL) != 0 ? StringIterator::ITER_WORD : StringIterator::ITER_CHARACTER;
377 return (_current_text_dir == TD_LTR) ? this->MoveNext(move_type) : this->MovePrev(move_type);
378 }
379
380 case WKC_HOME:
381 this->caretpos = 0;
382 this->char_iter->SetCurPosition(this->caretpos);
383 this->UpdateCaretPosition();
384 return true;
385
386 case WKC_END:
387 this->caretpos = static_cast<uint16_t>(this->buf.size());
388 this->char_iter->SetCurPosition(this->caretpos);
389 this->UpdateCaretPosition();
390 return true;
391
392 default:
393 break;
394 }
395
396 return false;
397}
398
405Textbuf::Textbuf(uint16_t max_bytes, uint16_t max_chars)
406 : char_iter(StringIterator::Create())
407{
408 assert(max_bytes != 0);
409 assert(max_chars != 0);
410
411 this->afilter = CS_ALPHANUMERAL;
412 this->max_bytes = max_bytes;
413 this->max_chars = max_chars == UINT16_MAX ? max_bytes : max_chars;
414 this->caret = true;
415 this->DeleteAll();
416}
417
422void Textbuf::Assign(const std::string_view text)
423{
424 size_t bytes = std::min<size_t>(this->max_bytes - 1, text.size());
425 this->buf = StrMakeValid(text.substr(0, bytes));
426
427 /* Make sure the name isn't too long for the text buffer in the number of
428 * characters (not bytes). max_chars also counts the '\0' characters. */
429 auto iter = this->buf.begin();
430 for (size_t len = 1; len < this->max_chars && iter != this->buf.end(); ++len) Utf8Consume(iter);
431
432 if (iter != this->buf.end()) {
433 this->buf.erase(iter, this->buf.end());
434 }
435
436 this->UpdateSize();
437}
438
439
446{
447 this->chars = static_cast<uint16_t>(Utf8StringLength(this->buf.data()) + 1); // terminating zero
448 assert(this->buf.size() < this->max_bytes);
449 assert(this->chars <= this->max_chars);
450
451 this->caretpos = static_cast<uint16_t>(this->buf.size());
452 this->UpdateStringIter();
453 this->UpdateWidth();
454 this->UpdateMarkedText();
455
456 this->UpdateCaretPosition();
457}
458
464{
465 /* caret changed? */
466 bool b = !!(_caret_timer & 0x20);
467
468 if (b != this->caret) {
469 this->caret = b;
470 return true;
471 }
472 return false;
473}
474
475HandleKeyPressResult Textbuf::HandleKeyPress(char32_t key, uint16_t keycode)
476{
477 bool edited = false;
478
479 switch (keycode) {
480 case WKC_ESC: return HKPR_CANCEL;
481
482 case WKC_RETURN: case WKC_NUM_ENTER: return HKPR_CONFIRM;
483
484 case (WKC_CTRL | 'V'):
485 case (WKC_SHIFT | WKC_INSERT):
486 edited = this->InsertClipboard();
487 break;
488
489 case (WKC_CTRL | 'U'):
490 this->DeleteAll();
491 edited = true;
492 break;
493
494 case WKC_BACKSPACE: case WKC_DELETE:
495 case WKC_CTRL | WKC_BACKSPACE: case WKC_CTRL | WKC_DELETE:
496 edited = this->DeleteChar(keycode);
497 break;
498
499 case WKC_LEFT: case WKC_RIGHT: case WKC_END: case WKC_HOME:
500 case WKC_CTRL | WKC_LEFT: case WKC_CTRL | WKC_RIGHT:
501 this->MovePos(keycode);
502 break;
503
504 default:
505 if (IsValidChar(key, this->afilter)) {
506 edited = this->InsertChar(key);
507 } else {
508 return HKPR_NOT_HANDLED;
509 }
510 break;
511 }
512
513 return edited ? HKPR_EDITING : HKPR_CURSOR;
514}
Class for iterating over different kind of parts of a string.
Definition string_base.h:14
static const size_t END
Sentinel to indicate end-of-iteration.
Definition string_base.h:23
IterType
Type of the iterator.
Definition string_base.h:17
@ ITER_WORD
Iterate over words.
Definition string_base.h:19
@ ITER_CHARACTER
Iterate over characters (or more exactly grapheme clusters).
Definition string_base.h:18
Dimension GetStringBoundingBox(std::string_view str, FontSize start_fontsize)
Return the string dimension in pixels.
Definition gfx.cpp:852
Functions related to the gfx engine.
ParagraphLayouter::Position GetCharPosInString(std::string_view str, const char *ch, FontSize start_fontsize)
Get the leading corner of a character in a single-line string relative to the start of the string.
Functions related to laying out the texts.
Types related to the graphics and/or input devices.
@ FS_NORMAL
Index of the normal font in the font tables.
Definition gfx_type.h:243
A number of safeguards to prevent using unsafe methods.
Definition of base types and functions in a cross-platform compatible way.
static void StrMakeValid(T &dst, const char *str, const char *last, StringValidationSettings settings)
Copies the valid (UTF-8) characters from str up to last to the dst.
Definition string.cpp:125
bool IsValidChar(char32_t key, CharSetFilter afilter)
Only allow certain keys.
Definition string.cpp:414
size_t Utf8StringLength(const char *s)
Get the length of an UTF-8 encoded string in number of characters and thus not the number of bytes th...
Definition string.cpp:377
size_t Utf8Decode(char32_t *c, const char *s)
Decode and consume the next UTF-8 encoded character.
Definition string.cpp:437
size_t Utf8Encode(T buf, char32_t c)
Encode a unicode character and place it in the buffer.
Definition string.cpp:478
Functions related to low-level strings.
int8_t Utf8CharLen(char32_t c)
Return the length of a UTF-8 encoded character.
char * Utf8PrevChar(char *s)
Retrieve the previous UNICODE character in an UTF-8 encoded string.
@ CS_ALPHANUMERAL
Both numeric and alphabetic and spaces and stuff.
Definition string_type.h:25
TextDirection _current_text_dir
Text direction of the currently selected language.
Definition strings.cpp:56
Functions related to OTTD's strings.
@ TD_LTR
Text is written left-to-right by default.
uint16_t pixels
the current size of the string in pixels
uint16_t caretpos
the current position of the caret in the buffer, in bytes
void UpdateSize()
Update Textbuf type with its actual physical character and screenlength Get the count of characters i...
Definition textbuf.cpp:445
bool MovePos(uint16_t keycode)
Handle text navigation with arrow keys left/right.
Definition textbuf.cpp:365
uint16_t markxoffs
the start position of the marked area in pixels
void UpdateMarkedText()
Update pixel positions of the marked text area.
Definition textbuf.cpp:313
void UpdateWidth()
Update pixel width of the text.
Definition textbuf.cpp:300
bool DeleteChar(uint16_t keycode)
Delete a character from a textbuffer, either with 'Delete' or 'Backspace' The character is delete fro...
Definition textbuf.cpp:50
void DeleteAll()
Delete every character in the textbuffer.
Definition textbuf.cpp:112
uint16_t max_bytes
the maximum size of the buffer in bytes (including terminating '\0')
uint16_t chars
the current size of the string in characters (including terminating '\0')
bool InsertString(const char *str, bool marked, const char *caret=nullptr, const char *insert_location=nullptr, const char *replacement_end=nullptr)
Insert a string into the text buffer.
Definition textbuf.cpp:159
bool MovePrev(StringIterator::IterType what)
Move to previous character position.
Definition textbuf.cpp:330
uint16_t max_chars
the maximum size of the buffer in characters (including terminating '\0')
uint16_t markend
the end position of the marked area in the buffer, in bytes
void DeleteText(uint16_t from, uint16_t to, bool update)
Delete a part of the text.
Definition textbuf.cpp:233
bool MoveNext(StringIterator::IterType what)
Move to next character position.
Definition textbuf.cpp:347
bool InsertClipboard()
Insert a chunk of text from the clipboard onto the textbuffer.
Definition textbuf.cpp:219
void Assign(const std::string_view text)
Copy a string into the textbuffer.
Definition textbuf.cpp:422
const char * GetText() const
Get the current text.
Definition textbuf.cpp:286
uint16_t caretxoffs
the current position of the caret in pixels
uint16_t marklength
the length of the marked area in pixels
uint16_t markpos
the start position of the marked area in the buffer, in bytes
bool CanDelChar(bool backspace)
Checks if it is possible to delete a character.
Definition textbuf.cpp:39
void DiscardMarkedText(bool update=true)
Discard any marked text.
Definition textbuf.cpp:274
bool caret
is the caret ("_") visible or not
CharSetFilter afilter
Allowed characters.
bool InsertChar(char32_t key)
Insert a character to a textbuffer.
Definition textbuf.cpp:128
void UpdateStringIter()
Update the character iter after the text has changed.
Definition textbuf.cpp:292
bool HandleCaret()
Handle the flashing of the caret.
Definition textbuf.cpp:463
Textbuf(uint16_t max_bytes, uint16_t max_chars=UINT16_MAX)
Initialize the textbuffer by supplying it the buffer to write into and the maximum length of this buf...
Definition textbuf.cpp:405
std::string buf
buffer in which text is saved
void UpdateCaretPosition()
Update pixel position of the caret.
Definition textbuf.cpp:306
std::optional< std::string > GetClipboardContents()
Try to retrieve the current clipboard contents.
Definition unix.cpp:205
Stuff related to text buffers.
HandleKeyPressResult
Return values for Textbuf::HandleKeypress.
@ HKPR_NOT_HANDLED
Key does not affect editboxes.
@ HKPR_CANCEL
Escape key pressed.
@ HKPR_EDITING
Textbuf content changed.
@ HKPR_CONFIRM
Return or enter key pressed.
@ HKPR_CURSOR
Non-text change, e.g. cursor position.
std::optional< std::string > GetClipboardContents()
Try to retrieve the current clipboard contents.
Definition unix.cpp:205
Window functions not directly related to making/drawing windows.