OpenTTD Source 20241224-master-gf74b0cf984
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#include "core/alloc_func.hpp"
20
21#include "safeguards.h"
22
29std::optional<std::string> GetClipboardContents();
30
31int _caret_timer;
32
33
40bool Textbuf::CanDelChar(bool backspace)
41{
42 return backspace ? this->caretpos != 0 : this->caretpos < this->bytes - 1;
43}
44
51bool Textbuf::DeleteChar(uint16_t keycode)
52{
53 bool word = (keycode & WKC_CTRL) != 0;
54
55 keycode &= ~WKC_SPECIAL_KEYS;
56 if (keycode != WKC_BACKSPACE && keycode != WKC_DELETE) return false;
57
58 bool backspace = keycode == WKC_BACKSPACE;
59
60 if (!CanDelChar(backspace)) return false;
61
62 char *s = this->buf + this->caretpos;
63 uint16_t len = 0;
64
65 if (word) {
66 /* Delete a complete word. */
67 if (backspace) {
68 /* Delete whitespace and word in front of the caret. */
69 len = this->caretpos - (uint16_t)this->char_iter->Prev(StringIterator::ITER_WORD);
70 s -= len;
71 } else {
72 /* Delete word and following whitespace following the caret. */
73 len = (uint16_t)this->char_iter->Next(StringIterator::ITER_WORD) - this->caretpos;
74 }
75 /* Update character count. */
76 for (const char *ss = s; ss < s + len; Utf8Consume(&ss)) {
77 this->chars--;
78 }
79 } else {
80 /* Delete a single character. */
81 if (backspace) {
82 /* Delete the last code point in front of the caret. */
83 s = Utf8PrevChar(s);
84 char32_t c;
85 len = (uint16_t)Utf8Decode(&c, s);
86 this->chars--;
87 } else {
88 /* Delete the complete character following the caret. */
89 len = (uint16_t)this->char_iter->Next(StringIterator::ITER_CHARACTER) - this->caretpos;
90 /* Update character count. */
91 for (const char *ss = s; ss < s + len; Utf8Consume(&ss)) {
92 this->chars--;
93 }
94 }
95 }
96
97 /* Move the remaining characters over the marker */
98 memmove(s, s + len, this->bytes - (s - this->buf) - len);
99 this->bytes -= len;
100
101 if (backspace) this->caretpos -= len;
102
103 this->UpdateStringIter();
104 this->UpdateWidth();
105 this->UpdateCaretPosition();
106 this->UpdateMarkedText();
107
108 return true;
109}
110
115{
116 memset(this->buf, 0, this->max_bytes);
117 this->bytes = this->chars = 1;
118 this->pixels = this->caretpos = this->caretxoffs = 0;
119 this->markpos = this->markend = this->markxoffs = this->marklength = 0;
120 this->UpdateStringIter();
121}
122
130bool Textbuf::InsertChar(char32_t key)
131{
132 uint16_t len = (uint16_t)Utf8CharLen(key);
133 if (this->bytes + len <= this->max_bytes && this->chars + 1 <= this->max_chars) {
134 memmove(this->buf + this->caretpos + len, this->buf + this->caretpos, this->bytes - this->caretpos);
135 Utf8Encode(this->buf + this->caretpos, key);
136 this->chars++;
137 this->bytes += len;
138 this->caretpos += len;
139
140 this->UpdateStringIter();
141 this->UpdateWidth();
142 this->UpdateCaretPosition();
143 this->UpdateMarkedText();
144 return true;
145 }
146 return false;
147}
148
160bool Textbuf::InsertString(const char *str, bool marked, const char *caret, const char *insert_location, const char *replacement_end)
161{
162 uint16_t insertpos = (marked && this->marklength != 0) ? this->markpos : this->caretpos;
163 if (insert_location != nullptr) {
164 insertpos = insert_location - this->buf;
165 if (insertpos > this->bytes) return false;
166
167 if (replacement_end != nullptr) {
168 this->DeleteText(insertpos, replacement_end - this->buf, str == nullptr);
169 }
170 } else {
171 if (marked) this->DiscardMarkedText(str == nullptr);
172 }
173
174 if (str == nullptr) return false;
175
176 uint16_t bytes = 0, chars = 0;
177 char32_t c;
178 for (const char *ptr = str; (c = Utf8Consume(&ptr)) != '\0';) {
179 if (!IsValidChar(c, this->afilter)) break;
180
181 uint8_t len = Utf8CharLen(c);
182 if (this->bytes + bytes + len > this->max_bytes) break;
183 if (this->chars + chars + 1 > this->max_chars) break;
184
185 bytes += len;
186 chars++;
187
188 /* Move caret if needed. */
189 if (ptr == caret) this->caretpos = insertpos + bytes;
190 }
191
192 if (bytes == 0) return false;
193
194 if (marked) {
195 this->markpos = insertpos;
196 this->markend = insertpos + bytes;
197 }
198
199 memmove(this->buf + insertpos + bytes, this->buf + insertpos, this->bytes - insertpos);
200 memcpy(this->buf + insertpos, str, bytes);
201
202 this->bytes += bytes;
203 this->chars += chars;
204 if (!marked && caret == nullptr) this->caretpos += bytes;
205 assert(this->bytes <= this->max_bytes);
206 assert(this->chars <= this->max_chars);
207 this->buf[this->bytes - 1] = '\0'; // terminating zero
208
209 this->UpdateStringIter();
210 this->UpdateWidth();
211 this->UpdateCaretPosition();
212 this->UpdateMarkedText();
213
214 return true;
215}
216
224{
225 auto contents = GetClipboardContents();
226 if (!contents.has_value()) return false;
227
228 return this->InsertString(contents.value().c_str(), false);
229}
230
237void Textbuf::DeleteText(uint16_t from, uint16_t to, bool update)
238{
239 uint c = 0;
240 const char *s = this->buf + from;
241 while (s < this->buf + to) {
242 Utf8Consume(&s);
243 c++;
244 }
245
246 /* Strip marked characters from buffer. */
247 memmove(this->buf + from, this->buf + to, this->bytes - to);
248 this->bytes -= to - from;
249 this->chars -= c;
250
251 auto fixup = [&](uint16_t &pos) {
252 if (pos <= from) return;
253 if (pos <= to) {
254 pos = from;
255 } else {
256 pos -= to - from;
257 }
258 };
259
260 /* Fixup caret if needed. */
261 fixup(this->caretpos);
262
263 /* Fixup marked text if needed. */
264 fixup(this->markpos);
265 fixup(this->markend);
266
267 if (update) {
268 this->UpdateStringIter();
269 this->UpdateCaretPosition();
270 this->UpdateMarkedText();
271 }
272}
273
279{
280 if (this->markend == 0) return;
281
282 this->DeleteText(this->markpos, this->markend, update);
283 this->markpos = this->markend = this->markxoffs = this->marklength = 0;
284}
285
290const char *Textbuf::GetText() const
291{
292 return this->buf;
293}
294
297{
298 this->char_iter->SetString(this->buf);
299 size_t pos = this->char_iter->SetCurPosition(this->caretpos);
300 this->caretpos = pos == StringIterator::END ? 0 : (uint16_t)pos;
301}
302
305{
306 this->pixels = GetStringBoundingBox(this->buf, FS_NORMAL).width;
307}
308
311{
312 const auto pos = GetCharPosInString(this->buf, this->buf + this->caretpos, FS_NORMAL);
313 this->caretxoffs = _current_text_dir == TD_LTR ? pos.left : pos.right;
314}
315
318{
319 if (this->markend != 0) {
320 const auto pos = GetCharPosInString(this->buf, this->buf + this->markpos, FS_NORMAL);
321 const auto end = GetCharPosInString(this->buf, this->buf + this->markend, FS_NORMAL);
322 this->markxoffs = std::min(pos.left, end.left);
323 this->marklength = std::max(pos.right, end.right) - this->markxoffs;
324 } else {
325 this->markxoffs = this->marklength = 0;
326 }
327}
328
335{
336 if (this->caretpos == 0) return false;
337
338 size_t pos = this->char_iter->Prev(what);
339 if (pos == StringIterator::END) return true;
340
341 this->caretpos = static_cast<uint16_t>(pos);
342 this->UpdateCaretPosition();
343 return true;
344}
345
352{
353 if (this->caretpos >= this->bytes - 1) return false;
354
355 size_t pos = this->char_iter->Next(what);
356 if (pos == StringIterator::END) return true;
357
358 this->caretpos = static_cast<uint16_t>(pos);
359 this->UpdateCaretPosition();
360 return true;
361}
362
369bool Textbuf::MovePos(uint16_t keycode)
370{
371 switch (keycode) {
372 case WKC_LEFT:
373 case WKC_CTRL | WKC_LEFT: {
374 auto move_type = (keycode & WKC_CTRL) != 0 ? StringIterator::ITER_WORD : StringIterator::ITER_CHARACTER;
375 return (_current_text_dir == TD_LTR) ? this->MovePrev(move_type) : this->MoveNext(move_type);
376 }
377
378 case WKC_RIGHT:
379 case WKC_CTRL | WKC_RIGHT: {
380 auto move_type = (keycode & WKC_CTRL) != 0 ? StringIterator::ITER_WORD : StringIterator::ITER_CHARACTER;
381 return (_current_text_dir == TD_LTR) ? this->MoveNext(move_type) : this->MovePrev(move_type);
382 }
383
384 case WKC_HOME:
385 this->caretpos = 0;
386 this->char_iter->SetCurPosition(this->caretpos);
387 this->UpdateCaretPosition();
388 return true;
389
390 case WKC_END:
391 this->caretpos = this->bytes - 1;
392 this->char_iter->SetCurPosition(this->caretpos);
393 this->UpdateCaretPosition();
394 return true;
395
396 default:
397 break;
398 }
399
400 return false;
401}
402
409Textbuf::Textbuf(uint16_t max_bytes, uint16_t max_chars)
410 : buf(MallocT<char>(max_bytes)), char_iter(StringIterator::Create())
411{
412 assert(max_bytes != 0);
413 assert(max_chars != 0);
414
415 this->afilter = CS_ALPHANUMERAL;
416 this->max_bytes = max_bytes;
417 this->max_chars = max_chars == UINT16_MAX ? max_bytes : max_chars;
418 this->caret = true;
419 this->DeleteAll();
420}
421
422Textbuf::~Textbuf()
423{
424 free(this->buf);
425}
426
432{
433 this->Assign(GetString(string));
434}
435
440void Textbuf::Assign(const std::string_view text)
441{
442 size_t bytes = std::min<size_t>(this->max_bytes - 1, text.size());
443 memcpy(this->buf, text.data(), bytes);
444 this->buf[bytes] = '\0';
445
446 StrMakeValidInPlace(this->buf, &this->buf[bytes], SVS_NONE);
447
448 /* Make sure the name isn't too long for the text buffer in the number of
449 * characters (not bytes). max_chars also counts the '\0' characters. */
450 while (Utf8StringLength(this->buf) + 1 > this->max_chars) {
451 *Utf8PrevChar(this->buf + strlen(this->buf)) = '\0';
452 }
453
454 this->UpdateSize();
455}
456
457
464{
465 const char *buf = this->buf;
466
467 this->chars = this->bytes = 1; // terminating zero
468
469 char32_t c;
470 while ((c = Utf8Consume(&buf)) != '\0') {
471 this->bytes += Utf8CharLen(c);
472 this->chars++;
473 }
474 assert(this->bytes <= this->max_bytes);
475 assert(this->chars <= this->max_chars);
476
477 this->caretpos = this->bytes - 1;
478 this->UpdateStringIter();
479 this->UpdateWidth();
480 this->UpdateMarkedText();
481
482 this->UpdateCaretPosition();
483}
484
490{
491 /* caret changed? */
492 bool b = !!(_caret_timer & 0x20);
493
494 if (b != this->caret) {
495 this->caret = b;
496 return true;
497 }
498 return false;
499}
500
501HandleKeyPressResult Textbuf::HandleKeyPress(char32_t key, uint16_t keycode)
502{
503 bool edited = false;
504
505 switch (keycode) {
506 case WKC_ESC: return HKPR_CANCEL;
507
508 case WKC_RETURN: case WKC_NUM_ENTER: return HKPR_CONFIRM;
509
510 case (WKC_CTRL | 'V'):
511 case (WKC_SHIFT | WKC_INSERT):
512 edited = this->InsertClipboard();
513 break;
514
515 case (WKC_CTRL | 'U'):
516 this->DeleteAll();
517 edited = true;
518 break;
519
520 case WKC_BACKSPACE: case WKC_DELETE:
521 case WKC_CTRL | WKC_BACKSPACE: case WKC_CTRL | WKC_DELETE:
522 edited = this->DeleteChar(keycode);
523 break;
524
525 case WKC_LEFT: case WKC_RIGHT: case WKC_END: case WKC_HOME:
526 case WKC_CTRL | WKC_LEFT: case WKC_CTRL | WKC_RIGHT:
527 this->MovePos(keycode);
528 break;
529
530 default:
531 if (IsValidChar(key, this->afilter)) {
532 edited = this->InsertChar(key);
533 } else {
534 return HKPR_NOT_HANDLED;
535 }
536 break;
537 }
538
539 return edited ? HKPR_EDITING : HKPR_CURSOR;
540}
Functions related to the allocation of memory.
T * MallocT(size_t num_elements)
Simplified allocation function that allocates the specified number of elements of the given type.
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:851
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:209
A number of safeguards to prevent using unsafe methods.
Definition of base types and functions in a cross-platform compatible way.
void free(const void *ptr)
Version of the standard free that accepts const pointers.
Definition stdafx.h:334
bool IsValidChar(char32_t key, CharSetFilter afilter)
Only allow certain keys.
Definition string.cpp:396
void StrMakeValidInPlace(char *str, const char *last, StringValidationSettings settings)
Scans the string for invalid characters and replaces then with a question mark '?' (if not ignored).
Definition string.cpp:178
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:359
size_t Utf8Decode(char32_t *c, const char *s)
Decode and consume the next UTF-8 encoded character.
Definition string.cpp:419
size_t Utf8Encode(T buf, char32_t c)
Encode a unicode character and place it in the buffer.
Definition string.cpp:460
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
@ SVS_NONE
Allow nothing and replace nothing.
Definition string_type.h:45
std::string GetString(StringID string)
Resolve the given StringID into a std::string with all the associated DParam lookups and formatting.
Definition strings.cpp:333
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.
uint32_t StringID
Numeric value that represents a string, independent of the selected language.
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:463
bool MovePos(uint16_t keycode)
Handle text navigation with arrow keys left/right.
Definition textbuf.cpp:369
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:317
void UpdateWidth()
Update pixel width of the text.
Definition textbuf.cpp:304
bool DeleteChar(uint16_t keycode)
Delete a character from a textbuffer, either with 'Delete' or 'Backspace' The character is delete fro...
Definition textbuf.cpp:51
void DeleteAll()
Delete every character in the textbuffer.
Definition textbuf.cpp:114
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:160
bool MovePrev(StringIterator::IterType what)
Move to previous character position.
Definition textbuf.cpp:334
uint16_t max_chars
the maximum size of the buffer in characters (including terminating '\0')
void Assign(StringID string)
Render a string into the textbuffer.
Definition textbuf.cpp:431
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:237
bool MoveNext(StringIterator::IterType what)
Move to next character position.
Definition textbuf.cpp:351
bool InsertClipboard()
Insert a chunk of text from the clipboard onto the textbuffer.
Definition textbuf.cpp:223
uint16_t bytes
the current size of the string in bytes (including terminating '\0')
const char * GetText() const
Get the current text.
Definition textbuf.cpp:290
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:40
void DiscardMarkedText(bool update=true)
Discard any marked text.
Definition textbuf.cpp:278
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:130
void UpdateStringIter()
Update the character iter after the text has changed.
Definition textbuf.cpp:296
char *const buf
buffer in which text is saved
bool HandleCaret()
Handle the flashing of the caret.
Definition textbuf.cpp:489
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:409
void UpdateCaretPosition()
Update pixel position of the caret.
Definition textbuf.cpp:310
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.