OpenTTD Source: src/textbuf.cpp Source File

OpenTTD Source 15.0-RC2

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 <https://www.gnu.org/licenses/old-licenses/gpl-2.0>.
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
40 bool Textbuf::CanDelChar(bool backspace)
41{
42 return backspace ? this->caretpos != 0 : this->caretpos < this->buf.size();
43}
44
51 bool 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
112 void Textbuf::DeleteAll()
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
128 bool 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
157 bool 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
223 bool Textbuf::InsertClipboard()
224{
225 auto contents = GetClipboardContents();
226 if (!contents.has_value()) return false;
227
228 return this->InsertString(contents.value(), false);
229}
230
237 void 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
272 void Textbuf::DiscardMarkedText(bool update)
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
290 void Textbuf::UpdateStringIter()
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
298 void Textbuf::UpdateWidth()
299{
300 this->pixels = GetStringBoundingBox(this->buf, FS_NORMAL).width;
301}
302
304 void Textbuf::UpdateCaretPosition()
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
311 void Textbuf::UpdateMarkedText()
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
328 bool Textbuf::MovePrev(StringIterator::IterType what)
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
345 bool Textbuf::MoveNext(StringIterator::IterType what)
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
363 bool 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
420 void 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
445 void Textbuf::UpdateSize()
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
463 bool Textbuf::HandleCaret()
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
475 HandleKeyPressResult 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}
StringIterator
Class for iterating over different kind of parts of a string.
Definition string_base.h:14
StringIterator::END
static const size_t END
Sentinel to indicate end-of-iteration.
Definition string_base.h:23
StringIterator::IterType
IterType
Type of the iterator.
Definition string_base.h:17
StringIterator::ITER_WORD
@ ITER_WORD
Iterate over words.
Definition string_base.h:19
StringIterator::ITER_CHARACTER
@ ITER_CHARACTER
Iterate over characters (or more exactly grapheme clusters).
Definition string_base.h:18
Utf8View
Constant span of UTF-8 encoded data.
Definition utf8.hpp:30
Utf8View::GetIterAtByte
iterator GetIterAtByte(size_t offset) const
Create iterator pointing at codepoint, which occupies the byte position "offset".
Definition utf8.cpp:83
EncodeUtf8
std::pair< char[4], size_t > EncodeUtf8(char32_t c)
Encode a character to UTF-8.
Definition utf8.cpp:21
GetStringBoundingBox
Dimension GetStringBoundingBox(std::string_view str, FontSize start_fontsize)
Return the string dimension in pixels.
Definition gfx.cpp:894
gfx_func.h
Functions related to the gfx engine.
GetCharPosInString
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.
Definition gfx_layout.cpp:425
gfx_layout.h
Functions related to laying out the texts.
gfx_type.h
Types related to the graphics and/or input devices.
FS_NORMAL
@ FS_NORMAL
Index of the normal font in the font tables.
Definition gfx_type.h:249
safeguards.h
A number of safeguards to prevent using unsafe methods.
stdafx.h
Definition of base types and functions in a cross-platform compatible way.
Utf8StringLength
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:349
IsValidChar
bool IsValidChar(char32_t key, CharSetFilter afilter)
Only allow certain keys.
Definition string.cpp:373
StrMakeValid
static void StrMakeValid(Builder &builder, StringConsumer &consumer, StringValidationSettings settings)
Copies the valid (UTF-8) characters from consumer to the builder.
Definition string.cpp:119
string_func.h
Functions related to low-level strings.
CS_ALPHANUMERAL
@ CS_ALPHANUMERAL
Both numeric and alphabetic and spaces and stuff.
Definition string_type.h:25
_current_text_dir
TextDirection _current_text_dir
Text direction of the currently selected language.
Definition strings.cpp:56
strings_func.h
Functions related to OTTD's strings.
TD_LTR
@ TD_LTR
Text is written left-to-right by default.
Definition strings_type.h:26
Textbuf::pixels
uint16_t pixels
the current size of the string in pixels
Definition textbuf_type.h:35
Textbuf::caretpos
uint16_t caretpos
the current position of the caret in the buffer, in bytes
Definition textbuf_type.h:37
Textbuf::UpdateSize
void UpdateSize()
Update Textbuf type with its actual physical character and screenlength Get the count of characters i...
Definition textbuf.cpp:445
Textbuf::MovePos
bool MovePos(uint16_t keycode)
Handle text navigation with arrow keys left/right.
Definition textbuf.cpp:363
Textbuf::markxoffs
uint16_t markxoffs
the start position of the marked area in pixels
Definition textbuf_type.h:41
Textbuf::UpdateMarkedText
void UpdateMarkedText()
Update pixel positions of the marked text area.
Definition textbuf.cpp:311
Textbuf::UpdateWidth
void UpdateWidth()
Update pixel width of the text.
Definition textbuf.cpp:298
Textbuf::DeleteChar
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
Textbuf::DeleteAll
void DeleteAll()
Delete every character in the textbuffer.
Definition textbuf.cpp:112
Textbuf::max_bytes
uint16_t max_bytes
the maximum size of the buffer in bytes (including terminating '\0')
Definition textbuf_type.h:32
Textbuf::GetText
std::string_view GetText() const
Get the current text.
Definition textbuf.cpp:284
Textbuf::chars
uint16_t chars
the current size of the string in characters (including terminating '\0')
Definition textbuf_type.h:34
Textbuf::MovePrev
bool MovePrev(StringIterator::IterType what)
Move to previous character position.
Definition textbuf.cpp:328
Textbuf::max_chars
uint16_t max_chars
the maximum size of the buffer in characters (including terminating '\0')
Definition textbuf_type.h:33
Textbuf::markend
uint16_t markend
the end position of the marked area in the buffer, in bytes
Definition textbuf_type.h:40
Textbuf::DeleteText
void DeleteText(uint16_t from, uint16_t to, bool update)
Delete a part of the text.
Definition textbuf.cpp:237
Textbuf::MoveNext
bool MoveNext(StringIterator::IterType what)
Move to next character position.
Definition textbuf.cpp:345
Textbuf::InsertClipboard
bool InsertClipboard()
Insert a chunk of text from the clipboard onto the textbuffer.
Definition textbuf.cpp:223
Textbuf::InsertString
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
Textbuf::caretxoffs
uint16_t caretxoffs
the current position of the caret in pixels
Definition textbuf_type.h:38
Textbuf::marklength
uint16_t marklength
the length of the marked area in pixels
Definition textbuf_type.h:42
Textbuf::markpos
uint16_t markpos
the start position of the marked area in the buffer, in bytes
Definition textbuf_type.h:39
Textbuf::CanDelChar
bool CanDelChar(bool backspace)
Checks if it is possible to delete a character.
Definition textbuf.cpp:40
Textbuf::Assign
void Assign(std::string_view text)
Copy a string into the textbuffer.
Definition textbuf.cpp:420
Textbuf::DiscardMarkedText
void DiscardMarkedText(bool update=true)
Discard any marked text.
Definition textbuf.cpp:272
Textbuf::caret
bool caret
is the caret ("_") visible or not
Definition textbuf_type.h:36
Textbuf::afilter
CharSetFilter afilter
Allowed characters.
Definition textbuf_type.h:31
Textbuf::InsertChar
bool InsertChar(char32_t key)
Insert a character to a textbuffer.
Definition textbuf.cpp:128
Textbuf::UpdateStringIter
void UpdateStringIter()
Update the character iter after the text has changed.
Definition textbuf.cpp:290
Textbuf::HandleCaret
bool HandleCaret()
Handle the flashing of the caret.
Definition textbuf.cpp:463
Textbuf::Textbuf
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
Textbuf::buf
std::string buf
buffer in which text is saved
Definition textbuf_type.h:69
Textbuf::UpdateCaretPosition
void UpdateCaretPosition()
Update pixel position of the caret.
Definition textbuf.cpp:304
GetClipboardContents
std::optional< std::string > GetClipboardContents()
Try to retrieve the current clipboard contents.
Definition unix.cpp:205
textbuf_type.h
Stuff related to text buffers.
HandleKeyPressResult
HandleKeyPressResult
Return values for Textbuf::HandleKeypress.
Definition textbuf_type.h:21
HKPR_NOT_HANDLED
@ HKPR_NOT_HANDLED
Key does not affect editboxes.
Definition textbuf_type.h:26
HKPR_CANCEL
@ HKPR_CANCEL
Escape key pressed.
Definition textbuf_type.h:25
HKPR_EDITING
@ HKPR_EDITING
Textbuf content changed.
Definition textbuf_type.h:22
HKPR_CONFIRM
@ HKPR_CONFIRM
Return or enter key pressed.
Definition textbuf_type.h:24
HKPR_CURSOR
@ HKPR_CURSOR
Non-text change, e.g. cursor position.
Definition textbuf_type.h:23
GetClipboardContents
std::optional< std::string > GetClipboardContents()
Try to retrieve the current clipboard contents.
Definition unix.cpp:205
utf8.hpp
Handling of UTF-8 encoded data.
window_func.h
Window functions not directly related to making/drawing windows.