summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormichi_cc <michi_cc@openttd.org>2013-08-05 20:35:27 +0000
committermichi_cc <michi_cc@openttd.org>2013-08-05 20:35:27 +0000
commite7dc14b25af4b2802a956dd1cd99c187fb4acb56 (patch)
treec9fcb2f1af8e1ec6d5d6ebe03d4c30eb00eb1fed
parent33f3cf3a5daeaf9e6e5b5414696f93676249bc41 (diff)
downloadopenttd-e7dc14b25af4b2802a956dd1cd99c187fb4acb56.tar.xz
(svn r25652) -Fix: Improve text caret movement for complex scripts.
-rw-r--r--projects/openttd_vs100.vcxproj1
-rw-r--r--projects/openttd_vs100.vcxproj.filters3
-rw-r--r--projects/openttd_vs80.vcproj4
-rw-r--r--projects/openttd_vs90.vcproj4
-rw-r--r--source.list1
-rw-r--r--src/string.cpp121
-rw-r--r--src/string_base.h60
-rw-r--r--src/string_func.h7
-rw-r--r--src/textbuf.cpp46
-rw-r--r--src/textbuf_type.h4
10 files changed, 238 insertions, 13 deletions
diff --git a/projects/openttd_vs100.vcxproj b/projects/openttd_vs100.vcxproj
index 759752ce8..c993fa822 100644
--- a/projects/openttd_vs100.vcxproj
+++ b/projects/openttd_vs100.vcxproj
@@ -595,6 +595,7 @@
<ClInclude Include="..\src\story_base.h" />
<ClInclude Include="..\src\story_type.h" />
<ClInclude Include="..\src\strgen\strgen.h" />
+ <ClInclude Include="..\src\string_base.h" />
<ClInclude Include="..\src\string_func.h" />
<ClInclude Include="..\src\string_type.h" />
<ClInclude Include="..\src\stringfilter_type.h" />
diff --git a/projects/openttd_vs100.vcxproj.filters b/projects/openttd_vs100.vcxproj.filters
index f5f292dbb..3146a2427 100644
--- a/projects/openttd_vs100.vcxproj.filters
+++ b/projects/openttd_vs100.vcxproj.filters
@@ -1014,6 +1014,9 @@
<ClInclude Include="..\src\strgen\strgen.h">
<Filter>Header Files</Filter>
</ClInclude>
+ <ClInclude Include="..\src\string_base.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
<ClInclude Include="..\src\string_func.h">
<Filter>Header Files</Filter>
</ClInclude>
diff --git a/projects/openttd_vs80.vcproj b/projects/openttd_vs80.vcproj
index bb5dc66bf..96f65dce2 100644
--- a/projects/openttd_vs80.vcproj
+++ b/projects/openttd_vs80.vcproj
@@ -1655,6 +1655,10 @@
>
</File>
<File
+ RelativePath=".\..\src\string_base.h"
+ >
+ </File>
+ <File
RelativePath=".\..\src\string_func.h"
>
</File>
diff --git a/projects/openttd_vs90.vcproj b/projects/openttd_vs90.vcproj
index f197c833b..bd3b7000b 100644
--- a/projects/openttd_vs90.vcproj
+++ b/projects/openttd_vs90.vcproj
@@ -1652,6 +1652,10 @@
>
</File>
<File
+ RelativePath=".\..\src\string_base.h"
+ >
+ </File>
+ <File
RelativePath=".\..\src\string_func.h"
>
</File>
diff --git a/source.list b/source.list
index 1ac230749..04b6ed45e 100644
--- 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
index 39fc8479c..bb1f2bbd0 100644
--- 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 *s2, bool ignore_garbage_at_front)
/* Do a normal comparison if ICU is missing or if we cannot create a collator. */
return strcasecmp(s1, s2);
}
+
+#ifdef WITH_ICU
+
+#include <unicode/utext.h>
+#include <unicode/brkiter.h>
+
+/** 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
index 000000000..73439f639
--- /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 <http://www.gnu.org/licenses/>.
+ */
+
+#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
index 25608889d..b0a42b808 100644
--- 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
index 84aa7157e..6ea042244 100644
--- 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->UpdateWidth();
+ this->bytes += len;
+ this->caretpos += len;
- this->caretpos += len;
+ this->UpdateStringIter();
+ this->UpdateWidth();
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,12 +237,15 @@ WChar Textbuf::MoveCaretLeft()
{
assert(this->CanMoveCaretLeft());
- WChar c;
- const char *s = Utf8PrevChar(this->buf + this->caretpos);
- Utf8Decode(&c, s);
- this->caretpos = s - this->buf;
+ size_t pos = this->char_iter->Prev();
+ if (pos == StringIterator::END) pos = 0;
+
+ this->caretpos = (uint16)pos;
this->UpdateCaretPosition();
+ WChar c;
+ 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, uint16 max_chars)
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, uint16 max_chars)
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
index b9d3d1a34..611d7e443 100644
--- 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();
};