diff --git a/projects/openttd_vs100.vcxproj b/projects/openttd_vs100.vcxproj --- a/projects/openttd_vs100.vcxproj +++ b/projects/openttd_vs100.vcxproj @@ -595,6 +595,7 @@ + diff --git a/projects/openttd_vs100.vcxproj.filters b/projects/openttd_vs100.vcxproj.filters --- a/projects/openttd_vs100.vcxproj.filters +++ b/projects/openttd_vs100.vcxproj.filters @@ -1014,6 +1014,9 @@ Header Files + + Header Files + Header Files diff --git a/projects/openttd_vs80.vcproj b/projects/openttd_vs80.vcproj --- a/projects/openttd_vs80.vcproj +++ b/projects/openttd_vs80.vcproj @@ -1655,6 +1655,10 @@ > + + diff --git a/projects/openttd_vs90.vcproj b/projects/openttd_vs90.vcproj --- a/projects/openttd_vs90.vcproj +++ b/projects/openttd_vs90.vcproj @@ -1652,6 +1652,10 @@ > + + diff --git a/source.list b/source.list --- a/source.list +++ b/source.list @@ -328,6 +328,7 @@ stdafx.h story_base.h story_type.h strgen/strgen.h +string_base.h string_func.h string_type.h stringfilter_type.h diff --git a/src/string.cpp b/src/string.cpp --- a/src/string.cpp +++ b/src/string.cpp @@ -14,6 +14,7 @@ #include "core/alloc_func.hpp" #include "core/math_func.hpp" #include "string_func.h" +#include "string_base.h" #include "table/control_codes.h" @@ -650,3 +651,123 @@ int strnatcmp(const char *s1, const char /* Do a normal comparison if ICU is missing or if we cannot create a collator. */ return strcasecmp(s1, s2); } + +#ifdef WITH_ICU + +#include +#include + +/** String iterator using ICU as a backend. */ +class IcuStringIterator : public StringIterator +{ + icu::BreakIterator *char_itr; ///< ICU iterator for characters. + const char *string; ///< Iteration string in UTF-8. + +public: + IcuStringIterator() : char_itr(NULL) + { + UErrorCode status = U_ZERO_ERROR; + this->char_itr = icu::BreakIterator::createCharacterInstance(icu::Locale(_current_language != NULL ? _current_language->isocode : "en"), status); + } + + virtual ~IcuStringIterator() + { + delete this->char_itr; + } + + virtual void SetString(const char *s) + { + this->string = s; + + UText text = UTEXT_INITIALIZER; + UErrorCode status = U_ZERO_ERROR; + utext_openUTF8(&text, s, -1, &status); + this->char_itr->setText(&text, status); + this->char_itr->first(); + } + + virtual size_t SetCurPosition(size_t pos) + { + /* isBoundary has the documented side-effect of setting the current + * position to the first valid boundary equal to or greater than + * the passed value. */ + this->char_itr->isBoundary((int32_t)pos); + return this->char_itr->current(); + } + + virtual size_t Next() + { + int32_t pos = this->char_itr->next(); + return pos == icu::BreakIterator::DONE ? END : pos; + } + + virtual size_t Prev() + { + int32_t pos = this->char_itr->previous(); + return pos == icu::BreakIterator::DONE ? END : pos; + } +}; + +/* static */ StringIterator *StringIterator::Create() +{ + return new IcuStringIterator(); +} + +#else + +/** Fallback simple string iterator. */ +class DefaultStringIterator : public StringIterator +{ + const char *string; ///< Current string. + size_t len; ///< String length. + size_t cur_pos; ///< Current iteration position. + +public: + DefaultStringIterator() : string(NULL) + { + } + + virtual void SetString(const char *s) + { + this->string = s; + this->len = strlen(s); + this->cur_pos = 0; + } + + virtual size_t SetCurPosition(size_t pos) + { + assert(this->string != NULL && pos <= this->len); + /* Sanitize in case we get a position inside an UTF-8 sequence. */ + while (pos > 0 && IsUtf8Part(this->string[pos])) pos--; + return this->cur_pos = pos; + } + + virtual size_t Next() + { + assert(this->string != NULL); + + /* Already at the end? */ + if (this->cur_pos >= this->len) return END; + + WChar c; + this->cur_pos += Utf8Decode(&c, this->string + this->cur_pos); + return this->cur_pos; + } + + virtual size_t Prev() + { + assert(this->string != NULL); + + /* Already at the beginning? */ + if (this->cur_pos == 0) return END; + + return this->cur_pos = Utf8PrevChar(this->string + this->cur_pos) - this->string; + } +}; + +/* static */ StringIterator *StringIterator::Create() +{ + return new DefaultStringIterator(); +} + +#endif diff --git a/src/string_base.h b/src/string_base.h new file mode 100644 --- /dev/null +++ b/src/string_base.h @@ -0,0 +1,60 @@ +/* $Id$ */ + +/* + * This file is part of OpenTTD. + * 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. + * 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. + * 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 . + */ + +#ifndef STRING_BASE_H +#define STRING_BASE_H + +#include "string_type.h" + +/** Class for iterating over different kind of parts of a string. */ +class StringIterator { +public: + /** Sentinel to indicate end-of-iteration. */ + static const size_t END = SIZE_MAX; + + /** + * Create a new iterator instance. + * @return New iterator instance. + */ + static StringIterator *Create(); + + virtual ~StringIterator() {} + + /** + * Set a new iteration string. Must also be called if the string contents + * changed. The cursor is reset to the start of the string. + * @param s New string. + */ + virtual void SetString(const char *s) = 0; + + /** + * Change the current string cursor. + * @param p New cursor position. + * @return Actual new cursor position at the next valid character boundary. + * @pre p has to be inside the current string. + */ + virtual size_t SetCurPosition(size_t pos) = 0; + + /** + * Advance the cursor by one iteration unit. + * @return New cursor position (in bytes) or #END if the cursor is already at the end of the string. + */ + virtual size_t Next() = 0; + + /** + * Move the cursor back by one iteration unit. + * @return New cursor position (in bytes) or #END if the cursor is already at the start of the string. + */ + virtual size_t Prev() = 0; + +protected: + StringIterator() {} +}; + +#endif /* STRING_BASE_H */ diff --git a/src/string_func.h b/src/string_func.h --- a/src/string_func.h +++ b/src/string_func.h @@ -147,6 +147,13 @@ static inline char *Utf8PrevChar(char *s return ret; } +static inline const char *Utf8PrevChar(const char *s) +{ + const char *ret = s; + while (IsUtf8Part(*--ret)) {} + return ret; +} + size_t Utf8StringLength(const char *s); /** diff --git a/src/textbuf.cpp b/src/textbuf.cpp --- a/src/textbuf.cpp +++ b/src/textbuf.cpp @@ -87,11 +87,11 @@ void Textbuf::DelChar(bool backspace) this->bytes -= len; this->chars--; + if (backspace) this->caretpos -= len; + + this->UpdateStringIter(); this->UpdateWidth(); - if (backspace) { - this->caretpos -= len; - this->UpdateCaretPosition(); - } + this->UpdateCaretPosition(); } /** @@ -147,6 +147,7 @@ void Textbuf::DeleteAll() memset(this->buf, 0, this->max_bytes); this->bytes = this->chars = 1; this->pixels = this->caretpos = this->caretxoffs = 0; + this->UpdateStringIter(); } /** @@ -163,10 +164,11 @@ bool Textbuf::InsertChar(WChar key) memmove(this->buf + this->caretpos + len, this->buf + this->caretpos, this->bytes - this->caretpos); Utf8Encode(this->buf + this->caretpos, key); this->chars++; - this->bytes += len; + this->bytes += len; + this->caretpos += len; + + this->UpdateStringIter(); this->UpdateWidth(); - - this->caretpos += len; this->UpdateCaretPosition(); return true; } @@ -210,6 +212,7 @@ bool Textbuf::InsertClipboard() assert(this->chars <= this->max_chars); this->buf[this->bytes - 1] = '\0'; // terminating zero + this->UpdateStringIter(); this->UpdateWidth(); this->UpdateCaretPosition(); @@ -234,11 +237,14 @@ WChar Textbuf::MoveCaretLeft() { assert(this->CanMoveCaretLeft()); + size_t pos = this->char_iter->Prev(); + if (pos == StringIterator::END) pos = 0; + + this->caretpos = (uint16)pos; + this->UpdateCaretPosition(); + WChar c; - const char *s = Utf8PrevChar(this->buf + this->caretpos); - Utf8Decode(&c, s); - this->caretpos = s - this->buf; - this->UpdateCaretPosition(); + Utf8Decode(&c, this->buf + this->caretpos); return c; } @@ -261,14 +267,24 @@ WChar Textbuf::MoveCaretRight() { assert(this->CanMoveCaretRight()); - WChar c; - this->caretpos += (uint16)Utf8Decode(&c, this->buf + this->caretpos); + size_t pos = this->char_iter->Next(); + if (pos == StringIterator::END) pos = this->bytes - 1; + + this->caretpos = (uint16)pos; this->UpdateCaretPosition(); + WChar c; Utf8Decode(&c, this->buf + this->caretpos); return c; } +/** Update the character iter after the text has changed. */ +void Textbuf::UpdateStringIter() +{ + this->char_iter->SetString(this->buf); + this->caretpos = (uint16)this->char_iter->SetCurPosition(this->caretpos); +} + /** Update pixel width of the text. */ void Textbuf::UpdateWidth() { @@ -372,6 +388,8 @@ Textbuf::Textbuf(uint16 max_bytes, uint1 assert(max_bytes != 0); assert(max_chars != 0); + this->char_iter = StringIterator::Create(); + this->afilter = CS_ALPHANUMERAL; this->max_bytes = max_bytes; this->max_chars = max_chars == UINT16_MAX ? max_bytes : max_chars; @@ -381,6 +399,7 @@ Textbuf::Textbuf(uint16 max_bytes, uint1 Textbuf::~Textbuf() { + delete this->char_iter; free(this->buf); } @@ -437,6 +456,7 @@ void Textbuf::UpdateSize() assert(this->chars <= this->max_chars); this->caretpos = this->bytes - 1; + this->UpdateStringIter(); this->UpdateWidth(); this->UpdateCaretPosition(); diff --git a/src/textbuf_type.h b/src/textbuf_type.h --- a/src/textbuf_type.h +++ b/src/textbuf_type.h @@ -14,6 +14,7 @@ #include "string_type.h" #include "strings_type.h" +#include "string_base.h" /** * Return values for Textbuf::HandleKeypress @@ -61,6 +62,8 @@ struct Textbuf { void UpdateSize(); private: + StringIterator *char_iter; + bool CanDelChar(bool backspace); WChar GetNextDelChar(bool backspace); void DelChar(bool backspace); @@ -69,6 +72,7 @@ private: bool CanMoveCaretRight(); WChar MoveCaretRight(); + void UpdateStringIter(); void UpdateWidth(); void UpdateCaretPosition(); };