OpenTTD Source 20250612-master-gb012d9e3dc
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 "core/utf8.hpp"
16#include "gfx_type.h"
17#include "gfx_func.h"
18#include "gfx_layout.h"
19#include "window_func.h"
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->buf.size();
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 size_t start;
63 size_t len;
64 if (word) {
65 /* Delete a complete word. */
66 if (backspace) {
67 /* Delete whitespace and word in front of the caret. */
68 start = this->char_iter->Prev(StringIterator::ITER_WORD);
69 len = this->caretpos - start;
70 } else {
71 /* Delete word and following whitespace following the caret. */
72 start = this->caretpos;
73 len = this->char_iter->Next(StringIterator::ITER_WORD) - start;
74 }
75 /* Update character count. */
76 this->chars -= static_cast<uint16_t>(Utf8StringLength(std::string_view(this->buf).substr(start, len)));
77 } else {
78 /* Delete a single character. */
79 if (backspace) {
80 /* Delete the last code point in front of the caret. */
81 Utf8View view(this->buf);
82 auto it = view.GetIterAtByte(this->caretpos);
83 --it;
84 start = it.GetByteOffset();
85 len = this->caretpos - start;
86 this->chars--;
87 } else {
88 /* Delete the complete character following the caret. */
89 start = this->caretpos;
90 len = this->char_iter->Next(StringIterator::ITER_CHARACTER) - start;
91 /* Update character count. */
92 this->chars -= static_cast<uint16_t>(Utf8StringLength(std::string_view(this->buf).substr(start, len)));
93 }
94 }
95
96 /* Move the remaining characters over the marker */
97 this->buf.erase(start, len);
98
99 if (backspace) this->caretpos -= static_cast<uint16_t>(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 auto [src, len] = EncodeUtf8(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 this->buf.insert(this->caretpos, src, len);
134 this->chars++;
135 this->caretpos += len;
136
137 this->UpdateStringIter();
138 this->UpdateWidth();
139 this->UpdateCaretPosition();
140 this->UpdateMarkedText();
141 return true;
142 }
143 return false;
144}
145
157bool Textbuf::InsertString(std::string_view str, bool marked, std::optional<size_t> caret, std::optional<size_t> insert_location, std::optional<size_t> replacement_end)
158{
159 uint16_t insertpos = (marked && this->marklength != 0) ? this->markpos : this->caretpos;
160 if (insert_location.has_value()) {
161 insertpos = static_cast<uint16_t>(*insert_location);
162 if (insertpos >= this->buf.size()) return false;
163
164 if (replacement_end.has_value()) {
165 this->DeleteText(insertpos, static_cast<uint16_t>(*replacement_end), str.empty());
166 }
167 } else {
168 if (marked) this->DiscardMarkedText(str.empty());
169 }
170
171 if (str.empty()) return false;
172
173 uint16_t chars = 0;
174 uint16_t bytes;
175 {
176 Utf8View view(str);
177 auto cur = view.begin();
178 const auto end = view.end();
179 while (cur != end) {
180 if (!IsValidChar(*cur, this->afilter)) break;
181
182 auto next = cur;
183 ++next;
184 if (this->buf.size() + next.GetByteOffset() >= this->max_bytes) break;
185 if (this->chars + chars + 1 > this->max_chars) break;
186
187 cur = next;
188 chars++;
189 }
190 bytes = static_cast<uint16_t>(cur.GetByteOffset());
191 }
192 if (bytes == 0) return false;
193
194 /* Move caret if needed. */
195 if (caret.has_value()) this->caretpos = insertpos + static_cast<uint16_t>(*caret);
196
197 if (marked) {
198 this->markpos = insertpos;
199 this->markend = insertpos + bytes;
200 }
201
202 this->buf.insert(insertpos, str.substr(0, bytes));
203
204 this->chars += chars;
205 if (!marked && !caret.has_value()) this->caretpos += bytes;
206 assert(this->buf.size() < this->max_bytes);
207 assert(this->chars <= this->max_chars);
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(), false);
229}
230
237void Textbuf::DeleteText(uint16_t from, uint16_t to, bool update)
238{
239 assert(from <= to);
240
241 /* Strip marked characters from buffer. */
242 this->chars -= static_cast<uint16_t>(Utf8StringLength(std::string_view(this->buf).substr(from, to - from)));
243 this->buf.erase(from, to - from);
244
245 auto fixup = [&](uint16_t &pos) {
246 if (pos <= from) return;
247 if (pos <= to) {
248 pos = from;
249 } else {
250 pos -= to - from;
251 }
252 };
253
254 /* Fixup caret if needed. */
255 fixup(this->caretpos);
256
257 /* Fixup marked text if needed. */
258 fixup(this->markpos);
259 fixup(this->markend);
260
261 if (update) {
262 this->UpdateStringIter();
263 this->UpdateCaretPosition();
264 this->UpdateMarkedText();
265 }
266}
267
273{
274 if (this->markend == 0) return;
275
276 this->DeleteText(this->markpos, this->markend, update);
277 this->markpos = this->markend = this->markxoffs = this->marklength = 0;
278}
279
284std::string_view Textbuf::GetText() const
285{
286 return this->buf;
287}
288
291{
292 this->char_iter->SetString(this->buf);
293 size_t pos = this->char_iter->SetCurPosition(this->caretpos);
294 this->caretpos = pos == StringIterator::END ? 0 : (uint16_t)pos;
295}
296
299{
300 this->pixels = GetStringBoundingBox(this->buf, FS_NORMAL).width;
301}
302
305{
306 const auto pos = GetCharPosInString(this->buf, this->caretpos, FS_NORMAL);
307 this->caretxoffs = _current_text_dir == TD_LTR ? pos.left : pos.right;
308}
309
312{
313 if (this->markend != 0) {
314 const auto pos = GetCharPosInString(this->buf, this->markpos, FS_NORMAL);
315 const auto end = GetCharPosInString(this->buf, this->markend, FS_NORMAL);
316 this->markxoffs = std::min(pos.left, end.left);
317 this->marklength = std::max(pos.right, end.right) - this->markxoffs;
318 } else {
319 this->markxoffs = this->marklength = 0;
320 }
321}
322
329{
330 if (this->caretpos == 0) return false;
331
332 size_t pos = this->char_iter->Prev(what);
333 if (pos == StringIterator::END) return true;
334
335 this->caretpos = static_cast<uint16_t>(pos);
336 this->UpdateCaretPosition();
337 return true;
338}
339
346{
347 if (this->caretpos >= this->buf.size()) return false;
348
349 size_t pos = this->char_iter->Next(what);
350 if (pos == StringIterator::END) return true;
351
352 this->caretpos = static_cast<uint16_t>(pos);
353 this->UpdateCaretPosition();
354 return true;
355}
356
363bool Textbuf::MovePos(uint16_t keycode)
364{
365 switch (keycode) {
366 case WKC_LEFT:
367 case WKC_CTRL | WKC_LEFT: {
368 auto move_type = (keycode & WKC_CTRL) != 0 ? StringIterator::ITER_WORD : StringIterator::ITER_CHARACTER;
369 return (_current_text_dir == TD_LTR) ? this->MovePrev(move_type) : this->MoveNext(move_type);
370 }
371
372 case WKC_RIGHT:
373 case WKC_CTRL | WKC_RIGHT: {
374 auto move_type = (keycode & WKC_CTRL) != 0 ? StringIterator::ITER_WORD : StringIterator::ITER_CHARACTER;
375 return (_current_text_dir == TD_LTR) ? this->MoveNext(move_type) : this->MovePrev(move_type);
376 }
377
378 case WKC_HOME:
379 this->caretpos = 0;
380 this->char_iter->SetCurPosition(this->caretpos);
381 this->UpdateCaretPosition();
382 return true;
383
384 case WKC_END:
385 this->caretpos = static_cast<uint16_t>(this->buf.size());
386 this->char_iter->SetCurPosition(this->caretpos);
387 this->UpdateCaretPosition();
388 return true;
389
390 default:
391 break;
392 }
393
394 return false;
395}
396
403Textbuf::Textbuf(uint16_t max_bytes, uint16_t max_chars)
404 : char_iter(StringIterator::Create())
405{
406 assert(max_bytes != 0);
407 assert(max_chars != 0);
408
409 this->afilter = CS_ALPHANUMERAL;
410 this->max_bytes = max_bytes;
411 this->max_chars = max_chars == UINT16_MAX ? max_bytes : max_chars;
412 this->caret = true;
413 this->DeleteAll();
414}
415
420void Textbuf::Assign(std::string_view text)
421{
422 size_t bytes = std::min<size_t>(this->max_bytes - 1, text.size());
423 this->buf = StrMakeValid(text.substr(0, bytes));
424
425 /* Make sure the name isn't too long for the text buffer in the number of
426 * characters (not bytes). max_chars also counts the '\0' characters. */
427 Utf8View view(text);
428 auto it = view.begin();
429 const auto end = view.end();
430 for (size_t len = 1; len < this->max_chars && it != end; ++len) ++it;
431
432 if (it != end) {
433 this->buf.erase(it.GetByteOffset(), std::string::npos);
434 }
435
436 this->UpdateSize();
437}
438
439
446{
447 this->chars = static_cast<uint16_t>(Utf8StringLength(this->buf) + 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
Constant span of UTF-8 encoded data.
Definition utf8.hpp:30
iterator GetIterAtByte(size_t offset) const
Create iterator pointing at codepoint, which occupies the byte position "offset".
Definition utf8.cpp:83
std::pair< char[4], size_t > EncodeUtf8(char32_t c)
Encode a character to UTF-8.
Definition utf8.cpp:21
Dimension GetStringBoundingBox(std::string_view str, FontSize start_fontsize)
Return the string dimension in pixels.
Definition gfx.cpp:887
Functions related to the gfx engine.
ParagraphLayouter::Position GetCharPosInString(std::string_view str, size_t pos, 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:251
A number of safeguards to prevent using unsafe methods.
Definition of base types and functions in a cross-platform compatible way.
size_t Utf8StringLength(std::string_view str)
Get the length of an UTF-8 encoded string in number of characters and thus not the number of bytes th...
Definition string.cpp:347
bool IsValidChar(char32_t key, CharSetFilter afilter)
Only allow certain keys.
Definition string.cpp:371
static void StrMakeValid(Builder &builder, StringConsumer &consumer, StringValidationSettings settings)
Copies the valid (UTF-8) characters from consumer to the builder.
Definition string.cpp:117
Functions related to low-level strings.
@ 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:57
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:363
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:311
void UpdateWidth()
Update pixel width of the text.
Definition textbuf.cpp:298
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:112
uint16_t max_bytes
the maximum size of the buffer in bytes (including terminating '\0')
std::string_view GetText() const
Get the current text.
Definition textbuf.cpp:284
uint16_t chars
the current size of the string in characters (including terminating '\0')
bool MovePrev(StringIterator::IterType what)
Move to previous character position.
Definition textbuf.cpp:328
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:237
bool MoveNext(StringIterator::IterType what)
Move to next character position.
Definition textbuf.cpp:345
bool InsertClipboard()
Insert a chunk of text from the clipboard onto the textbuffer.
Definition textbuf.cpp:223
bool InsertString(std::string_view str, bool marked, std::optional< size_t > caret=std::nullopt, std::optional< size_t > insert_location=std::nullopt, std::optional< size_t > replacement_end=std::nullopt)
Insert a string into the text buffer.
Definition textbuf.cpp:157
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 Assign(std::string_view text)
Copy a string into the textbuffer.
Definition textbuf.cpp:420
void DiscardMarkedText(bool update=true)
Discard any marked text.
Definition textbuf.cpp:272
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:290
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:403
std::string buf
buffer in which text is saved
void UpdateCaretPosition()
Update pixel position of the caret.
Definition textbuf.cpp:304
std::optional< std::string > GetClipboardContents()
Try to retrieve the current clipboard contents.
Definition unix.cpp:206
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:206
Handling of UTF-8 encoded data.
Window functions not directly related to making/drawing windows.