Inline GfxMainBlitterViewport
[openttd/fttd.git] / src / textbuf.cpp
blob2796b6f16d6940ec7754d942d30719bee7b3a003
1 /* $Id$ */
3 /*
4 * This file is part of OpenTTD.
5 * 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.
6 * 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.
7 * 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/>.
8 */
10 /** @file textbuf.cpp Textbuffer handling. */
12 #include "stdafx.h"
13 #include <stdarg.h>
15 #include "textbuf_type.h"
16 #include "string.h"
17 #include "strings_func.h"
18 #include "gfx_type.h"
19 #include "gfx_func.h"
20 #include "gfx_layout.h"
21 #include "window_func.h"
22 #include "language.h"
24 /**
25 * Try to retrieve the current clipboard contents.
27 * @note OS-specific function.
28 * @return True if some text could be retrieved.
30 bool GetClipboardContents(char *buffer, size_t buff_len);
32 int _caret_timer;
35 /** Sentinel to indicate end-of-iteration. */
36 static const size_t END = SIZE_MAX;
39 #ifdef WITH_ICU_SORT
41 void Textbuf::SetString (const char *s)
43 const char *string_base = s;
45 /* Unfortunately current ICU versions only provide rudimentary support
46 * for word break iterators (especially for CJK languages) in combination
47 * with UTF-8 input. As a work around we have to convert the input to
48 * UTF-16 and create a mapping back to UTF-8 character indices. */
49 this->utf16_str.Clear();
50 this->utf16_to_utf8.Clear();
52 while (*s != '\0') {
53 size_t idx = s - string_base;
55 WChar c = Utf8Consume(&s);
56 if (c < 0x10000) {
57 *this->utf16_str.Append() = (UChar)c;
58 } else {
59 /* Make a surrogate pair. */
60 *this->utf16_str.Append() = (UChar)(0xD800 + ((c - 0x10000) >> 10));
61 *this->utf16_str.Append() = (UChar)(0xDC00 + ((c - 0x10000) & 0x3FF));
62 *this->utf16_to_utf8.Append() = idx;
64 *this->utf16_to_utf8.Append() = idx;
66 *this->utf16_str.Append() = '\0';
67 *this->utf16_to_utf8.Append() = s - string_base;
69 UText text = UTEXT_INITIALIZER;
70 UErrorCode status = U_ZERO_ERROR;
71 utext_openUChars(&text, this->utf16_str.Begin(), this->utf16_str.Length() - 1, &status);
72 this->char_itr->setText(&text, status);
73 this->word_itr->setText(&text, status);
74 this->char_itr->first();
75 this->word_itr->first();
78 size_t Textbuf::SetCurPosition (size_t pos)
80 /* Convert incoming position to an UTF-16 string index. */
81 uint utf16_pos = 0;
82 for (uint i = 0; i < this->utf16_to_utf8.Length(); i++) {
83 if (this->utf16_to_utf8[i] == pos) {
84 utf16_pos = i;
85 break;
89 /* isBoundary has the documented side-effect of setting the current
90 * position to the first valid boundary equal to or greater than
91 * the passed value. */
92 this->char_itr->isBoundary(utf16_pos);
93 return this->utf16_to_utf8[this->char_itr->current()];
96 size_t Textbuf::Next (bool word)
98 int32_t pos;
100 if (word) {
101 pos = this->word_itr->following (this->char_itr->current());
102 /* The ICU word iterator considers both the start and the end of a word a valid
103 * break point, but we only want word starts. Move to the next location in
104 * case the new position points to whitespace. */
105 while (pos != icu::BreakIterator::DONE &&
106 IsWhitespace (Utf16DecodeChar ((const uint16 *)&this->utf16_str[pos]))) {
107 int32_t new_pos = this->word_itr->next();
108 /* Don't set it to DONE if it was valid before. Otherwise we'll return END
109 * even though the iterator wasn't at the end of the string before. */
110 if (new_pos == icu::BreakIterator::DONE) break;
111 pos = new_pos;
114 this->char_itr->isBoundary (pos);
116 } else {
117 pos = this->char_itr->next();
120 return pos == icu::BreakIterator::DONE ? END : this->utf16_to_utf8[pos];
123 size_t Textbuf::Prev (bool word)
125 int32_t pos;
127 if (word) {
128 pos = this->word_itr->preceding (this->char_itr->current());
129 /* The ICU word iterator considers both the start and the end of a word a valid
130 * break point, but we only want word starts. Move to the previous location in
131 * case the new position points to whitespace. */
132 while (pos != icu::BreakIterator::DONE &&
133 IsWhitespace (Utf16DecodeChar ((const uint16 *)&this->utf16_str[pos]))) {
134 int32_t new_pos = this->word_itr->previous();
135 /* Don't set it to DONE if it was valid before. Otherwise we'll return END
136 * even though the iterator wasn't at the start of the string before. */
137 if (new_pos == icu::BreakIterator::DONE) break;
138 pos = new_pos;
141 this->char_itr->isBoundary (pos);
143 } else {
144 pos = this->char_itr->previous();
147 return pos == icu::BreakIterator::DONE ? END : this->utf16_to_utf8[pos];
150 #else
152 inline void Textbuf::SetString (const char *s)
154 this->cur_pos = 0;
157 size_t Textbuf::SetCurPosition (size_t pos)
159 assert (pos <= this->len);
160 /* Sanitize in case we get a position inside an UTF-8 sequence. */
161 while (pos > 0 && IsUtf8Part(this->buffer[pos])) pos--;
162 return this->cur_pos = pos;
165 size_t Textbuf::Next (bool word)
167 /* Already at the end? */
168 if (this->cur_pos >= this->len) return END;
170 if (word) {
171 WChar c;
172 /* Consume current word. */
173 size_t offs = Utf8Decode (&c, this->buffer + this->cur_pos);
174 while (this->cur_pos < this->len && !IsWhitespace(c)) {
175 this->cur_pos += offs;
176 offs = Utf8Decode (&c, this->buffer + this->cur_pos);
178 /* Consume whitespace to the next word. */
179 while (this->cur_pos < this->len && IsWhitespace(c)) {
180 this->cur_pos += offs;
181 offs = Utf8Decode (&c, this->buffer + this->cur_pos);
184 return this->cur_pos;
186 } else {
187 WChar c;
188 this->cur_pos += Utf8Decode (&c, this->buffer + this->cur_pos);
189 return this->cur_pos;
193 size_t Textbuf::Prev (bool word)
195 /* Already at the beginning? */
196 if (this->cur_pos == 0) return END;
198 if (word) {
199 const char *s = this->buffer + this->cur_pos;
200 WChar c;
201 /* Consume preceding whitespace. */
202 do {
203 s = Utf8PrevChar (s);
204 Utf8Decode (&c, s);
205 } while (s > this->buffer && IsWhitespace(c));
206 /* Consume preceding word. */
207 while (s > this->buffer && !IsWhitespace(c)) {
208 s = Utf8PrevChar (s);
209 Utf8Decode (&c, s);
211 /* Move caret back to the beginning of the word. */
212 if (IsWhitespace(c)) Utf8Consume (&s);
214 return this->cur_pos = s - this->buffer;
216 } else {
217 return this->cur_pos = Utf8PrevChar (this->buffer + this->cur_pos) - this->buffer;
221 #endif
225 * Get the positions of two characters relative to the start of the string.
226 * @param c1 Pointer to the first character in the string.
227 * @param[out] x1 Left position of the glyph associated with c1.
228 * @param c2 Pointer to the second character in the string.
229 * @param[out] x2 Left position of the glyph associated with c2.
231 void Textbuf::GetCharPositions (const char *c1, int *x1, const char *c2, int *x2) const
233 Layouter layout (this->GetText());
234 *x1 = layout.front()->GetCharPosition (this->GetText(), c1);
235 *x2 = (c2 == c1) ? *x1 : layout.front()->GetCharPosition (this->GetText(), c2);
239 * Get the character that is drawn at a specific position.
240 * @param x Position relative to the start of the string.
241 * @return Pointer to the character at the position or NULL if no character is at the position.
243 const char *Textbuf::GetCharAtPosition (int x) const
245 if (x < 0) return NULL;
247 Layouter layout (this->GetText());
248 return layout.front()->GetCharAtPosition (this->GetText(), x);
252 * Delete a character from a textbuffer, either with 'Delete' or 'Backspace'
253 * The character is delete from the position the caret is at
254 * @param backspace Delete backwards
255 * @param word Delete a whole word
256 * @return Return true on successful change of Textbuf, or false otherwise
258 bool Textbuf::DeleteChar (bool backspace, bool word)
260 if (this->caretpos == (backspace ? 0 : this->length())) return false;
262 char *s = this->buffer + this->caretpos;
263 uint16 len = 0;
265 if (word) {
266 /* Delete a complete word. */
267 if (backspace) {
268 /* Delete whitespace and word in front of the caret. */
269 len = this->caretpos - (uint16)this->Prev(true);
270 s -= len;
271 } else {
272 /* Delete word and following whitespace following the caret. */
273 len = (uint16)this->Next(true) - this->caretpos;
275 /* Update character count. */
276 for (const char *ss = s; ss < s + len; Utf8Consume(&ss)) {
277 this->chars--;
279 } else {
280 /* Delete a single character. */
281 if (backspace) {
282 /* Delete the last code point in front of the caret. */
283 s = Utf8PrevChar(s);
284 WChar c;
285 len = (uint16)Utf8Decode(&c, s);
286 this->chars--;
287 } else {
288 /* Delete the complete character following the caret. */
289 len = (uint16)this->Next(false) - this->caretpos;
290 /* Update character count. */
291 for (const char *ss = s; ss < s + len; Utf8Consume(&ss)) {
292 this->chars--;
297 /* Move the remaining characters over the marker */
298 memmove(s, s + len, this->len + 1 - (s - this->buffer) - len);
299 this->len -= len;
301 if (backspace) this->caretpos -= len;
303 this->UpdateStringIter();
304 this->UpdateWidth();
305 this->UpdateCaretPosition();
306 this->UpdateMarkedText();
308 return true;
312 * Delete every character in the textbuffer
314 void Textbuf::DeleteAll()
316 this->zerofill();
317 this->chars = 1;
318 this->pixels = this->caretpos = this->caretxoffs = 0;
319 this->markpos = this->markend = this->markxoffs = this->marklength = 0;
320 this->UpdateStringIter();
324 * Insert a character to a textbuffer. If maxwidth of the Textbuf is zero,
325 * we don't care about the visual-length but only about the physical
326 * length of the string
327 * @param key Character to be inserted
328 * @return Return true on successful change of Textbuf, or false otherwise
330 bool Textbuf::InsertChar(WChar key)
332 uint16 len = (uint16)Utf8CharLen(key);
333 if (this->len + len < this->capacity && this->chars + 1 <= this->max_chars) {
334 memmove(this->buffer + this->caretpos + len, this->buffer + this->caretpos, this->len + 1 - this->caretpos);
335 Utf8Encode(this->buffer + this->caretpos, key);
336 this->chars++;
337 this->len += len;
338 this->caretpos += len;
340 this->UpdateStringIter();
341 this->UpdateWidth();
342 this->UpdateCaretPosition();
343 this->UpdateMarkedText();
344 return true;
346 return false;
350 * Insert a string into the text buffer. If maxwidth of the Textbuf is zero,
351 * we don't care about the visual-length but only about the physical
352 * length of the string.
353 * @param str String to insert.
354 * @param marked Replace the currently marked text with the new text.
355 * @param caret Move the caret to this point in the insertion string.
356 * @param insert_location Position at which to insert the string.
357 * @param replacement_end Replace all characters from #insert_location up to this location with the new string.
358 * @return True on successful change of Textbuf, or false otherwise.
360 bool Textbuf::InsertString(const char *str, bool marked, const char *caret, const char *insert_location, const char *replacement_end)
362 uint16 insertpos = (marked && this->marklength != 0) ? this->markpos : this->caretpos;
363 if (insert_location != NULL) {
364 insertpos = insert_location - this->buffer;
365 if (insertpos > this->len + 1) return false;
367 if (replacement_end != NULL) {
368 this->DeleteText(insertpos, replacement_end - this->buffer, str == NULL);
370 } else {
371 if (marked && (this->markend != 0)) {
372 this->DeleteText (this->markpos, this->markend, str == NULL);
373 this->markpos = this->markend = this->markxoffs = this->marklength = 0;
377 if (str == NULL) return false;
379 uint16 bytes = 0, chars = 0;
380 WChar c;
381 for (const char *ptr = str; (c = Utf8Consume(&ptr)) != '\0';) {
382 if (!IsValidChar(c, this->afilter)) break;
384 byte len = Utf8CharLen(c);
385 if (this->len + bytes + len >= this->capacity) break;
386 if (this->chars + chars + 1 > this->max_chars) break;
388 bytes += len;
389 chars++;
391 /* Move caret if needed. */
392 if (ptr == caret) this->caretpos = insertpos + bytes;
395 if (bytes == 0) return false;
397 if (marked) {
398 this->markpos = insertpos;
399 this->markend = insertpos + bytes;
402 memmove(this->buffer + insertpos + bytes, this->buffer + insertpos, this->len + 1 - insertpos);
403 memcpy(this->buffer + insertpos, str, bytes);
405 this->len += bytes;
406 this->chars += chars;
407 if (!marked && caret == NULL) this->caretpos += bytes;
408 assert(this->len < this->capacity);
409 assert(this->chars <= this->max_chars);
410 this->buffer[this->len] = '\0'; // terminating zero
412 this->UpdateStringIter();
413 this->UpdateWidth();
414 this->UpdateCaretPosition();
415 this->UpdateMarkedText();
417 return true;
421 * Insert a chunk of text from the clipboard onto the textbuffer. Get TEXT clipboard
422 * and append this up to the maximum length (either absolute or screenlength). If maxlength
423 * is zero, we don't care about the screenlength but only about the physical length of the string
424 * @return true on successful change of Textbuf, or false otherwise
426 bool Textbuf::InsertClipboard()
428 char utf8_buf[512];
430 if (!GetClipboardContents(utf8_buf, lengthof(utf8_buf))) return false;
432 return this->InsertString(utf8_buf, false);
436 * Delete a part of the text.
437 * @param from Start of the text to delete.
438 * @param to End of the text to delete.
439 * @param update Set to true if the internal state should be updated.
441 void Textbuf::DeleteText(uint16 from, uint16 to, bool update)
443 uint c = 0;
444 const char *s = this->buffer + from;
445 while (s < this->buffer + to) {
446 Utf8Consume(&s);
447 c++;
450 /* Strip marked characters from buffer. */
451 memmove(this->buffer + from, this->buffer + to, this->len + 1 - to);
452 this->len -= to - from;
453 this->chars -= c;
455 /* Fixup caret if needed. */
456 if (this->caretpos > from) {
457 if (this->caretpos <= to) {
458 this->caretpos = from;
459 } else {
460 this->caretpos -= to - from;
464 if (update) {
465 this->UpdateStringIter();
466 this->UpdateCaretPosition();
467 this->UpdateMarkedText();
471 /** Update the character iter after the text has changed. */
472 void Textbuf::UpdateStringIter()
474 this->SetString (this->buffer);
475 size_t pos = this->SetCurPosition (this->caretpos);
476 this->caretpos = pos == END ? 0 : (uint16)pos;
479 /** Update pixel width of the text. */
480 void Textbuf::UpdateWidth()
482 this->pixels = GetStringBoundingBox(this->buffer, FS_NORMAL).width;
485 /** Update pixel position of the caret. */
486 void Textbuf::UpdateCaretPosition()
488 if (this->chars > 1) {
489 Layouter layout (this->GetText());
490 this->caretxoffs = layout.front()->GetCharPosition (this->GetText(), this->buffer + this->caretpos);
491 } else {
492 this->caretxoffs = 0;
496 /** Update pixel positions of the marked text area. */
497 void Textbuf::UpdateMarkedText()
499 if (this->markend != 0) {
500 int x1, x2;
501 this->GetCharPositions (this->buffer + this->markpos, &x1,
502 this->buffer + this->markend, &x2);
503 this->markxoffs = x1;
504 this->marklength = x2 - x1;
505 } else {
506 this->markxoffs = this->marklength = 0;
511 * Handle text navigation to the left.
512 * @param word Whether to move a whole word left.
513 * @return Return true on successful change of Textbuf, or false otherwise
515 bool Textbuf::MoveLeft (bool word)
517 if (this->caretpos == 0) return false;
519 size_t pos = this->Prev (word);
520 if (pos == END) return true;
522 this->caretpos = (uint16)pos;
523 this->UpdateCaretPosition();
524 return true;
528 * Handle text navigation to the right.
529 * @param word Whether to move a whole word right.
530 * @return Return true on successful change of Textbuf, or false otherwise
532 bool Textbuf::MoveRight (bool word)
534 if (this->caretpos >= this->length()) return false;
536 size_t pos = this->Next (word);
537 if (pos == END) return true;
539 this->caretpos = (uint16)pos;
540 this->UpdateCaretPosition();
541 return true;
545 * Handle text navigation to the end of the text.
546 * @return Return true on successful change of Textbuf, or false otherwise
548 bool Textbuf::MoveEnd (void)
550 this->caretpos = this->length();
551 this->SetCurPosition (this->caretpos);
552 this->UpdateCaretPosition();
553 return true;
557 * Initialize the textbuffer by supplying it the buffer to write into
558 * and the maximum length of this buffer
559 * @param buf the buffer that will be holding the data for input
560 * @param max_bytes maximum size in bytes, including terminating '\0'
561 * @param max_chars maximum size in chars, including terminating '\0'
563 Textbuf::Textbuf (uint16 max_bytes, char *buf, uint16 max_chars)
564 : stringb (max_bytes, buf),
565 max_chars(max_chars == UINT16_MAX ? max_bytes : max_chars)
567 assert(max_bytes != 0);
568 assert(max_chars != 0);
569 assert (buffer[0] == '\0'); // should have been set by stringb constructor
571 #ifdef WITH_ICU_SORT
572 const char *isocode = _current_language != NULL ? _current_language->isocode : "en";
573 UErrorCode status = U_ZERO_ERROR;
574 this->char_itr.reset (icu::BreakIterator::createCharacterInstance (icu::Locale(isocode), status));
575 this->word_itr.reset (icu::BreakIterator::createWordInstance (icu::Locale(isocode), status));
577 *this->utf16_str.Append() = '\0';
578 *this->utf16_to_utf8.Append() = 0;
579 #else
580 this->len = 0;
581 this->cur_pos = 0;
582 #endif
584 this->afilter = CS_ALPHANUMERAL;
585 this->caret = true;
586 this->DeleteAll();
590 * Render a string into the textbuffer.
591 * @param string String
593 void Textbuf::Assign(StringID string)
595 GetString (this, string);
596 this->UpdateSize();
600 * Copy a string into the textbuffer.
601 * @param text Source.
603 void Textbuf::Assign(const char *text)
605 this->copy (text);
606 this->UpdateSize();
610 * Print a formatted string into the textbuffer.
612 void Textbuf::Print(const char *format, ...)
614 va_list va;
615 va_start(va, format);
616 this->vfmt (format, va);
617 va_end(va);
618 this->UpdateSize();
623 * Update Textbuf type with its actual physical character and screenlength
624 * Get the count of characters in the string as well as the width in pixels.
625 * Useful when copying in a larger amount of text at once
627 void Textbuf::UpdateSize()
629 const char *buf = this->buffer;
630 assert (strlen(buf) == this->len);
631 assert (this->len < this->capacity);
632 assert (this->max_chars > 1);
634 this->chars = 1; // terminating zero
635 for (;;) {
636 if (Utf8Consume(&buf) == '\0') {
637 assert (buf == this->buffer + this->len + 1);
638 break;
640 this->chars++;
641 if (this->chars == this->max_chars) {
642 this->truncate (buf - this->buffer);
643 break;
647 this->caretpos = this->len;
648 this->UpdateStringIter();
649 this->UpdateWidth();
650 this->UpdateMarkedText();
652 this->UpdateCaretPosition();
656 * Handle the flashing of the caret.
657 * @return True if the caret state changes.
659 bool Textbuf::HandleCaret()
661 /* caret changed? */
662 bool b = !!(_caret_timer & 0x20);
664 if (b != this->caret) {
665 this->caret = b;
666 return true;
668 return false;
671 HandleKeyPressResult Textbuf::HandleKeyPress(WChar key, uint16 keycode)
673 bool edited = false;
675 switch (keycode) {
676 case WKC_ESC: return HKPR_CANCEL;
678 case WKC_RETURN: case WKC_NUM_ENTER: return HKPR_CONFIRM;
680 case (WKC_CTRL | 'V'):
681 edited = this->InsertClipboard();
682 break;
684 case (WKC_CTRL | 'U'):
685 this->DeleteAll();
686 edited = true;
687 break;
689 case WKC_BACKSPACE: case WKC_DELETE:
690 case WKC_CTRL | WKC_BACKSPACE: case WKC_CTRL | WKC_DELETE: {
691 bool word = (keycode & WKC_CTRL) != 0;
692 bool backspace = (keycode & ~WKC_SPECIAL_KEYS) == WKC_BACKSPACE;
693 edited = this->DeleteChar (backspace, word);
694 break;
697 case WKC_LEFT:
698 case WKC_CTRL | WKC_LEFT:
699 this->MoveLeft (keycode & WKC_CTRL);
700 break;
702 case WKC_RIGHT:
703 case WKC_CTRL | WKC_RIGHT:
704 this->MoveRight (keycode & WKC_CTRL);
705 break;
707 case WKC_HOME:
708 this->caretpos = 0;
709 this->SetCurPosition (this->caretpos);
710 this->UpdateCaretPosition();
711 break;
713 case WKC_END:
714 this->MoveEnd();
715 break;
717 default:
718 if (IsValidChar(key, this->afilter)) {
719 edited = this->InsertChar(key);
720 } else {
721 return HKPR_NOT_HANDLED;
723 break;
726 return edited ? HKPR_EDITING : HKPR_CURSOR;